You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
293 lines
6.1 KiB
293 lines
6.1 KiB
package jsruntime
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"github.com/dop251/goja"
|
|
"html"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/url"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type JsRuntime struct {
|
|
MainSrc string
|
|
UseSrcFun func() string
|
|
ExtFileDir string
|
|
Relys Relys
|
|
TimeoutSec int64
|
|
runtime *goja.Runtime
|
|
mutex sync.Mutex
|
|
RenderCallBackFun func(string)
|
|
IsDebugModel bool
|
|
isRenderIng bool
|
|
}
|
|
|
|
type Rely struct {
|
|
Src string
|
|
}
|
|
|
|
type Relys []Rely
|
|
|
|
type RunMode int
|
|
|
|
const (
|
|
ModeSync RunMode = 1
|
|
//ModeAsync RunMode = 2
|
|
)
|
|
|
|
func NewJsRuntime(mainScript string, extFileDir string, rels Relys, mode RunMode) (jr *JsRuntime, err error) {
|
|
|
|
jr = &JsRuntime{
|
|
MainSrc: mainScript,
|
|
Relys: rels,
|
|
ExtFileDir: extFileDir,
|
|
TimeoutSec: 30,
|
|
}
|
|
|
|
jr.runtime = goja.New()
|
|
|
|
jr.EnableTimeoutFunc()
|
|
|
|
jr.EnableIntervalFun()
|
|
|
|
jr.EnableRequestFunc()
|
|
|
|
jr.EnableConsoleFun()
|
|
|
|
jr.SetVariable("Base64Decode", func(text string) string {
|
|
code, _ := base64.StdEncoding.DecodeString(text)
|
|
return string(code)
|
|
})
|
|
jr.SetVariable("Base64Encode", func(text string) string {
|
|
code := base64.StdEncoding.EncodeToString([]byte(text))
|
|
return code
|
|
})
|
|
|
|
jr.SetVariable("GoReadFile", func(filePath string) string {
|
|
code, err := ioutil.ReadFile(filepath.Join(jr.ExtFileDir, filePath))
|
|
if err != nil {
|
|
jr.PrintError(fmt.Sprintln("GoRunCodeByFile Read Error:", err.Error()), string(code))
|
|
}
|
|
return string(code)
|
|
})
|
|
|
|
jr.SetVariable("GoRunCode", func(code string) {
|
|
err = jr.RunCode(string(code))
|
|
if err != nil {
|
|
jr.PrintError(fmt.Sprintln("GoRunCode Run Error:", err.Error()), code)
|
|
}
|
|
})
|
|
|
|
jr.SetVariable("GoRunCodeByFile", func(filePath string) {
|
|
code, err := ioutil.ReadFile(filepath.Join(jr.ExtFileDir, filePath))
|
|
if err != nil {
|
|
jr.PrintError(fmt.Sprintln("GoRunCodeByFile Read Error:", err.Error()), string(code))
|
|
}
|
|
err = jr.RunCode(string(code))
|
|
if err != nil {
|
|
jr.PrintError(fmt.Sprintln("GoRunCodeByFile Run Error:", err.Error()), string(code))
|
|
}
|
|
})
|
|
err = jr.InitJsCode()
|
|
if err != nil {
|
|
log.Println("InitJsCode", err)
|
|
}
|
|
|
|
return jr, nil
|
|
}
|
|
|
|
func (jr *JsRuntime) PrintError(msg string, code string) {
|
|
|
|
log.Println(msg)
|
|
if !jr.IsDebugModel {
|
|
return
|
|
}
|
|
|
|
var errLine string
|
|
var line = -1
|
|
evalErr := strings.Split(msg, "<eval>")
|
|
if len(evalErr) > 1 {
|
|
evalErr = strings.Split(evalErr[1], ":")
|
|
if len(evalErr) > 1 {
|
|
line, _ = strconv.Atoi(evalErr[1])
|
|
|
|
}
|
|
}
|
|
if line == -1 {
|
|
evalErr = strings.Split(msg, ": Line ")
|
|
if len(evalErr) > 1 {
|
|
evalErr = strings.Split(evalErr[1], ":")
|
|
if len(evalErr) > 0 {
|
|
line, _ = strconv.Atoi(evalErr[0])
|
|
}
|
|
}
|
|
}
|
|
//log.Println("error line", line,evalErr)
|
|
if line > -1 {
|
|
lines := strings.Split(string(code), "\n")
|
|
linesStart := line - 15
|
|
linesEnd := line + 15
|
|
if linesStart < 0 {
|
|
linesStart = 0
|
|
}
|
|
if len(lines)-1 < linesEnd {
|
|
linesEnd = len(lines)
|
|
}
|
|
if linesStart > linesEnd {
|
|
errLine = "无法定位的报错位置"
|
|
linesStart = 0
|
|
} else {
|
|
//
|
|
//log.Println("error line linesStart", linesStart)
|
|
//log.Println("error line linesEnd", linesEnd)
|
|
|
|
//linesEnd = linesStart + linesEnd
|
|
for e := range lines[linesStart:linesEnd] {
|
|
if e+linesStart+1 == line {
|
|
lines[linesStart:linesEnd][e] = fmt.Sprintf(`%2d: >>>>>> %s`, e+linesStart, lines[linesStart:linesEnd][e])
|
|
} else {
|
|
lines[linesStart:linesEnd][e] = fmt.Sprintf(`%2d: %s`, e+linesStart, lines[linesStart:linesEnd][e])
|
|
}
|
|
}
|
|
errLine = strings.Join(lines[linesStart:linesEnd], "\n")
|
|
}
|
|
}
|
|
|
|
if jr.RenderCallBackFun != nil {
|
|
jr.RenderCallBackFun(fmt.Sprintf(`
|
|
<body style="background: #eeeeee;padding: 5%%;width:85%%;">
|
|
<h3 style="color: red;">%s</h3>
|
|
<div style="background: #fff;">
|
|
<pre style="width:95%%;font-size: 14px; padding: 2%%; ">%s<pre>
|
|
</div>
|
|
</body>
|
|
`, html.EscapeString(msg), html.EscapeString(errLine)))
|
|
jr.RenderCallBackFun = func(s string) {
|
|
|
|
}
|
|
}
|
|
}
|
|
func (jr *JsRuntime) InitJsCode() error {
|
|
|
|
for e := range jr.Relys {
|
|
//start := time.Now().UnixNano()
|
|
err := jr.RunCode(jr.Relys[e].Src)
|
|
//end := time.Now().UnixNano()
|
|
|
|
//log.Println(e, "加载时间:", end-start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (jr *JsRuntime) SetVariable(name string, value interface{}) {
|
|
jr.runtime.Set(name, value)
|
|
}
|
|
|
|
var codeMap sync.Map
|
|
|
|
func (jr *JsRuntime) md5(data string) string {
|
|
return fmt.Sprintf("%x", md5.Sum([]byte(data)))
|
|
}
|
|
|
|
func (jr *JsRuntime) RunCode(code string) error {
|
|
id := jr.md5(code)
|
|
var p interface{}
|
|
var ok bool
|
|
p, ok = codeMap.Load(id)
|
|
if !ok {
|
|
pro, err := goja.Compile("", code, false)
|
|
if err != nil {
|
|
log.Println("Compile Error")
|
|
return err
|
|
}
|
|
codeMap.Store(id, pro)
|
|
p = pro
|
|
}
|
|
program := p.(*goja.Program)
|
|
|
|
_, err := jr.runtime.RunProgram(program)
|
|
//log.Println("RunCode result", result)
|
|
return err
|
|
}
|
|
|
|
func (jr *JsRuntime) Render(filePath, href, tplSrc string, GoExtData interface{}, cb func(data string)) (err error) {
|
|
|
|
jr.RenderCallBackFun = cb
|
|
jr.mutex.Lock()
|
|
defer jr.mutex.Unlock()
|
|
|
|
runtime := jr.runtime
|
|
mainSrc := jr.MainSrc
|
|
isLock := true
|
|
jr.isRenderIng = true
|
|
|
|
//timeoutSec := jr.TimeoutSec
|
|
defer func() {
|
|
jr.isRenderIng = false
|
|
isLock = false
|
|
jr.runtime.ClearInterrupt()
|
|
}()
|
|
|
|
var useSrc string
|
|
if jr.UseSrcFun != nil {
|
|
useSrc = jr.UseSrcFun()
|
|
}
|
|
if useSrc != "" {
|
|
err = jr.RunCode(useSrc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
url, err := url.Parse(href)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
url2, err := url.Parse(strings.Replace(filePath, `\`, "/", -1))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
runtime.Set("GoExtData", GoExtData)
|
|
|
|
runtime.Set("GoHtmlSrc", tplSrc)
|
|
|
|
runtime.Set("GoHref", href)
|
|
|
|
runtime.Set("GoQuery", url.Query().Encode())
|
|
|
|
runtime.Set("GoPath", url2.Path)
|
|
|
|
runtime.Set("GoReturn", func(data string) {
|
|
jr.RenderCallBackFun(data)
|
|
})
|
|
|
|
t := time.AfterFunc(time.Duration(jr.TimeoutSec)*time.Second, func() {
|
|
if jr.isRenderIng {
|
|
jr.runtime.Interrupt("time out")
|
|
jr.runtime.ClearInterrupt()
|
|
}
|
|
})
|
|
|
|
defer t.Stop()
|
|
|
|
err = jr.RunCode(mainSrc)
|
|
if err != nil {
|
|
jr.PrintError(err.Error(), "")
|
|
return err
|
|
}
|
|
|
|
return
|
|
}
|