|
|
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 }
|