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

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
}