Initial commit (code only without large binaries)
This commit is contained in:
445
EdgeNode/internal/js/context.go
Normal file
445
EdgeNode/internal/js/context.go
Normal file
@@ -0,0 +1,445 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build script
|
||||
|
||||
package js
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"rogchap.com/v8go"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var namespaceReg = regexp.MustCompile(`^[a-zA-Z0-9_.]+$`)
|
||||
|
||||
type Context struct {
|
||||
rawContext *v8go.Context
|
||||
isolate *Isolate
|
||||
objectTemplate *ObjectTemplate
|
||||
|
||||
goObjectId uint32
|
||||
goObjectMap map[uint32]any
|
||||
goObjectLocker sync.RWMutex
|
||||
|
||||
serverId int64
|
||||
|
||||
countUses int
|
||||
}
|
||||
|
||||
func NewContext(rawContext *v8go.Context, isolate *Isolate) (*Context, error) {
|
||||
var ctx = &Context{
|
||||
rawContext: rawContext,
|
||||
isolate: isolate,
|
||||
goObjectMap: map[uint32]any{},
|
||||
}
|
||||
err := ctx.init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (this *Context) init() error {
|
||||
this.objectTemplate = NewObjectTemplate(this)
|
||||
|
||||
// 加载内置函数库
|
||||
err := this.loadLibraries()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Context) Isolate() *Isolate {
|
||||
return this.isolate
|
||||
}
|
||||
|
||||
func (this *Context) Run(source string, origin string) (*v8go.Value, error) {
|
||||
return this.rawContext.RunScript(source, origin)
|
||||
}
|
||||
|
||||
func (this *Context) RunScript(script *Script) (*v8go.Value, error) {
|
||||
return script.rawScript.Run(this.rawContext)
|
||||
}
|
||||
|
||||
func (this *Context) RawContext() *v8go.Context {
|
||||
return this.rawContext
|
||||
}
|
||||
|
||||
func (this *Context) NewObjectTemplate() *ObjectTemplate {
|
||||
return NewObjectTemplate(this)
|
||||
}
|
||||
|
||||
// NewValue 包装新的值
|
||||
// TODO 支持[]byte
|
||||
func (this *Context) NewValue(value any) (*v8go.Value, error) {
|
||||
if value == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch x := value.(type) {
|
||||
case *v8go.Value:
|
||||
return x, nil
|
||||
case *v8go.Object:
|
||||
return x.Value, nil
|
||||
case int8:
|
||||
value = int32(x)
|
||||
case uint8:
|
||||
value = uint32(x)
|
||||
case int:
|
||||
value = int64(x)
|
||||
case uint:
|
||||
value = uint64(x)
|
||||
case int16:
|
||||
value = int32(x)
|
||||
case uint16:
|
||||
value = uint32(x)
|
||||
case string, int32, uint32, bool, int64, uint64, *big.Int:
|
||||
// 内置支持的类型,我们 DO NOTHING HERE
|
||||
default:
|
||||
var rv = reflect.ValueOf(value)
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice:
|
||||
arrayValue, err := this.Run("new Array()", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var obj = arrayValue.Object()
|
||||
err = obj.Set("length", uint32(rv.Len()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
var rvv = rv.Index(i)
|
||||
if !rvv.CanInterface() {
|
||||
continue
|
||||
}
|
||||
ev, err := this.NewValue(rvv.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = obj.SetIdx(uint32(i), ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return obj.Value, nil
|
||||
case reflect.Map:
|
||||
obj, err := this.objectTemplate.NewInstance(this)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, kv := range rv.MapKeys() {
|
||||
var rvv = rv.MapIndex(kv)
|
||||
if !rvv.CanInterface() {
|
||||
continue
|
||||
}
|
||||
ev, err := this.NewValue(rvv.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var kind = kv.Kind()
|
||||
|
||||
// 支持map[any]Type
|
||||
if kind == reflect.Interface {
|
||||
kind = kv.Elem().Kind()
|
||||
kv = kv.Elem()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.String:
|
||||
err = obj.Set(kv.String(), ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case reflect.Int, reflect.Uint, reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64, reflect.Float32, reflect.Float64:
|
||||
err = obj.Set(types.String(kv.Interface()), ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case reflect.Bool:
|
||||
if kv.Bool() {
|
||||
err = obj.Set("1", ev)
|
||||
} else {
|
||||
err = obj.Set("0", ev)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
// 我们直接忽略那些类型不符的Key
|
||||
}
|
||||
}
|
||||
return obj.Value, nil
|
||||
case reflect.Struct:
|
||||
obj, err := this.objectTemplate.NewInstance(this)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var rt = rv.Type()
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
var field = rt.Field(i)
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
var jsonTag = field.Tag.Get("json")
|
||||
if len(jsonTag) == 0 {
|
||||
jsonTag = strings.ToLower(field.Name[:1]) + field.Name[1:]
|
||||
}
|
||||
|
||||
var rFieldValue = rv.Field(i)
|
||||
if !rFieldValue.CanInterface() {
|
||||
continue
|
||||
}
|
||||
ev, err := this.NewValue(rFieldValue.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = obj.Set(jsonTag, ev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return obj.Value, nil
|
||||
case reflect.Ptr:
|
||||
rv = reflect.Indirect(rv)
|
||||
if rv.CanInterface() {
|
||||
return this.NewValue(rv.Interface())
|
||||
}
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
return v8go.NewValue(this.isolate.RawIsolate(), value)
|
||||
}
|
||||
|
||||
func (this *Context) AddGoObject(obj any) (objectId uint32) {
|
||||
this.goObjectLocker.Lock()
|
||||
defer this.goObjectLocker.Unlock()
|
||||
|
||||
this.goObjectId++
|
||||
this.goObjectMap[this.goObjectId] = obj
|
||||
return this.goObjectId
|
||||
}
|
||||
|
||||
func (this *Context) AddGoRequestObject(obj RequestInterface) (objectId uint32) {
|
||||
return this.AddGoObject(obj)
|
||||
}
|
||||
|
||||
func (this *Context) AddGoResponseObject(obj ResponseInterface) (objectId uint32) {
|
||||
return this.AddGoObject(obj)
|
||||
}
|
||||
|
||||
func (this *Context) GoObject(objectId uint32) (obj any, ok bool) {
|
||||
this.goObjectLocker.RLock()
|
||||
obj, ok = this.goObjectMap[objectId]
|
||||
this.goObjectLocker.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (this *Context) GoObjectMap() map[uint32]any {
|
||||
this.goObjectLocker.RLock()
|
||||
defer this.goObjectLocker.RUnlock()
|
||||
return this.goObjectMap
|
||||
}
|
||||
|
||||
// Done 单次调用用Context之后,需要调用Done()来计数和清除绑定的系统资源
|
||||
func (this *Context) Done() {
|
||||
this.countUses++
|
||||
|
||||
for _, library := range SharedLibraryManager.All() {
|
||||
library.JSDone(this)
|
||||
}
|
||||
|
||||
this.goObjectLocker.Lock()
|
||||
this.goObjectMap = map[uint32]any{}
|
||||
this.goObjectId = 0
|
||||
this.goObjectLocker.Unlock()
|
||||
}
|
||||
|
||||
func (this *Context) Cleanup() {
|
||||
this.Done()
|
||||
}
|
||||
|
||||
func (this *Context) Close() {
|
||||
this.Done()
|
||||
|
||||
for _, library := range SharedLibraryManager.All() {
|
||||
library.JSDispose(this)
|
||||
}
|
||||
|
||||
this.rawContext.Close()
|
||||
}
|
||||
|
||||
func (this *Context) Global() *v8go.Object {
|
||||
return this.rawContext.Global()
|
||||
}
|
||||
|
||||
func (this *Context) loadLibraries() error {
|
||||
// 运行公共函数脚本
|
||||
_, err := this.Run(LibraryUtilsJavascript, "utils.js")
|
||||
if err != nil {
|
||||
return this.WrapJSErr(err)
|
||||
}
|
||||
|
||||
// libraries
|
||||
for _, library := range SharedLibraryManager.All() {
|
||||
library.JSInit(this)
|
||||
|
||||
// 准备命名空间
|
||||
var namespace = library.JSNamespace()
|
||||
if !namespaceReg.MatchString(namespace) {
|
||||
return errors.New("load library failed: invalid namespace '" + namespace + "'")
|
||||
}
|
||||
if !strings.HasPrefix(namespace, "gojs.") {
|
||||
return errors.New("load library failed: namespace '" + namespace + "' must be started with prefix 'gojs.'")
|
||||
}
|
||||
|
||||
_, err = this.Run("gojs.prepareNamespace(\""+namespace+"\")", "utils.js")
|
||||
if err != nil {
|
||||
return fmt.Errorf("prepare namespace '%s' failed: %w", namespace, err)
|
||||
}
|
||||
|
||||
// 运行脚本
|
||||
var prototypeJS = strings.ReplaceAll(library.JSPrototype(), "$this.", library.JSNamespace()+".")
|
||||
_, err = this.Run(prototypeJS, namespace+".js")
|
||||
if err != nil {
|
||||
return fmt.Errorf("run '%s' prototype failed: %w", namespace, this.WrapJSErr(err))
|
||||
}
|
||||
|
||||
// 增加函数
|
||||
var reflectValue = reflect.ValueOf(library)
|
||||
libValue, err := this.Run(namespace, "utils.js")
|
||||
if err != nil {
|
||||
return fmt.Errorf("get namespace object '%s' failed: %w", namespace, err)
|
||||
}
|
||||
var libObject = libValue.Object()
|
||||
var argumentType = reflect.TypeOf(&FunctionArguments{})
|
||||
for i := 0; i < reflectValue.NumMethod(); i++ {
|
||||
var method = reflectValue.Method(i)
|
||||
if !method.IsValid() {
|
||||
continue
|
||||
}
|
||||
var typeMethod = reflectValue.Type().Method(i)
|
||||
var methodName = typeMethod.Name
|
||||
if strings.HasPrefix(methodName, "JS") {
|
||||
continue
|
||||
}
|
||||
|
||||
var methodType = typeMethod.Type
|
||||
if methodType.NumIn() != 2 /** this, arguments **/ || methodType.NumOut() != 2 /** result, error **/ {
|
||||
continue
|
||||
}
|
||||
|
||||
if !methodType.In(1).AssignableTo(argumentType) {
|
||||
continue
|
||||
}
|
||||
|
||||
if methodType.Out(1).Name() != "error" {
|
||||
continue
|
||||
}
|
||||
|
||||
err = libObject.Set(methodName, v8go.NewFunctionTemplate(this.isolate.RawIsolate(), func(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||
out := method.Call([]reflect.Value{reflect.ValueOf(NewFunctionArguments(this, info))})
|
||||
if !out[1].IsNil() {
|
||||
errValue, _ := this.NewValue(out[1].Interface().(error).Error())
|
||||
this.isolate.ThrowException(errValue)
|
||||
return nil
|
||||
}
|
||||
|
||||
v, err := this.NewValue(out[0].Interface())
|
||||
if err != nil {
|
||||
errValue, _ := this.NewValue(err.Error())
|
||||
this.isolate.ThrowException(errValue)
|
||||
return nil
|
||||
}
|
||||
return v
|
||||
}).GetFunction(this.rawContext))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// common scripts
|
||||
var commonScripts = sharedCommonScripts
|
||||
if len(commonScripts) > 0 {
|
||||
for _, commonScript := range commonScripts {
|
||||
_, err := this.Run(commonScript.Code, commonScript.Filename)
|
||||
if err != nil {
|
||||
remotelogs.Error("SCRIPT", "load common script '"+commonScript.Filename+"' failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load common files from directory
|
||||
err = this.loadLibrariesFromDisk()
|
||||
if err != nil {
|
||||
remotelogs.Error("SCRIPT", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Context) WrapJSErr(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
jsErr, ok := err.(*v8go.JSError)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("%w, location: %s", jsErr, jsErr.Location)
|
||||
}
|
||||
|
||||
func (this *Context) SetServerId(serverId int64) {
|
||||
this.serverId = serverId
|
||||
}
|
||||
|
||||
func (this *Context) ServerId() int64 {
|
||||
return this.serverId
|
||||
}
|
||||
|
||||
func (this *Context) ReadLineFromLibrary(name string, line int) string {
|
||||
if line < 1 {
|
||||
return ""
|
||||
}
|
||||
for _, library := range SharedLibraryManager.All() {
|
||||
if library.JSNamespace() == name || library.JSNamespace()+".js" == name {
|
||||
var lines = strings.Split(library.JSPrototype(), "\n")
|
||||
if len(lines) >= line {
|
||||
return lines[line-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 从硬盘中加载脚本
|
||||
func (this *Context) loadLibrariesFromDisk() error {
|
||||
var dir = Tea.Root + "/scripts/js/"
|
||||
var jsFiles = files.NewFile(dir).List()
|
||||
for _, file := range jsFiles {
|
||||
if file.IsFile() && strings.HasSuffix(file.Name(), ".js") {
|
||||
s, err := file.ReadAllString()
|
||||
if err != nil {
|
||||
return fmt.Errorf("load local common script '%s' failed: %w", file.Path(), err)
|
||||
}
|
||||
|
||||
_, err = this.Run(s, file.Name())
|
||||
if err != nil {
|
||||
return fmt.Errorf("load local common script '%s' failed: %w", file.Path(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user