This commit is contained in:
unknown
2026-02-04 20:27:13 +08:00
commit 3b042d1dad
9410 changed files with 1488147 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
)
var sharedCommonScripts []*serverconfigs.CommonScript
func SetCommonScripts(commonScripts []*serverconfigs.CommonScript) {
sharedCommonScripts = commonScripts
}
func GetCommonScripts() []*serverconfigs.CommonScript {
return sharedCommonScripts
}
func IsSameCommonScripts(commonScripts []*serverconfigs.CommonScript) bool {
var oldScripts = sharedCommonScripts // 为了操作安全需要拷贝
var newScripts = commonScripts
if len(oldScripts) != len(newScripts) {
return false
}
var oldMap = map[string]*serverconfigs.CommonScript{} // filename => CommonScript
for _, script := range oldScripts {
oldMap[script.Filename] = script
}
var newMap = map[string]*serverconfigs.CommonScript{} // filename => CommonScript
for _, script := range newScripts {
newMap[script.Filename] = script
}
for filename, oldScript := range oldMap {
newScript, ok := newMap[filename]
if !ok {
return false
}
if oldScript.Code != newScript.Code {
return false
}
}
return true
}

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

View File

@@ -0,0 +1,61 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
type ContextPool struct {
ch chan *Context
maxSize int
}
func NewContextPool(maxSize int) *ContextPool {
if maxSize <= 0 {
maxSize = 128
}
return &ContextPool{
ch: make(chan *Context, maxSize),
maxSize: maxSize,
}
}
func (this *ContextPool) MaxSize() int {
return this.maxSize
}
func (this *ContextPool) Size() int {
return len(this.ch)
}
func (this *ContextPool) Get() *Context {
// 不需要default语句我们要确保整个Pool中只有固定的元素防止内存溢出
select {
case ctx := <-this.ch:
return ctx
}
}
func (this *ContextPool) Put(ctx *Context) {
ctx.Cleanup()
select {
case this.ch <- ctx:
default:
ctx.Close()
}
}
func (this *ContextPool) IsUsing() bool {
return len(this.ch) != this.maxSize
}
func (this *ContextPool) Close() {
Loop:
for {
select {
case ctx := <-this.ch:
ctx.Close()
default:
break Loop
}
}
}

View File

@@ -0,0 +1,42 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js_test
import (
"github.com/TeaOSLab/EdgeNode/internal/js"
"testing"
"time"
)
func TestContext_LoadLibrary(t *testing.T) {
isolate, err := js.NewIsolate()
if err != nil {
t.Fatal(err)
}
defer isolate.Dispose()
ctx, err := isolate.GetContext()
if err != nil {
t.Fatal(err)
}
var before = time.Now()
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
v, err := ctx.Run(`
let url = new gojs.net.URL("https://user:pass@goedge.cn/docs?nav=1#link")
JSON.stringify(url)
//url.toString()
`, "")
if err != nil {
t.Fatal(err)
}
t.Log(v)
}
func TestContext_ReadLine(t *testing.T) {
}

View File

@@ -0,0 +1,8 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
package js
import "errors"
var ErrGoObjectNotFound = errors.New("go object not found")

View File

@@ -0,0 +1,332 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
import (
"github.com/iwind/TeaGo/types"
"rogchap.com/v8go"
)
type FunctionArguments struct {
ctx *Context
rawInfo *v8go.FunctionCallbackInfo
}
func NewFunctionArguments(ctx *Context, rawInfo *v8go.FunctionCallbackInfo) *FunctionArguments {
return &FunctionArguments{
ctx: ctx,
rawInfo: rawInfo,
}
}
func (this *FunctionArguments) Context() *Context {
return this.ctx
}
func (this *FunctionArguments) Args() []*v8go.Value {
return this.rawInfo.Args()
}
func (this *FunctionArguments) ArgAt(index int) (value *v8go.Value, ok bool) {
var args = this.rawInfo.Args()
if index < len(args) {
return args[index], true
}
return nil, false
}
func (this *FunctionArguments) StringAt(index int) (value string, ok bool) {
var args = this.rawInfo.Args()
if index < len(args) {
var arg = args[index]
if arg.IsString() {
return arg.String(), true
}
return "", false
}
return "", false
}
func (this *FunctionArguments) FormatStringAt(index int) (value string, ok bool) {
var args = this.rawInfo.Args()
if index < len(args) {
var arg = args[index]
if arg.IsString() || arg.IsNumber() {
return arg.String(), true
}
return "", false
}
return "", false
}
func (this *FunctionArguments) StringsAt(index int) (value []string, ok bool) {
var args = this.rawInfo.Args()
if index < len(args) {
var arg = args[index]
if arg.IsArray() {
var obj = arg.Object()
arrayLengthValue, err := obj.Get("length")
if err != nil {
return
}
if arrayLengthValue.IsUint32() {
var arrayLength = arrayLengthValue.Uint32()
for i := uint32(0); i < arrayLength; i++ {
elementValue, err := obj.GetIdx(i)
if err != nil {
return
}
if elementValue.IsString() {
value = append(value, elementValue.String())
}
}
ok = true
return
}
}
}
return
}
func (this *FunctionArguments) FormatStringsAt(index int) (value []string, ok bool) {
var args = this.rawInfo.Args()
if index < len(args) {
var arg = args[index]
if arg.IsArray() {
var obj = arg.Object()
arrayLengthValue, err := obj.Get("length")
if err != nil {
return
}
if arrayLengthValue.IsUint32() {
var arrayLength = arrayLengthValue.Uint32()
for i := uint32(0); i < arrayLength; i++ {
elementValue, err := obj.GetIdx(i)
if err != nil {
return
}
if elementValue.IsString() || elementValue.IsNumber() {
value = append(value, elementValue.String())
}
}
ok = true
return
}
}
}
return
}
func (this *FunctionArguments) ObjectAt(index int) (obj *Object, ok bool) {
var args = this.rawInfo.Args()
if index < len(args) {
var arg = args[index]
if arg.IsObject() {
return NewObject(arg.Object()), true
}
}
return nil, false
}
func (this *FunctionArguments) GoObjectAt(index int) (value any, ok bool) {
var args = this.rawInfo.Args()
if index < len(args) {
var arg = args[index]
if arg.IsUint32() {
return this.ctx.GoObject(arg.Uint32())
}
}
return nil, false
}
func (this *FunctionArguments) IntAt(index int) (value int, ok bool) {
var args = this.rawInfo.Args()
if index < len(args) {
var arg = args[index]
if arg.IsInt32() {
value = types.Int(arg.Int32())
ok = true
return
}
if arg.IsUint32() {
value = types.Int(arg.Uint32())
ok = true
return
}
if arg.IsBigInt() {
value = types.Int(arg.BigInt().Int64())
ok = true
return
}
if arg.IsNumber() {
value = types.Int(arg.Number())
ok = true
return
}
if arg.IsString() {
var s = arg.String()
if this.isDigitString(s) {
value = types.Int(s)
ok = true
return
}
}
}
return 0, false
}
func (this *FunctionArguments) Int64At(index int) (value int64, ok bool) {
var args = this.rawInfo.Args()
if index < len(args) {
var arg = args[index]
if arg.IsInt32() {
value = types.Int64(arg.Int32())
ok = true
return
}
if arg.IsUint32() {
value = types.Int64(arg.Uint32())
ok = true
return
}
if arg.IsBigInt() {
value = types.Int64(arg.BigInt().Int64())
ok = true
return
}
if arg.IsNumber() {
value = types.Int64(arg.Number())
ok = true
return
}
if arg.IsString() {
var s = arg.String()
if this.isDigitString(s) {
value = types.Int64(s)
ok = true
return
}
}
}
return 0, false
}
func (this *FunctionArguments) Float32At(index int) (value float32, ok bool) {
var args = this.rawInfo.Args()
if index < len(args) {
var arg = args[index]
if arg.IsInt32() {
value = types.Float32(arg.Int32())
ok = true
return
}
if arg.IsUint32() {
value = types.Float32(arg.Uint32())
ok = true
return
}
if arg.IsBigInt() {
value = types.Float32(arg.BigInt().Int64())
ok = true
return
}
if arg.IsNumber() {
value = types.Float32(arg.Number())
ok = true
return
}
if arg.IsString() {
var s = arg.String()
// TODO 支持小数点
if this.isDigitString(s) {
value = types.Float32(s)
ok = true
return
}
}
}
return 0, false
}
func (this *FunctionArguments) Float64At(index int) (value float64, ok bool) {
var args = this.rawInfo.Args()
if index < len(args) {
var arg = args[index]
if arg.IsInt32() {
value = types.Float64(arg.Int32())
ok = true
return
}
if arg.IsUint32() {
value = types.Float64(arg.Uint32())
ok = true
return
}
if arg.IsBigInt() {
value = types.Float64(arg.BigInt().Int64())
ok = true
return
}
if arg.IsNumber() {
value = arg.Number()
ok = true
return
}
if arg.IsString() {
var s = arg.String()
// TODO 支持小数点
if this.isDigitString(s) {
value = types.Float64(s)
ok = true
return
}
}
}
return 0, false
}
func (this *FunctionArguments) BytesAt(index int) ([]byte, bool) {
var args = this.rawInfo.Args()
if index < len(args) {
var arg = args[index]
if arg.IsArrayBuffer() {
sizeValue, err := arg.Object().Get("byteLength")
if err != nil {
return nil, false
}
var size = int(sizeValue.Int32())
if size == 0 {
return []byte{}, true
}
var result = make([]byte, size)
for i := 0; i < size; i++ {
byteValue, err := arg.Object().GetIdx(uint32(i))
if err != nil {
return nil, false
}
result[i] = types.Uint8(byteValue.Uint32())
}
return result, true
}
}
return nil, false
}
func (this *FunctionArguments) This() *v8go.Object {
return this.rawInfo.This()
}
func (this *FunctionArguments) isDigitString(s string) bool {
if len(s) == 0 || len(s) > 11 /** 我们只解析不大于11位的 **/ {
return false
}
for _, c := range s {
if c < '0' || c > '9' {
return false
}
}
return true
}

View File

@@ -0,0 +1,7 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
type FunctionCallback func(info *FunctionArguments) (any, error)

View File

@@ -0,0 +1,115 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
import (
"rogchap.com/v8go"
"sync/atomic"
)
const maxContextsPerIsolate = 8 // TODO 根据系统内存动态调整
type Isolate struct {
rawIsolate *v8go.Isolate
contextPool *ContextPool
countUses uint32
isDisposed bool
}
func NewIsolate() (*Isolate, error) {
return NewIsolateWithContexts(maxContextsPerIsolate)
}
func NewIsolateWithContexts(contextSize int) (*Isolate, error) {
var isolate = &Isolate{
rawIsolate: v8go.NewIsolate(),
contextPool: NewContextPool(contextSize),
}
err := isolate.init()
if err != nil {
isolate.Dispose()
return nil, err
}
return isolate, nil
}
func (this *Isolate) init() error {
// 初始化context
for i := 0; i < this.contextPool.MaxSize(); i++ {
ctx, err := this.createContext()
if err != nil {
return err
}
this.contextPool.Put(ctx)
}
return nil
}
func (this *Isolate) OverUses() bool {
return atomic.LoadUint32(&this.countUses) > 4096
}
func (this *Isolate) GetContext() (*Context, error) {
atomic.AddUint32(&this.countUses, 1)
ctx := this.contextPool.Get()
if ctx != nil {
return ctx, nil
}
ctx, err := this.createContext()
if err != nil {
return nil, err
}
return ctx, nil
}
func (this *Isolate) PutContext(ctx *Context) {
ctx.Done()
this.contextPool.Put(ctx)
}
func (this *Isolate) Compile(source string, origin string) (*Script, error) {
script, err := this.rawIsolate.CompileUnboundScript(source, origin, v8go.CompileOptions{
Mode: v8go.CompileModeDefault,
})
if err != nil {
return nil, err
}
return NewScript(script), nil
}
func (this *Isolate) ContextPool() *ContextPool {
return this.contextPool
}
func (this *Isolate) RawIsolate() *v8go.Isolate {
return this.rawIsolate
}
func (this *Isolate) ThrowException(value *v8go.Value) *v8go.Value {
return this.rawIsolate.ThrowException(value)
}
func (this *Isolate) IsUsing() bool {
return this.contextPool.IsUsing()
}
func (this *Isolate) Dispose() {
if this.isDisposed {
return
}
this.isDisposed = true
this.contextPool.Close()
this.rawIsolate.Dispose()
}
func (this *Isolate) createContext() (*Context, error) {
return NewContext(v8go.NewContext(this.rawIsolate), this)
}

View File

@@ -0,0 +1,161 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
import (
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils/goman"
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
"github.com/iwind/TeaGo/Tea"
"time"
)
type IsolatePool struct {
hotIsolates []*Isolate // 当前正在使用的isolates
coldIsolates []*Isolate // 等待回收的isolates
coldTicker *time.Ticker
coldIndex int
maxSize int
index int
}
func NewIsolatePool(maxSize int) (*IsolatePool, error) {
if maxSize <= 0 {
maxSize = 1
}
if maxSize > 512 {
maxSize = 512
}
var pool = &IsolatePool{
hotIsolates: make([]*Isolate, maxSize),
maxSize: maxSize,
}
err := pool.init()
if err != nil {
remotelogs.Error("SCRIPT", "create isolate pool failed: "+err.Error())
pool.Dispose()
return nil, err
}
return pool, nil
}
func NewAutoIsolatePool() (*IsolatePool, error) {
// 每 512M 内存一个isolate
var totalMemory = memutils.SystemMemoryGB()
var count = totalMemory * 2
if count <= 0 {
count = 2
}
// 防止在有些系统上OOM
if count > 256 {
count = 256
}
return NewIsolatePool(count)
}
func (this *IsolatePool) init() error {
for i := 0; i < this.maxSize; i++ {
isolate, err := NewIsolate()
if err != nil {
return err
}
this.hotIsolates[i] = isolate
}
// run init functions
if len(this.hotIsolates) > 0 {
var isolate = this.hotIsolates[0]
ctx, err := isolate.GetContext()
if err == nil {
_, err = ctx.Run("gojs.runOnce()", "utils.js")
if err != nil {
remotelogs.Error("SCRIPT", "run once functions failed: "+err.Error())
}
isolate.PutContext(ctx)
}
}
this.coldTicker = time.NewTicker(5 * time.Second)
if Tea.IsTesting() {
this.coldTicker = time.NewTicker(5 * time.Second)
}
goman.New(func() {
for range this.coldTicker.C {
this.tick()
}
})
return nil
}
func (this *IsolatePool) tick() {
// dispose cold isolates
if len(this.coldIsolates) > 0 {
var newIndex = -1
for index, coldIsolate := range this.coldIsolates {
if coldIsolate.IsUsing() {
break
}
newIndex = index
coldIsolate.Dispose()
}
if newIndex >= 0 {
this.coldIsolates = this.coldIsolates[newIndex+1:]
}
}
// add new isolate
if this.coldIndex > this.maxSize-1 {
this.coldIndex = 0
}
var oldIsolate = this.hotIsolates[this.coldIndex]
if oldIsolate.OverUses() {
newIsolate, err := NewIsolate()
if err != nil {
remotelogs.Error("SCRIPT", "create isolate failed: "+err.Error())
return
}
this.hotIsolates[this.coldIndex] = newIsolate
this.coldIsolates = append(this.coldIsolates, oldIsolate)
}
this.coldIndex++
}
func (this *IsolatePool) GetContext() (*Context, error) {
this.index++ // 不需要加锁,这里没必要非常严格
var index = this.index
if index >= this.maxSize {
index = 0
this.index = 0
}
// TODO 未来实现循环查找可用的context防止因单个context执行时间过长阻塞整个isolate
return this.hotIsolates[index].GetContext()
}
func (this *IsolatePool) PutContext(ctx *Context) {
if ctx != nil {
ctx.Isolate().PutContext(ctx)
}
}
func (this *IsolatePool) MaxSize() int {
return this.maxSize
}
func (this *IsolatePool) Dispose() {
for _, isolate := range this.hotIsolates {
isolate.Dispose()
}
}

View File

@@ -0,0 +1,165 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js_test
import (
"github.com/TeaOSLab/EdgeNode/internal/js"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/types"
"runtime"
"runtime/debug"
"testing"
"time"
)
func TestIsolatePool_GetContext(t *testing.T) {
pool, err := js.NewIsolatePool(1)
if err != nil {
t.Fatal(err)
}
ctx, err := pool.GetContext()
if err != nil {
t.Fatal(err)
}
defer pool.PutContext(ctx)
v, err := ctx.Run("1+1", "")
if err != nil {
t.Fatal(err)
}
t.Log("result:", v)
}
func TestIsolatePool_Memory(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
t.Log("creating pool")
var req = &FakeRequest{}
var before = time.Now()
pool, err := js.NewAutoIsolatePool()
if err != nil {
t.Fatal(err)
}
t.Log("created pool", time.Since(before).Seconds()*1000, "ms")
time.Sleep(10 * time.Second)
t.Log("starting")
for i := 0; i < 10_000_000; i++ {
if i > 0 && i%50000 == 0 {
t.Log(i)
time.Sleep(1 * time.Second)
}
ctx, err := pool.GetContext()
if err != nil {
t.Fatal(err)
}
var requestObjectId = ctx.AddGoRequestObject(req)
_, err = ctx.Run(`(function () {
let req = new gojs.net.http.Request()
req.setGoObject(`+types.String(requestObjectId)+`)
let resp = new gojs.net.http.Response()
if (req.path.match(/webhook/)) {
req.deleteHeader("Cookie")
req.setHeader("User-Agent", "gojs/1.0")
resp.send(200, "Hello, World")
}
if (req.path == "/favicon.ico") {
req.done()
}
})()`, "")
if err != nil {
t.Fatal(err)
}
pool.PutContext(ctx)
}
t.Log("created", time.Since(before).Seconds(), "s")
if testutils.IsSingleTesting() {
time.Sleep(5 * time.Second)
t.Log("gc")
runtime.GC()
debug.FreeOSMemory()
time.Sleep(120 * time.Second)
t.Log("gc again")
runtime.GC()
debug.FreeOSMemory()
time.Sleep(5 * time.Hour)
t.Log("2 minutes")
}
}
func TestIsolatePool_Memory2(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
pool, err := js.NewIsolatePool(256)
if err != nil {
t.Fatal(err)
}
time.Sleep(4 * time.Second)
var gcPause = func() {
var before = time.Now()
runtime.GC()
var costSeconds = time.Since(before).Seconds()
var stats = &debug.GCStats{}
debug.ReadGCStats(stats)
t.Log("GC pause:", stats.PauseTotal.Seconds()*1000, "ms", "cost:", costSeconds*1000, "ms")
}
gcPause()
time.Sleep(10 * time.Second)
_ = pool
t.Log(pool.MaxSize())
}
func BenchmarkIsolatePool_GetContext(b *testing.B) {
pool, err := js.NewIsolatePool(32)
if err != nil {
b.Fatal(err)
}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
func() {
ctx, err := pool.GetContext()
if err != nil {
return
}
defer pool.PutContext(ctx)
// TODO 需要实现测试用例
/**err = ctx.AddGoObject(&FakeRequest{
host: "example.com",
remoteAddr: "127.0.0.1",
})
if err != nil {
b.Log(err)
return
}
_, err = ctx.Run("req.host() + ' @ ' + req.remoteAddr() + ' @ ' + req.proto() + ' @ ' + req.url()", "")
if err != nil {
b.Log(err)
return
}**/
}()
}
})
}

View File

@@ -0,0 +1,127 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
package js_test
import (
"github.com/TeaOSLab/EdgeNode/internal/js"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/types"
"sync"
"testing"
)
func TestIsolate_GetContext(t *testing.T) {
isolate, err := js.NewIsolate()
if err != nil {
t.Fatal(err)
}
ctx, err := isolate.GetContext()
if err != nil {
t.Fatal(err)
}
defer isolate.PutContext(ctx)
v, err := ctx.Run("1+1", "")
if err != nil {
t.Fatal(err)
}
t.Log(v)
}
func TestIsolate_GetContext2(t *testing.T) {
isolate, err := js.NewIsolate()
if err != nil {
t.Fatal(err)
}
var count = 100
if testutils.IsSingleTesting() {
count = 10000
}
var wg = sync.WaitGroup{}
wg.Add(count)
for i := 0; i < count; i++ {
go func() {
defer wg.Done()
ctx, err := isolate.GetContext()
if err != nil {
t.Log(err)
return
}
defer isolate.PutContext(ctx)
_, err = ctx.Run("1+1", "")
if err != nil {
t.Log(err)
}
}()
}
wg.Wait()
t.Log(isolate.ContextPool().Size())
}
func TestIsolate_GetContext_Request(t *testing.T) {
isolate, err := js.NewIsolate()
if err != nil {
t.Fatal(err)
}
ctx, err := isolate.GetContext()
if err != nil {
t.Fatal(err)
}
var reqObjectId = ctx.AddGoRequestObject(&FakeRequest{
host: "example.com",
remoteAddr: "127.0.0.1",
})
defer isolate.PutContext(ctx)
v, err := ctx.Run(`let req = new gojs.net.http.Request()
req.setGoObject(`+types.String(reqObjectId)+`)
req.host + ' @ ' + req.remoteAddr + ' @ ' + req.url`, "")
if err != nil {
t.Fatal(err)
}
t.Log(v)
}
func BenchmarkIsolate_GetContext(b *testing.B) {
isolate, err := js.NewIsolate()
if err != nil {
b.Fatal(err)
}
unboundScript, err := isolate.Compile("req.host() + ' @ ' + req.remoteAddr() + ' @ ' + req.proto() + ' @ ' + req.url()", "")
if err != nil {
b.Fatal(err)
}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
func() {
ctx, err := isolate.GetContext()
if err != nil {
return
}
defer isolate.PutContext(ctx)
// TODO 需要重新实现测试用例
/**err = ctx.BindRequest(&FakeRequest{
host: "example.com",
remoteAddr: "127.0.0.1",
})
if err != nil {
b.Log(err)
return
}**/
_, err = ctx.RunScript(unboundScript)
if err != nil {
b.Log(err)
return
}
}()
}
})
}

View File

@@ -0,0 +1,27 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
package js
type JSBaseLibrary struct {
}
func (this *JSBaseLibrary) JSInit(ctx *Context) {
}
func (this *JSBaseLibrary) JSDone(ctx *Context) {
}
func (this *JSBaseLibrary) JSDispose(ctx *Context) {
}
func (this *JSBaseLibrary) JSGoObjectNotFound() (any, error) {
return nil, ErrGoObjectNotFound
}
func (this *JSBaseLibrary) JSSuccess() (any, error) {
return nil, nil
}

View File

@@ -0,0 +1,64 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
import (
"encoding/base64"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
)
func init() {
if !teaconst.IsMain {
return
}
SharedLibraryManager.Register(&JSBase64Library{})
}
type JSBase64Library struct {
JSBaseLibrary
}
func (this *JSBase64Library) JSNamespace() string {
return "gojs.base64"
}
func (this *JSBase64Library) JSPrototype() string {
return `gojs.base64.encode = function (data) {
return $this.Encode(data)
}
gojs.base64.decode = function (data) {
return $this.Decode(data)
}
`
}
func (this *JSBase64Library) Encode(arguments *FunctionArguments) (string, error) {
s, ok := arguments.StringAt(0)
if ok {
return base64.StdEncoding.EncodeToString([]byte(s)), nil
}
b, ok := arguments.BytesAt(0)
if ok {
return base64.StdEncoding.EncodeToString(b), nil
}
return "", nil
}
func (this *JSBase64Library) Decode(arguments *FunctionArguments) (string, error) {
s, ok := arguments.StringAt(0)
if ok {
result, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return "", err
}
return string(result), nil
}
return "", nil
}

View File

@@ -0,0 +1,34 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
package js_test
import (
"github.com/TeaOSLab/EdgeNode/internal/js"
"testing"
)
func TestJSBase64Library_Encode(t *testing.T) {
isolate, err := js.NewIsolate()
if err != nil {
t.Fatal(err)
}
ctx, err := isolate.GetContext()
if err != nil {
t.Fatal(err)
}
defer ctx.Done()
t.Log(ctx.Run(`gojs.base64.encode('123456')`, "base64.js"))
t.Log(ctx.Run(`gojs.base64.decode(gojs.base64.encode('123456'))`, "base64.js"))
t.Log(ctx.Run(`let buf = new ArrayBuffer(6)
buf[0] = 49
buf[1] = 50
buf[2] = 51
buf[3] = 52
buf[4] = 53
buf[5] = 54
gojs.base64.encode(buf)
`, "base64.js"))
}

View File

@@ -0,0 +1,75 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
import teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
func init() {
if !teaconst.IsMain {
return
}
SharedLibraryManager.Register(&JSConsoleAssertLibrary{})
}
type JSConsoleAssertLibrary struct {
JSBaseLibrary
}
func (this *JSConsoleAssertLibrary) JSNamespace() string {
return "gojs.console.JSConsoleAssertLibrary"
}
func (this *JSConsoleAssertLibrary) JSPrototype() string {
return `console.assert = function () {
$this.Assert(arguments)
}`
}
func (this *JSConsoleAssertLibrary) Assert(arguments *FunctionArguments) (any, error) {
arg0, ok := arguments.ArgAt(0)
if !ok {
return nil, nil
}
if arg0.IsArgumentsObject() {
var obj = arg0.Object()
if obj == nil {
return nil, nil
}
assertion, err := obj.GetIdx(0)
if err != nil {
return nil, err
}
if assertion.IsBoolean() {
var b = assertion.Boolean()
if !b {
msgObj, err := obj.GetIdx(1)
if err != nil {
return nil, err
}
var msg = ""
// 支持 assert(assertion, msg)
// TODO 支持 assert(assertion, obj)
if msgObj.IsString() {
msg = msgObj.String()
}
msgValue, err := arguments.Context().NewValue("Assertion Failed: " + msg)
if err != nil {
return nil, err
}
arguments.Context().Isolate().ThrowException(msgValue)
}
}
}
return nil, nil
}

View File

@@ -0,0 +1,95 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/iwind/TeaGo/maps"
"strings"
"testing"
)
var SharedJSConsoleLogLibrary = NewJSConsoleLogLibrary()
func init() {
if !teaconst.IsMain {
return
}
SharedLibraryManager.Register(SharedJSConsoleLogLibrary)
}
type JSConsoleLogLibrary struct {
JSBaseLibrary
t *testing.T
}
func NewJSConsoleLogLibrary() *JSConsoleLogLibrary {
return &JSConsoleLogLibrary{}
}
func (this *JSConsoleLogLibrary) JSNamespace() string {
return "gojs.console.JSConsoleLogLibrary"
}
func (this *JSConsoleLogLibrary) JSPrototype() string {
return `console.log = function () {
$this.Log(arguments)
}`
}
func (this *JSConsoleLogLibrary) JSSetTesting(t *testing.T) {
this.t = t
}
func (this *JSConsoleLogLibrary) Log(arguments *FunctionArguments) (any, error) {
arg0, ok := arguments.ArgAt(0)
if !ok {
return nil, nil
}
var values = []string{}
if arg0.IsArgumentsObject() {
var obj = arg0.Object()
if obj != nil {
valueLength, err := obj.Get("length")
if err != nil {
return nil, err
}
var length = valueLength.Uint32()
for i := uint32(0); i < length; i++ {
arg, err := obj.GetIdx(i)
if err != nil {
return nil, err
}
if arg.IsObject() || arg.IsArray() {
objectJSON, err := arg.MarshalJSON()
if err != nil {
return nil, err
}
values = append(values, string(objectJSON))
} else {
values = append(values, arg.String())
}
}
}
}
if this.t != nil {
this.t.Log("[JS_CONSOLE_LOG]" + strings.Join(values, " "))
} else {
var serverId = arguments.Context().ServerId()
if serverId > 0 {
remotelogs.ServerLog(serverId, "SCRIPT", "[CONSOLE_LOG]"+strings.Join(values, " "), nodeconfigs.NodeLogTypeScriptConsoleLog, maps.Map{})
} else {
remotelogs.Println("SCRIPT", "[CONSOLE_LOG]"+strings.Join(values, " "))
}
}
return nil, nil
}

View File

@@ -0,0 +1,36 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js_test
import (
"github.com/TeaOSLab/EdgeNode/internal/js"
"testing"
"time"
)
func TestContext_ConsoleLog(t *testing.T) {
isolate, err := js.NewIsolate()
if err != nil {
t.Fatal(err)
}
defer isolate.Dispose()
ctx, err := isolate.GetContext()
if err != nil {
t.Fatal(err)
}
var before = time.Now()
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
v, err := ctx.Run(`
console.log("Hello, World", 1, 1.2, false, null, {"a":"b"}, [1, 2, 3, 4])
`, "")
if err != nil {
t.Fatal(err)
}
t.Log(v)
}

View File

@@ -0,0 +1,68 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"errors"
"fmt"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
)
func init() {
if !teaconst.IsMain {
return
}
SharedLibraryManager.Register(&JSCryptoLibrary{})
}
type JSCryptoLibrary struct {
JSBaseLibrary
}
func (this *JSCryptoLibrary) JSNamespace() string {
return "gojs.JSCryptoLibrary"
}
func (this *JSCryptoLibrary) JSPrototype() string {
return `gojs.md5 = function (s) {
return $this.Md5(s)
}
gojs.sha1 = function (s) {
return $this.Sha1(s)
}
gojs.sha256 = function (s) {
return $this.Sha256(s)
}`
}
func (this *JSCryptoLibrary) Md5(arguments *FunctionArguments) (string, error) {
s, ok := arguments.StringAt(0)
if !ok {
return "", errors.New("invalid argument")
}
return fmt.Sprintf("%x", md5.Sum([]byte(s))), nil
}
func (this *JSCryptoLibrary) Sha1(arguments *FunctionArguments) (string, error) {
s, ok := arguments.StringAt(0)
if !ok {
return "", errors.New("invalid argument")
}
return fmt.Sprintf("%x", sha1.Sum([]byte(s))), nil
}
func (this *JSCryptoLibrary) Sha256(arguments *FunctionArguments) (string, error) {
s, ok := arguments.StringAt(0)
if !ok {
return "", errors.New("invalid argument")
}
return fmt.Sprintf("%x", sha256.Sum256([]byte(s))), nil
}

View File

@@ -0,0 +1,115 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
import (
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"errors"
"fmt"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"hash"
)
func init() {
if !teaconst.IsMain {
return
}
SharedLibraryManager.Register(&JSHMACCryptoLibrary{})
}
type JSHMACCryptoLibrary struct {
JSBaseLibrary
}
func (this *JSHMACCryptoLibrary) JSNamespace() string {
return "gojs.crypto.JSHMACLibrary"
}
func (this *JSHMACCryptoLibrary) JSPrototype() string {
return `gojs.crypto.HMAC = class {
#goObjectId
constructor(algorithm, key) {
this.#goObjectId = $this.NewHMAC(algorithm, key)
}
update(data) {
$this.Update(this.#goObjectId, data)
}
sum() {
return $this.Sum(this.#goObjectId)
}
}`
}
func (this *JSHMACCryptoLibrary) NewHMAC(arguments *FunctionArguments) (any, error) {
algorithm, ok := arguments.StringAt(0)
if !ok {
return nil, errors.New("require hash algorithm")
}
key, ok := arguments.StringAt(1)
if !ok {
return nil, errors.New("require hash key")
}
var h hash.Hash
switch algorithm {
case "md5":
h = hmac.New(md5.New, []byte(key))
case "sha1":
h = hmac.New(sha1.New, []byte(key))
case "sha256":
h = hmac.New(sha256.New, []byte(key))
case "sha512":
h = hmac.New(sha512.New, []byte(key))
default:
return nil, errors.New("invalid hash algorithm")
}
return arguments.Context().AddGoObject(h), nil
}
func (this *JSHMACCryptoLibrary) Update(arguments *FunctionArguments) (any, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return nil, ErrGoObjectNotFound
}
data, ok := arguments.FormatStringAt(1)
if !ok {
return nil, errors.New("invalid data")
}
h, ok := obj.(hash.Hash)
if !ok {
return nil, errors.New("invalid hash")
}
h.Reset()
h.Write([]byte(data))
return nil, nil
}
func (this *JSHMACCryptoLibrary) Sum(arguments *FunctionArguments) (any, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return nil, ErrGoObjectNotFound
}
h, ok := obj.(hash.Hash)
if !ok {
return nil, errors.New("invalid hash")
}
return fmt.Sprintf("%x", h.Sum(nil)), nil
}

View File

@@ -0,0 +1,33 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js_test
import (
"github.com/TeaOSLab/EdgeNode/internal/js"
"testing"
)
func TestJSHMACCryptoLibrary_NewHMAC(t *testing.T) {
isolate, err := js.NewIsolate()
if err != nil {
t.Fatal(err)
}
ctx, err := isolate.GetContext()
if err != nil {
t.Fatal(err)
}
defer ctx.Done()
v, err := ctx.Run(`
let h = new gojs.crypto.HMAC("sha1", "")
h.update("123456")
h.sum()
`, "hmac.js")
if err != nil {
t.Fatal(err)
}
t.Log(v)
}

View File

@@ -0,0 +1,48 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js_test
import (
"github.com/TeaOSLab/EdgeNode/internal/js"
"github.com/iwind/TeaGo/types"
"testing"
)
func TestCrypto(t *testing.T) {
isolate, err := js.NewIsolate()
if err != nil {
t.Fatal(err)
}
ctx, err := isolate.GetContext()
if err != nil {
t.Fatal(err)
}
defer ctx.Done()
for _, s := range []string{"", "123456", "abc"} {
{
v, err := ctx.Run("gojs.md5('"+s+"')", "test.js")
if err != nil {
t.Fatal(err)
}
t.Log("md5("+s+"): "+v.String(), "["+types.String(len(v.String()))+"]")
}
{
v, err := ctx.Run("gojs.sha1('"+s+"')", "test.js")
if err != nil {
t.Fatal(err)
}
t.Log("sha1("+s+"): "+v.String(), "["+types.String(len(v.String()))+"]")
}
{
v, err := ctx.Run("gojs.sha256('"+s+"')", "test.js")
if err != nil {
t.Fatal(err)
}
t.Log("sha256("+s+"): "+v.String(), "["+types.String(len(v.String()))+"]")
}
}
}

View File

@@ -0,0 +1,348 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
package js
import (
"bytes"
"errors"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/maps"
"io"
"net/http"
"net/url"
"strings"
"time"
)
var sharedHTTPClientPool = utils.NewHTTPClient(30 * time.Second)
func init() {
if !teaconst.IsMain {
return
}
SharedLibraryManager.Register(&JSNetHTTPClientLibrary{})
}
type JSNetHTTPClientLibrary struct {
JSBaseLibrary
}
func (this *JSNetHTTPClientLibrary) JSNamespace() string {
return "gojs.net.http.client.JSNetHTTPClientLibrary"
}
func (this *JSNetHTTPClientLibrary) JSPrototype() string {
return `
gojs.net.http.client.Request = class {
#url
#headers = {}
#method
#body
constructor(url) {
this.#url = url
}
get url() {
return this.#url
}
addHeader(name, value) {
if (typeof(name) == "string" && typeof(value) == "string") {
if (typeof(this.#headers[name]) == "undefined") {
this.#headers[name] = [value]
} else {
this.#headers[name].push(value)
}
}
}
get headers() {
return this.#headers
}
setContentType(contentType) {
this.#headers["Content-Type"] = [contentType]
}
setUserAgent(userAgent) {
this.#headers["User-Agent"] = [userAgent]
}
setMethod(method) {
this.#method = method
}
get method() {
return this.#method
}
setBody(body) {
this.#body = body
}
get body() {
return this.#body
}
get() {
this.setMethod("GET")
return gojs.net.http.client.DefaultClient.do(this)
}
post() {
this.setMethod("POST")
return gojs.net.http.client.DefaultClient.do(this)
}
}
gojs.net.http.client.Response = class {
#goObjectId
constructor(goObjectId) {
this.#goObjectId = goObjectId
}
get goObjectId() {
return this.#goObjectId
}
get error() {
return $this.ResponseError(this.#goObjectId)
}
get contentLength() {
return $this.ResponseContentLength(this.#goObjectId)
}
get status() {
return $this.ResponseStatus(this.#goObjectId)
}
get headers() {
return $this.ResponseHeaders(this.#goObjectId)
}
get body() {
return $this.ResponseBodyString(this.#goObjectId)
}
get bodyObject() {
return JSON.parse(this.body)
}
}
gojs.net.http.client.Client = class {
do(req) {
let respObjectId = $this.Do({
"url": req.url,
"method": req.method,
"headers": req.headers,
"headerKeys": Object.keys(req.headers),
"body": req.body,
})
return new gojs.net.http.client.Response(respObjectId)
}
}
gojs.net.http.client.DefaultClient = new gojs.net.http.client.Client()
`
}
func (this *JSNetHTTPClientLibrary) JSDone(ctx *Context) {
// TODO 因为每个请求会执行,所以这里需要性能,可能需要有调用的时候才执行
var objMap = ctx.GoObjectMap()
for _, obj := range objMap {
if obj != nil {
resp, ok := obj.(*JSNetHTTPClientLibraryResponse)
if ok {
resp.Close()
}
}
}
}
func (this *JSNetHTTPClientLibrary) Do(arguments *FunctionArguments) (uint32, error) {
obj, ok := arguments.ObjectAt(0)
if !ok {
return 0, ErrGoObjectNotFound
}
if obj.IsNullOrUndefined() {
return 0, ErrGoObjectNotFound
}
urlString, _ := obj.GetString("url")
_, err := url.Parse(urlString)
if err != nil {
return 0, errors.New("invalid url: '" + urlString + "'")
}
method, _ := obj.GetString("method")
if len(method) == 0 {
method = http.MethodGet
}
method = strings.ToUpper(method)
var headers = http.Header{}
headerObj, ok := obj.GetObject("headers")
if ok && !headerObj.IsNullOrUndefined() {
headerKeysArray, _ := obj.GetStringsArray("headerKeys")
for _, headerKey := range headerKeysArray {
headerValues, _ := headerObj.GetStringsArray(headerKey)
if len(headerValues) > 0 {
headers[headerKey] = headerValues
}
}
}
var bodyReader io.Reader
bodyString, _ := obj.GetString("body")
if len(bodyString) > 0 {
bodyReader = bytes.NewReader([]byte(bodyString))
}
req, err := http.NewRequest(method, urlString, bodyReader)
if err != nil {
var goObjectId = arguments.Context().AddGoObject(NewJSNetHTTPClientLibraryResponse(nil, err))
return goObjectId, nil
}
var hasContentType = false
var hasUserAgent = false
req.Header = headers
for k := range headers {
if strings.ToLower(k) == "content-type" {
hasContentType = true
}
if strings.ToLower(k) == "user-agent" {
hasUserAgent = true
}
}
if !hasContentType && method == http.MethodPost {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
if !hasUserAgent {
req.Header.Set("User-Agent", teaconst.GlobalProductName+"-Node-Script/"+teaconst.Version)
}
resp, err := sharedHTTPClientPool.Do(req)
if err != nil {
var goObjectId = arguments.Context().AddGoObject(NewJSNetHTTPClientLibraryResponse(nil, err))
return goObjectId, nil
}
var goObjectId = arguments.Context().AddGoObject(NewJSNetHTTPClientLibraryResponse(resp, err))
return goObjectId, nil
}
func (this *JSNetHTTPClientLibrary) ResponseError(arguments *FunctionArguments) (any, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return "", ErrGoObjectNotFound
}
var err = obj.(*JSNetHTTPClientLibraryResponse).Error()
if err != nil {
return maps.Map{"message": err.Error()}, nil
}
return nil, nil
}
func (this *JSNetHTTPClientLibrary) ResponseStatus(arguments *FunctionArguments) (int, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return 0, ErrGoObjectNotFound
}
return obj.(*JSNetHTTPClientLibraryResponse).Status(), nil
}
func (this *JSNetHTTPClientLibrary) ResponseContentLength(arguments *FunctionArguments) (int64, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return 0, ErrGoObjectNotFound
}
return obj.(*JSNetHTTPClientLibraryResponse).ContentLength(), nil
}
func (this *JSNetHTTPClientLibrary) ResponseHeaders(arguments *FunctionArguments) (http.Header, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return nil, ErrGoObjectNotFound
}
return obj.(*JSNetHTTPClientLibraryResponse).Headers(), nil
}
func (this *JSNetHTTPClientLibrary) ResponseBodyString(arguments *FunctionArguments) (string, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return "", ErrGoObjectNotFound
}
s, _ := obj.(*JSNetHTTPClientLibraryResponse).BodyString()
return s, nil
}
type JSNetHTTPClientLibraryResponse struct {
resp *http.Response
err error
body []byte
bodyRead bool
}
func NewJSNetHTTPClientLibraryResponse(resp *http.Response, err error) *JSNetHTTPClientLibraryResponse {
return &JSNetHTTPClientLibraryResponse{resp: resp, err: err}
}
func (this *JSNetHTTPClientLibraryResponse) Close() {
if this.resp != nil && this.resp.Body != nil {
_ = this.resp.Body.Close()
}
}
func (this *JSNetHTTPClientLibraryResponse) Error() error {
return this.err
}
func (this *JSNetHTTPClientLibraryResponse) Status() int {
if this.resp != nil {
return this.resp.StatusCode
}
return 0
}
func (this *JSNetHTTPClientLibraryResponse) ContentLength() int64 {
if this.resp != nil {
return this.resp.ContentLength
}
return 0
}
func (this *JSNetHTTPClientLibraryResponse) Headers() http.Header {
if this.resp != nil {
return this.resp.Header
}
return http.Header{}
}
func (this *JSNetHTTPClientLibraryResponse) BodyString() (string, error) {
if this.resp != nil {
data, err := this.readBody()
if err != nil {
return "", err
}
return string(data), nil
}
return "", nil
}
func (this *JSNetHTTPClientLibraryResponse) readBody() ([]byte, error) {
if this.bodyRead {
return this.body, nil
}
data, err := io.ReadAll(this.resp.Body)
this.body = data
this.bodyRead = true
return data, err
}

View File

@@ -0,0 +1,74 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js_test
import (
"github.com/TeaOSLab/EdgeNode/internal/js"
"testing"
)
func TestJSNetHTTPClientLibrary(t *testing.T) {
isolate, err := js.NewIsolate()
if err != nil {
t.Fatal(err)
}
ctx, err := isolate.GetContext()
if err != nil {
t.Fatal(err)
}
defer ctx.Done()
js.SharedJSConsoleLogLibrary.JSSetTesting(t)
_, err = ctx.Run(`
let clientReq = new gojs.net.http.client.Request("https://goedge.cn/api/boot/versions?os=linux&arch=amd64&pretty=true")
clientReq.setUserAgent("gojs/1.0")
let clientResp = clientReq.get()
if (clientResp.error != null) {
console.log("error:", clientResp.error)
} else {
console.log("status:", clientResp.status, "contentLength:", clientResp.contentLength, "headers:", clientResp.headers, "toString:", clientResp.body, "object:", clientResp.bodyObject.code)
}
`, "client.js")
if err != nil {
t.Fatal(err)
}
}
func TestJSNetHTTPClientLibrary_Post(t *testing.T) {
isolate, err := js.NewIsolate()
if err != nil {
t.Fatal(err)
}
ctx, err := isolate.GetContext()
if err != nil {
t.Fatal(err)
}
defer ctx.Done()
js.SharedJSConsoleLogLibrary.JSSetTesting(t)
_, err = ctx.Run(`
let clientReq = new gojs.net.http.client.Request("https://127.0.0.1/post")
//clientReq.setUserAgent("gojs/1.0")
clientReq.setContentType("application/json")
clientReq.setBody('{"hello":"world"}')
let clientResp = clientReq.post()
//let client = new gojs.net.http.client.Client()
//clientResp = client.do(clientReq)
if (clientResp.error != null) {
console.log("error:", clientResp.error)
} else {
console.log("status:", clientResp.status, "contentLength:", clientResp.contentLength, "headers:", clientResp.headers, "toString:", clientResp.body)
}
`, "client.js")
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,437 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
import (
"errors"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/iwind/TeaGo/maps"
"net/http"
)
func init() {
if !teaconst.IsMain {
return
}
SharedLibraryManager.Register(&JSNetHTTPRequestLibrary{})
}
type JSNetHTTPRequestLibrary struct {
JSBaseLibrary
}
func (this *JSNetHTTPRequestLibrary) JSNamespace() string {
return "gojs.net.http.JSNetHTTPRequestLibrary"
}
func (this *JSNetHTTPRequestLibrary) JSPrototype() string {
return `gojs.net.http.Request = class {
#goObjectId
constructor() {
}
setGoObject(objectId) {
this.#goObjectId = objectId
}
goObjectId() {
return this.#goObjectId
}
get id() {
return $this.Id(this.#goObjectId)
}
get requestId() {
return this.id
}
get url() {
return $this.URL(this.#goObjectId)
}
get server() {
return $this.Server(this.#goObjectId)
}
get node() {
return $this.Node(this.#goObjectId)
}
get requestURL() {
return this.url
}
get path() {
return $this.Path(this.#goObjectId)
}
get requestPath() {
return this.path
}
get uri() {
return $this.URI(this.#goObjectId)
}
get requestURI() {
return this.uri
}
set uri(uri) {
$this.SetURI(this.#goObjectId, uri)
}
set requestURI(uri) {
this.uri = uri
}
get host() {
return $this.Host(this.#goObjectId)
}
get remoteAddr() {
return $this.RemoteAddr(this.#goObjectId)
}
get rawRemoteAddr() {
return $this.RawRemoteAddr(this.#goObjectId)
}
get remotePort() {
return $this.RemotePort(this.#goObjectId)
}
get method() {
return $this.Method(this.#goObjectId)
}
get contentLength() {
return $this.ContentLength(this.#goObjectId)
}
get transferEncoding() {
return $this.TransferEncoding(this.#goObjectId)
}
get proto() {
return $this.Proto(this.#goObjectId)
}
get protoMajor() {
return $this.ProtoMajor(this.#goObjectId)
}
get protoMinor() {
return $this.ProtoMinor(this.#goObjectId)
}
cookie(name) {
return $this.Cookie(this.#goObjectId, name)
}
get header() {
let header = $this.Header(this.#goObjectId)
if (header == null) {
header = {}
}
return header
}
setHeader(name, values) {
$this.SetHeader(this.#goObjectId, name, values)
}
deleteHeader(name) {
$this.DeleteHeader(this.#goObjectId, name)
}
setAttr(name, value) {
$this.SetAttr(this.#goObjectId, name, value)
}
setVar(name, value) {
$this.SetHeader(this.#goObjectId, name, value)
}
format(s) {
return $this.Format(this.#goObjectId, s)
}
done() {
$this.Done(this.#goObjectId)
}
close() {
$this.Close(this.#goObjectId)
}
allow() {
$this.Allow(this.#goObjectId)
}
}`
}
func (this *JSNetHTTPRequestLibrary) Id(arguments *FunctionArguments) (string, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return "", ErrGoObjectNotFound
}
return obj.(RequestInterface).Id(), nil
}
func (this *JSNetHTTPRequestLibrary) Server(arguments *FunctionArguments) (maps.Map, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return maps.Map{}, ErrGoObjectNotFound
}
return obj.(RequestInterface).Server(), nil
}
func (this *JSNetHTTPRequestLibrary) Node(arguments *FunctionArguments) (maps.Map, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return maps.Map{}, ErrGoObjectNotFound
}
return obj.(RequestInterface).Node(), nil
}
func (this *JSNetHTTPRequestLibrary) URL(arguments *FunctionArguments) (string, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return "", ErrGoObjectNotFound
}
return obj.(RequestInterface).URL(), nil
}
func (this *JSNetHTTPRequestLibrary) Path(arguments *FunctionArguments) (string, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return "", ErrGoObjectNotFound
}
return obj.(RequestInterface).Path(), nil
}
func (this *JSNetHTTPRequestLibrary) URI(arguments *FunctionArguments) (string, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return "", ErrGoObjectNotFound
}
return obj.(RequestInterface).URI(), nil
}
func (this *JSNetHTTPRequestLibrary) SetURI(arguments *FunctionArguments) (any, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return "", ErrGoObjectNotFound
}
var uri, _ = arguments.FormatStringAt(1)
obj.(RequestInterface).SetURI(uri)
return this.JSSuccess()
}
func (this *JSNetHTTPRequestLibrary) Host(arguments *FunctionArguments) (string, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return "", ErrGoObjectNotFound
}
return obj.(RequestInterface).Host(), nil
}
func (this *JSNetHTTPRequestLibrary) RemoteAddr(arguments *FunctionArguments) (string, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return "", ErrGoObjectNotFound
}
return obj.(RequestInterface).RemoteAddr(), nil
}
func (this *JSNetHTTPRequestLibrary) RawRemoteAddr(arguments *FunctionArguments) (string, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return "", ErrGoObjectNotFound
}
return obj.(RequestInterface).RawRemoteAddr(), nil
}
func (this *JSNetHTTPRequestLibrary) RemotePort(arguments *FunctionArguments) (int, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return 0, ErrGoObjectNotFound
}
return obj.(RequestInterface).RemotePort(), nil
}
func (this *JSNetHTTPRequestLibrary) Method(arguments *FunctionArguments) (string, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return "", ErrGoObjectNotFound
}
return obj.(RequestInterface).Method(), nil
}
func (this *JSNetHTTPRequestLibrary) ContentLength(arguments *FunctionArguments) (int64, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return 0, ErrGoObjectNotFound
}
return obj.(RequestInterface).ContentLength(), nil
}
func (this *JSNetHTTPRequestLibrary) TransferEncoding(arguments *FunctionArguments) (string, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return "", ErrGoObjectNotFound
}
return obj.(RequestInterface).TransferEncoding(), nil
}
func (this *JSNetHTTPRequestLibrary) Proto(arguments *FunctionArguments) (string, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return "", ErrGoObjectNotFound
}
return obj.(RequestInterface).Proto(), nil
}
func (this *JSNetHTTPRequestLibrary) ProtoMajor(arguments *FunctionArguments) (int, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return 0, ErrGoObjectNotFound
}
return obj.(RequestInterface).ProtoMajor(), nil
}
func (this *JSNetHTTPRequestLibrary) ProtoMinor(arguments *FunctionArguments) (int, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return 0, ErrGoObjectNotFound
}
return obj.(RequestInterface).ProtoMinor(), nil
}
func (this *JSNetHTTPRequestLibrary) Cookie(arguments *FunctionArguments) (string, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return "", ErrGoObjectNotFound
}
var name, _ = arguments.FormatStringAt(1)
if len(name) == 0 {
return "", nil
}
return obj.(RequestInterface).Cookie(name), nil
}
func (this *JSNetHTTPRequestLibrary) Header(arguments *FunctionArguments) (http.Header, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return http.Header{}, ErrGoObjectNotFound
}
return obj.(RequestInterface).Header(), nil
}
func (this *JSNetHTTPRequestLibrary) SetHeader(arguments *FunctionArguments) (any, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return nil, ErrGoObjectNotFound
}
argName, ok := arguments.FormatStringAt(1)
if !ok || len(argName) == 0 {
return nil, errors.New("header name should not be empty")
}
var name = argName
argValues, ok := arguments.FormatStringsAt(2)
if !ok {
argValueString, ok := arguments.FormatStringAt(2)
if !ok {
obj.(RequestInterface).SetHeader(name, []string{""})
return this.JSSuccess()
}
obj.(RequestInterface).SetHeader(name, []string{argValueString})
return this.JSSuccess()
}
obj.(RequestInterface).SetHeader(name, argValues)
return this.JSSuccess()
}
func (this *JSNetHTTPRequestLibrary) DeleteHeader(arguments *FunctionArguments) (any, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return nil, ErrGoObjectNotFound
}
name, ok := arguments.FormatStringAt(1)
if !ok {
return this.JSSuccess()
}
obj.(RequestInterface).DeleteHeader(name)
return this.JSSuccess()
}
func (this *JSNetHTTPRequestLibrary) SetAttr(arguments *FunctionArguments) (any, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return nil, ErrGoObjectNotFound
}
argName, ok := arguments.FormatStringAt(1)
if !ok || len(argName) == 0 {
return nil, errors.New("header name should not be empty")
}
var name = argName
value, _ := arguments.FormatStringAt(2)
obj.(RequestInterface).SetAttr(name, value)
return this.JSSuccess()
}
func (this *JSNetHTTPRequestLibrary) SetVar(arguments *FunctionArguments) (any, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return nil, ErrGoObjectNotFound
}
argName, ok := arguments.FormatStringAt(1)
if !ok || len(argName) == 0 {
return nil, errors.New("header name should not be empty")
}
var name = argName
value, _ := arguments.FormatStringAt(2)
obj.(RequestInterface).SetVar(name, value)
return this.JSSuccess()
}
func (this *JSNetHTTPRequestLibrary) Format(arguments *FunctionArguments) (string, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return "", ErrGoObjectNotFound
}
s, _ := arguments.FormatStringAt(1)
return obj.(RequestInterface).Format(s), nil
}
func (this *JSNetHTTPRequestLibrary) Done(arguments *FunctionArguments) (any, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return nil, ErrGoObjectNotFound
}
obj.(RequestInterface).Done()
return this.JSSuccess()
}
func (this *JSNetHTTPRequestLibrary) Close(arguments *FunctionArguments) (any, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return nil, ErrGoObjectNotFound
}
obj.(RequestInterface).Close()
return this.JSSuccess()
}
func (this *JSNetHTTPRequestLibrary) Allow(arguments *FunctionArguments) (any, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return nil, ErrGoObjectNotFound
}
obj.(RequestInterface).Allow()
return this.JSSuccess()
}

View File

@@ -0,0 +1,107 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js_test
import (
"github.com/TeaOSLab/EdgeNode/internal/js"
"github.com/iwind/TeaGo/types"
"testing"
"time"
)
func TestJSNetHTTPRequestLibrary(t *testing.T) {
isolate, err := js.NewIsolateWithContexts(1)
if err != nil {
t.Fatal(err)
}
defer isolate.Dispose()
ctx, err := isolate.GetContext()
if err != nil {
t.Fatal(err)
}
var before = time.Now()
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
js.SharedJSConsoleLogLibrary.JSSetTesting(t)
var req = &FakeRequest{}
req.host = "example.com"
req.remoteAddr = "192.168.1.100"
var objId = ctx.AddGoObject(req)
defer isolate.PutContext(ctx)
v, err := ctx.Run(`
let req = new gojs.net.http.Request()
req.setGoObject(`+types.String(objId)+`)
// user codes
console.log("url:", req.url)
console.log("host:", req.host)
console.log("remoteAddr:", req.remoteAddr)
console.log("rawRemoteAddr:", req.rawRemoteAddr)
console.log("remotePort:", req.remotePort)
console.log("method:", req.method)
console.log("contentLength:", req.contentLength)
console.log("transferEncoding:", req.transferEncoding)
console.log("proto", req.proto)
console.log("protoMajor:", req.protoMajor)
console.log("protoMinor", req.protoMinor)
req.setHeader("Server", "goedge/1.0")
req.setHeader("Hello", "World")
req.setHeader("Hello-Deleted", "World")
req.setHeader("Content-Length", [1024])
req.setHeader("Set-Cookie", ["a=1", "b=2"])
req.deleteHeader("Hello-Deleted")
console.log("header", req.header)
req.setAttr("a", "b")
req.setVar("a", "b")
req.done()
`, "req.js")
if err != nil {
t.Fatal(ctx.WrapJSErr(err))
}
_ = v
}
func BenchmarkHTTPRequestLibrary(b *testing.B) {
isolate, err := js.NewIsolateWithContexts(128)
if err != nil {
b.Fatal(err)
}
defer isolate.Dispose()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
func() {
ctx, err := isolate.GetContext()
if err != nil {
b.Fatal(err)
}
defer isolate.PutContext(ctx)
var req = &FakeRequest{}
req.host = "example.com"
var objId = ctx.AddGoObject(req)
_, err = ctx.Run(`{
let req = new gojs.net.http.Request()
req.setGoObject(`+types.String(objId)+`)
// user codes
req.host
}`, "")
if err != nil {
b.Fatal(err)
}
}()
}
})
}

View File

@@ -0,0 +1,203 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
import (
"errors"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"net/http"
)
func init() {
if !teaconst.IsMain {
return
}
SharedLibraryManager.Register(&JSNetHTTPResponseLibrary{})
}
type JSNetHTTPResponseLibrary struct {
JSBaseLibrary
}
func (this *JSNetHTTPResponseLibrary) JSNamespace() string {
return "gojs.net.http.JSNetHTTPResponseLibrary"
}
func (this *JSNetHTTPResponseLibrary) JSPrototype() string {
return `gojs.net.http.Response = class {
#goObjectId
constructor() {
}
setGoObject(objectId) {
this.#goObjectId = objectId
}
goObjectId() {
return this.#goObjectId
}
setHeader(name, values) {
$this.SetHeader(this.#goObjectId, name, values)
}
deleteHeader(name) {
$this.DeleteHeader(this.#goObjectId, name)
}
get header() {
let header = $this.Header(this.#goObjectId)
if (header == null) {
header = {}
}
return header
}
send(status, body) {
if (body != null && typeof(body) == "object" && (body instanceof gojs.net.http.client.Response)) {
$this.SendResp(this.#goObjectId, status, body.goObjectId)
return
}
$this.Send(this.#goObjectId, status, body)
}
sendFile(status, path) {
$this.SendFile(this.#goObjectId, status, path)
}
redirect(status, url) {
$this.Redirect(this.#goObjectId, status, url)
}
}`
}
func (this *JSNetHTTPResponseLibrary) Header(arguments *FunctionArguments) (http.Header, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return http.Header{}, ErrGoObjectNotFound
}
return obj.(ResponseInterface).Header(), nil
}
func (this *JSNetHTTPResponseLibrary) SetHeader(arguments *FunctionArguments) (any, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return nil, ErrGoObjectNotFound
}
argName, ok := arguments.FormatStringAt(1)
if !ok || len(argName) == 0 {
return nil, errors.New("header name should not be empty")
}
var name = argName
argValues, ok := arguments.FormatStringsAt(2)
if !ok {
argValueString, ok := arguments.FormatStringAt(2)
if !ok {
obj.(ResponseInterface).SetHeader(name, []string{""})
return this.JSSuccess()
}
obj.(ResponseInterface).SetHeader(name, []string{argValueString})
return this.JSSuccess()
}
obj.(ResponseInterface).SetHeader(name, argValues)
return this.JSSuccess()
}
func (this *JSNetHTTPResponseLibrary) DeleteHeader(arguments *FunctionArguments) (any, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return nil, ErrGoObjectNotFound
}
name, ok := arguments.FormatStringAt(1)
if !ok {
return this.JSSuccess()
}
obj.(ResponseInterface).DeleteHeader(name)
return this.JSSuccess()
}
func (this *JSNetHTTPResponseLibrary) Send(arguments *FunctionArguments) (any, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return nil, ErrGoObjectNotFound
}
status, ok := arguments.IntAt(1)
if !ok {
status = 200
}
body, _ := arguments.FormatStringAt(2)
obj.(ResponseInterface).Send(status, body)
return this.JSSuccess()
}
// SendResp 发送从 net.http.client.Client 获取的响应数据
func (this *JSNetHTTPResponseLibrary) SendResp(arguments *FunctionArguments) (int64, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return 0, ErrGoObjectNotFound
}
status, ok := arguments.IntAt(1)
if !ok {
status = 200
}
respObj, ok := arguments.GoObjectAt(2)
if !ok {
return 0, ErrGoObjectNotFound
}
var resp = respObj.(*JSNetHTTPClientLibraryResponse).resp
resp.StatusCode = status
return obj.(ResponseInterface).SendResp(resp)
}
func (this *JSNetHTTPResponseLibrary) SendFile(arguments *FunctionArguments) (written int64, err error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return 0, ErrGoObjectNotFound
}
status, ok := arguments.IntAt(1)
if !ok {
status = 200
}
path, _ := arguments.FormatStringAt(2)
return obj.(ResponseInterface).SendFile(status, path)
}
func (this *JSNetHTTPResponseLibrary) Redirect(arguments *FunctionArguments) (any, error) {
obj, ok := arguments.GoObjectAt(0)
if !ok {
return 0, ErrGoObjectNotFound
}
status, ok := arguments.IntAt(1)
if !ok {
status = http.StatusFound
}
url, ok := arguments.StringAt(2)
if !ok || len(url) == 0 {
return nil, errors.New("redirect() function require 'url' parameter")
}
obj.(ResponseInterface).Redirect(status, url)
return this.JSSuccess()
}

View File

@@ -0,0 +1,56 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js_test
import (
"github.com/TeaOSLab/EdgeNode/internal/js"
"github.com/iwind/TeaGo/types"
"testing"
"time"
)
func TestJSNetHTTPResponseLibrary(t *testing.T) {
isolate, err := js.NewIsolateWithContexts(1)
if err != nil {
t.Fatal(err)
}
defer isolate.Dispose()
ctx, err := isolate.GetContext()
if err != nil {
t.Fatal(err)
}
var before = time.Now()
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
js.SharedJSConsoleLogLibrary.JSSetTesting(t)
var req = &FakeResponse{}
var objId = ctx.AddGoObject(req)
defer isolate.PutContext(ctx)
v, err := ctx.Run(`
let resp = new gojs.net.http.Response()
resp.setGoObject(`+types.String(objId)+`)
// user codes
resp.setHeader("Server", "goedge/1.0")
resp.setHeader("Hello", "World")
resp.setHeader("Hello-Deleted", "World")
resp.setHeader("Content-Length", [1024])
resp.setHeader("Set-Cookie", ["a=1", "b=2"])
resp.deleteHeader("Hello-Deleted")
console.log("header", resp.header)
resp.send(200, "Hello, From Javascript")
`, "req.js")
if err != nil {
t.Fatal(ctx.WrapJSErr(err))
}
_ = v
}

View File

@@ -0,0 +1,76 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"net/url"
)
func init() {
if !teaconst.IsMain {
return
}
SharedLibraryManager.Register(&JSURLLibrary{})
}
type JSURLLibrary struct {
JSBaseLibrary
}
func (this *JSURLLibrary) JSNamespace() string {
return "gojs.net.JSURLLibrary"
}
func (this *JSURLLibrary) JSPrototype() string {
return `gojs.net.URL = class {
#urlString
constructor(urlString) {
this.urlString = urlString
let url = $this.Parse(urlString)
gojs.copyAttrs(this, url)
}
toString () {
return this.urlString
}
}`
}
func (this *JSURLLibrary) Parse(arguments *FunctionArguments) (maps.Map, error) {
var urlString = ""
arg0, ok := arguments.ArgAt(0)
if ok {
urlString = arg0.String()
}
u, err := url.Parse(urlString)
if err != nil {
return maps.Map{}, err
}
var user = map[string]string{}
if u.User != nil {
password, _ := u.User.Password()
user = map[string]string{
"username": u.User.Username(),
"password": password,
}
}
return maps.Map{
"host": u.Host,
"query": u.RawQuery,
"port": types.Int(u.Port()),
"path": u.Path,
"hash": u.Fragment,
"scheme": u.Scheme,
"opaque": u.Opaque,
"user": user,
}, nil
}

View File

@@ -0,0 +1,38 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js_test
import (
"github.com/TeaOSLab/EdgeNode/internal/js"
"testing"
"time"
)
func TestJSURLLibrary_Parse(t *testing.T) {
isolate, err := js.NewIsolate()
if err != nil {
t.Fatal(err)
}
defer isolate.Dispose()
ctx, err := isolate.GetContext()
if err != nil {
t.Fatal(err)
}
var before = time.Now()
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
v, err := ctx.Run(`
let url = new gojs.net.URL("https://user:pass@goedge.cn:8080/docs?nav=1#link")
JSON.stringify(url)
//url.toString()
`, "")
if err != nil {
t.Fatal(err)
}
t.Log(v)
}

View File

@@ -0,0 +1,255 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
import (
"context"
"errors"
"fmt"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/go-redis/redis/v8"
_ "github.com/go-redis/redis/v8"
"github.com/iwind/TeaGo/types"
"time"
)
func init() {
if !teaconst.IsMain {
return
}
SharedLibraryManager.Register(&JSRedisLibrary{})
}
type JSRedisLibrary struct {
JSBaseLibrary
}
func (this *JSRedisLibrary) JSNamespace() string {
return "gojs.redis"
}
func (this *JSRedisLibrary) JSPrototype() string {
return `gojs.Redis = class {
#goObjectId
constructor(options) {
this.#goObjectId = $this.Connect(options)
}
set(key, value, ttl) {
$this.Set(this.#goObjectId, key, value, ttl)
}
incr(key) {
return $this.Incr(this.#goObjectId, key)
}
incrBy(key, increment) {
return $this.IncrBy(this.#goObjectId, key, increment)
}
incrByFloat(key, increment) {
return $this.IncrByFloat(this.#goObjectId, key, increment)
}
get(key) {
return $this.Get(this.#goObjectId, key)
}
close() {
$this.Close(this.#goObjectId)
}
}
`
}
func (this *JSRedisLibrary) Connect(arguments *FunctionArguments) (any, error) {
var optionAddr = "127.0.0.1:6379"
var optionUsername = ""
var optionPassword = ""
var optionDB = 0
arg, ok := arguments.ArgAt(0)
if ok && arg.IsObject() {
var options = arg.Object()
if options.Has("addr") {
addr, err := options.Get("addr")
if err == nil {
optionAddr = addr.String()
}
}
if options.Has("username") {
username, err := options.Get("username")
if err == nil {
optionUsername = username.String()
}
}
if options.Has("password") {
password, err := options.Get("password")
if err == nil {
optionPassword = password.String()
}
}
if options.Has("db") {
db, err := options.Get("db")
if err == nil {
if db.IsNumber() {
optionDB = int(db.Int32())
} else if db.IsString() {
optionDB = types.Int(db.String())
}
}
}
}
var client = redis.NewClient(&redis.Options{
Network: "tcp",
Addr: optionAddr,
Username: optionUsername,
Password: optionPassword,
DB: optionDB,
})
var objectId = arguments.Context().AddGoObject(client)
return objectId, nil
}
func (this *JSRedisLibrary) Set(arguments *FunctionArguments) (any, error) {
goObject, ok := arguments.GoObjectAt(0)
if !ok {
return "", nil
}
key, ok := arguments.FormatStringAt(1)
if !ok {
return "", errors.New("set(): invalid key")
}
value, ok := arguments.FormatStringAt(2)
if !ok {
return "", errors.New("set(): invalid value")
}
ttl, ok := arguments.IntAt(3)
if !ok {
ttl = 0
}
var client = goObject.(*redis.Client)
var cmd = client.Set(context.Background(), key, value, time.Duration(ttl))
if cmd.Err() != nil {
return nil, fmt.Errorf("set(): %w", cmd.Err())
}
return nil, nil
}
func (this *JSRedisLibrary) Incr(arguments *FunctionArguments) (any, error) {
goObject, ok := arguments.GoObjectAt(0)
if !ok {
return "", nil
}
key, ok := arguments.FormatStringAt(1)
if !ok {
return "", errors.New("incr(): invalid key")
}
var client = goObject.(*redis.Client)
var cmd = client.Incr(context.Background(), key)
if cmd.Err() != nil {
return nil, fmt.Errorf("incr(): %w", cmd.Err())
}
result, err := cmd.Uint64()
if err != nil {
return nil, fmt.Errorf("incr(): %w", err)
}
return result, nil
}
func (this *JSRedisLibrary) IncrBy(arguments *FunctionArguments) (any, error) {
goObject, ok := arguments.GoObjectAt(0)
if !ok {
return "", nil
}
key, ok := arguments.FormatStringAt(1)
if !ok {
return "", errors.New("incrBy(): invalid key")
}
increment, ok := arguments.Int64At(2)
if !ok {
return "", errors.New("incrBy(): invalid increment")
}
var client = goObject.(*redis.Client)
var cmd = client.IncrBy(context.Background(), key, increment)
if cmd.Err() != nil {
return nil, fmt.Errorf("incrBy(): %w", cmd.Err())
}
result, err := cmd.Uint64()
if err != nil {
return nil, fmt.Errorf("incrBy(): %w", err)
}
return result, nil
}
func (this *JSRedisLibrary) IncrByFloat(arguments *FunctionArguments) (any, error) {
goObject, ok := arguments.GoObjectAt(0)
if !ok {
return "", nil
}
key, ok := arguments.FormatStringAt(1)
if !ok {
return "", errors.New("incrByFloat(): invalid key")
}
increment, ok := arguments.Float64At(2)
if !ok {
return "", errors.New("incrByFloat(): invalid increment")
}
var client = goObject.(*redis.Client)
var cmd = client.IncrByFloat(context.Background(), key, increment)
if cmd.Err() != nil {
return nil, fmt.Errorf("incrByFloat(): %w", cmd.Err())
}
return cmd.Val(), nil
}
func (this *JSRedisLibrary) Get(arguments *FunctionArguments) (string, error) {
goObject, ok := arguments.GoObjectAt(0)
if !ok {
return "", nil
}
key, ok := arguments.FormatStringAt(1)
if !ok {
return "", errors.New("get(): invalid key")
}
var client = goObject.(*redis.Client)
var cmd = client.Get(context.Background(), key)
if cmd.Err() != nil {
// the key not found
if cmd.Err() == redis.Nil {
return "", nil
}
return "", fmt.Errorf("get(): %w", cmd.Err())
}
return cmd.Result()
}
func (this *JSRedisLibrary) Close(arguments *FunctionArguments) (any, error) {
goObject, ok := arguments.GoObjectAt(0)
if !ok {
return nil, nil
}
err := goObject.(*redis.Client).Close()
return nil, err
}

View File

@@ -0,0 +1,63 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
var LibraryUtilsJavascript = `
let gojs = {}
gojs.prepareNamespace = function (ns) {
let pieces = ns.split(".")
let parent = gojs
let index = 0
pieces.forEach(function (piece) {
if (piece == "gojs" && index == 0) {
return
}
if (typeof(parent[piece]) == "undefined") {
parent[piece] = {}
}
parent = parent[piece]
index ++
})
}
gojs.copyAttrs = function (destObj, srcObj) {
if (typeof(srcObj) !== "object") {
return
}
for (let k in srcObj) {
if (srcObj.hasOwnProperty(k)) {
destObj[k] = srcObj[k]
}
}
}
BigInt.prototype.toJSON = function () {
return this.toString()
}
{
let callbacks = []
gojs.once = function (f) {
if (typeof(f) == "function") {
callbacks.push(f)
}
}
gojs.runOnce = function () {
callbacks.forEach(function (f){
try {
f()
} catch(e) {
var fString = f.toString()
if (fString.length > 100) {
fString = fString.substr(0, 100) + "..."
}
throw new Error(fString + ": " + e.toString())
}
})
}
}
`

View File

@@ -0,0 +1,12 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
package js
type LibraryInterface interface {
JSNamespace() string // 命名空间
JSPrototype() string // 原型代码
JSInit(ctx *Context) // 初始化时调用
JSDone(ctx *Context) // 单个调用结束时调用
JSDispose(ctx *Context) // 回收Context时调用
}

View File

@@ -0,0 +1,30 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
package js
import "sync"
var SharedLibraryManager = NewLibraryManager()
type LibraryManager struct {
list []LibraryInterface
locker sync.Mutex
}
func NewLibraryManager() *LibraryManager {
return &LibraryManager{}
}
func (this *LibraryManager) Register(library LibraryInterface) {
this.locker.Lock()
this.list =
append(this.list, library)
this.locker.Unlock()
}
func (this *LibraryManager) All() []LibraryInterface {
this.locker.Lock()
defer this.locker.Unlock()
return this.list
}

View File

@@ -0,0 +1,81 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js
import "rogchap.com/v8go"
type Object struct {
*v8go.Object
}
func NewObject(rawObject *v8go.Object) *Object {
return &Object{Object: rawObject}
}
func (this *Object) GetString(key string) (value string, ok bool) {
if !this.Has(key) {
return "", false
}
v, err := this.Get(key)
if err != nil {
return "", false
}
if v.IsNullOrUndefined() {
return "", false
}
return v.String(), true
}
func (this *Object) GetObject(key string) (value *Object, ok bool) {
if !this.Has(key) {
return nil, false
}
val, err := this.Get(key)
if err != nil {
return nil, false
}
if !val.IsObject() {
return nil, false
}
return NewObject(val.Object()), true
}
func (this *Object) GetStringsArray(key string) (value []string, ok bool) {
if !this.Has(key) {
return nil, false
}
val, err := this.Get(key)
if err != nil {
return nil, false
}
if val.IsNullOrUndefined() {
return nil, false
}
if !val.IsArray() {
return nil, false
}
var obj = val.Object()
arrayLengthValue, err := obj.Get("length")
if err != nil {
return
}
if arrayLengthValue.IsUint32() {
var arrayLength = arrayLengthValue.Uint32()
for i := uint32(0); i < arrayLength; i++ {
elementValue, err := obj.GetIdx(i)
if err != nil {
return
}
if elementValue.IsString() {
value = append(value, elementValue.String())
}
}
ok = true
return
}
return
}

View File

@@ -0,0 +1,130 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js_test
import (
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"net/http"
"time"
)
type FakeRequest struct {
host string
remoteAddr string
header http.Header
uri string
}
func (this *FakeRequest) Id() string {
return types.String(time.Now().UnixMicro())
}
func (this *FakeRequest) Server() maps.Map {
return maps.Map{"id": 123}
}
func (this *FakeRequest) Node() maps.Map {
return maps.Map{"id": 456}
}
func (this *FakeRequest) URL() string {
return "https://example.com/hello?name=Lily"
}
func (this *FakeRequest) Path() string {
return "/hello"
}
func (this *FakeRequest) URI() string {
return this.uri
}
func (this *FakeRequest) SetURI(uri string) {
this.uri = uri
}
func (this *FakeRequest) Host() string {
return this.host
}
func (this *FakeRequest) RemoteAddr() string {
return this.remoteAddr
}
func (this *FakeRequest) RawRemoteAddr() string {
return "127.0.0.1:12345"
}
func (this *FakeRequest) RemotePort() int {
return 80
}
func (this *FakeRequest) Method() string {
return http.MethodGet
}
func (this *FakeRequest) ContentLength() int64 {
return 1024
}
func (this *FakeRequest) TransferEncoding() string {
return "gzip"
}
func (this *FakeRequest) Proto() string {
return "HTTP/1.2"
}
func (this *FakeRequest) ProtoMajor() int {
return 1
}
func (this *FakeRequest) ProtoMinor() int {
return 2
}
func (this *FakeRequest) Cookie(name string) string {
return "cookie"
}
func (this *FakeRequest) Header() http.Header {
return this.header
}
func (this *FakeRequest) SetHeader(name string, values []string) {
if this.header == nil {
this.header = http.Header{}
}
this.header[name] = values
}
func (this *FakeRequest) DeleteHeader(name string) {
if this.header == nil {
return
}
delete(this.header, name)
}
func (this *FakeRequest) SetAttr(name string, value string) {
}
func (this *FakeRequest) SetVar(name string, value string) {
}
func (this *FakeRequest) Format(s string) string {
return s
}
func (this *FakeRequest) Done() {
}
func (this *FakeRequest) Allow() {
}
func (this *FakeRequest) Close() {
}

View File

@@ -0,0 +1,38 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js
import (
"github.com/iwind/TeaGo/maps"
"net/http"
)
type RequestInterface interface {
Id() string
Server() maps.Map
Node() maps.Map
URL() string
Path() string
URI() string
SetURI(uri string)
Host() string
RemoteAddr() string
RawRemoteAddr() string
RemotePort() int
Method() string
ContentLength() int64
TransferEncoding() string
Proto() string
ProtoMajor() int
ProtoMinor() int
Cookie(name string) string
Header() http.Header
SetHeader(name string, values []string)
DeleteHeader(name string)
SetAttr(name string, value string)
SetVar(name string, value string)
Format(format string) string
Done()
Close()
Allow()
}

View File

@@ -0,0 +1,50 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js_test
import (
"github.com/iwind/TeaGo/logs"
"net/http"
)
type FakeResponse struct {
host string
remoteAddr string
header http.Header
}
func (this *FakeResponse) Header() http.Header {
return this.header
}
func (this *FakeResponse) SetHeader(name string, values []string) {
if this.header == nil {
this.header = http.Header{}
}
this.header[name] = values
}
func (this *FakeResponse) DeleteHeader(name string) {
if this.header == nil {
return
}
delete(this.header, name)
}
func (this *FakeResponse) Send(status int, body string) {
logs.Println("send", status, body)
}
func (this *FakeResponse) SendFile(status int, path string) (int64, error) {
return 0, nil
}
func (this *FakeResponse) SendResp(resp *http.Response) (int64, error) {
return 0, nil
}
func (this *FakeResponse) Redirect(status int, url string) {
}

View File

@@ -0,0 +1,15 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package js
import "net/http"
type ResponseInterface interface {
SetHeader(name string, values []string)
DeleteHeader(name string)
Header() http.Header
Send(status int, body string)
SendFile(status int, path string) (int64, error)
SendResp(resp *http.Response) (int64, error)
Redirect(status int, url string)
}

View File

@@ -0,0 +1,15 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
import "rogchap.com/v8go"
type Script struct {
rawScript *v8go.UnboundScript
}
func NewScript(rawScript *v8go.UnboundScript) *Script {
return &Script{rawScript: rawScript}
}

View File

@@ -0,0 +1,51 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build script
// +build script
package js
import (
"fmt"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"rogchap.com/v8go"
)
type ObjectTemplate struct {
ctx *Context
rawTemplate *v8go.ObjectTemplate
}
func NewObjectTemplate(ctx *Context) *ObjectTemplate {
return &ObjectTemplate{
ctx: ctx,
rawTemplate: v8go.NewObjectTemplate(ctx.Isolate().RawIsolate()),
}
}
func (this *ObjectTemplate) NewInstance(ctx *Context) (*v8go.Object, error) {
return this.rawTemplate.NewInstance(ctx.RawContext())
}
func (this *ObjectTemplate) Set(name string, val any, attributes ...v8go.PropertyAttribute) error {
return this.rawTemplate.Set(name, val, attributes...)
}
func (this *ObjectTemplate) SetFunction(name string, callback FunctionCallback) error {
return this.rawTemplate.Set(name, v8go.NewFunctionTemplate(this.ctx.Isolate().RawIsolate(), func(info *v8go.FunctionCallbackInfo) *v8go.Value {
v, err := callback(NewFunctionArguments(this.ctx, info))
if err != nil {
remotelogs.Error("JS_FUNCTION_TEMPLATE", "call callback failed: "+err.Error())
return nil
}
vv, err := this.ctx.NewValue(v)
if err != nil {
remotelogs.Error("JS_FUNCTION_TEMPLATE", "create new value failed: "+err.Error()+", value: "+fmt.Sprintf("%#v", v))
return nil
}
return vv
}))
}
func (this *ObjectTemplate) SetInternalFieldCount(count int) {
this.rawTemplate.SetInternalFieldCount(uint32(count))
}