Initial commit (code only without large binaries)
This commit is contained in:
47
EdgeNode/internal/js/common_scripts.go
Normal file
47
EdgeNode/internal/js/common_scripts.go
Normal 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
|
||||
}
|
||||
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
|
||||
}
|
||||
61
EdgeNode/internal/js/context_pool.go
Normal file
61
EdgeNode/internal/js/context_pool.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
42
EdgeNode/internal/js/context_test.go
Normal file
42
EdgeNode/internal/js/context_test.go
Normal 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) {
|
||||
|
||||
}
|
||||
8
EdgeNode/internal/js/errors.go
Normal file
8
EdgeNode/internal/js/errors.go
Normal 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")
|
||||
332
EdgeNode/internal/js/function_arguments.go
Normal file
332
EdgeNode/internal/js/function_arguments.go
Normal 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
|
||||
}
|
||||
7
EdgeNode/internal/js/function_callback.go
Normal file
7
EdgeNode/internal/js/function_callback.go
Normal 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)
|
||||
115
EdgeNode/internal/js/isolate.go
Normal file
115
EdgeNode/internal/js/isolate.go
Normal 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)
|
||||
}
|
||||
161
EdgeNode/internal/js/isolate_pool.go
Normal file
161
EdgeNode/internal/js/isolate_pool.go
Normal 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()
|
||||
}
|
||||
}
|
||||
165
EdgeNode/internal/js/isolate_pool_test.go
Normal file
165
EdgeNode/internal/js/isolate_pool_test.go
Normal 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
|
||||
}**/
|
||||
}()
|
||||
}
|
||||
})
|
||||
}
|
||||
127
EdgeNode/internal/js/isolate_test.go
Normal file
127
EdgeNode/internal/js/isolate_test.go
Normal 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
|
||||
}
|
||||
}()
|
||||
}
|
||||
})
|
||||
}
|
||||
27
EdgeNode/internal/js/lib_base.go
Normal file
27
EdgeNode/internal/js/lib_base.go
Normal 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
|
||||
}
|
||||
64
EdgeNode/internal/js/lib_base64.go
Normal file
64
EdgeNode/internal/js/lib_base64.go
Normal 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
|
||||
}
|
||||
34
EdgeNode/internal/js/lib_base64_test.go
Normal file
34
EdgeNode/internal/js/lib_base64_test.go
Normal 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"))
|
||||
}
|
||||
75
EdgeNode/internal/js/lib_console_assert.go
Normal file
75
EdgeNode/internal/js/lib_console_assert.go
Normal 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
|
||||
}
|
||||
95
EdgeNode/internal/js/lib_console_log.go
Normal file
95
EdgeNode/internal/js/lib_console_log.go
Normal 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
|
||||
}
|
||||
36
EdgeNode/internal/js/lib_console_log_test.go
Normal file
36
EdgeNode/internal/js/lib_console_log_test.go
Normal 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)
|
||||
}
|
||||
68
EdgeNode/internal/js/lib_crypto.go
Normal file
68
EdgeNode/internal/js/lib_crypto.go
Normal 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
|
||||
}
|
||||
115
EdgeNode/internal/js/lib_crypto_hmac.go
Normal file
115
EdgeNode/internal/js/lib_crypto_hmac.go
Normal 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
|
||||
}
|
||||
33
EdgeNode/internal/js/lib_crypto_hmac_test.go
Normal file
33
EdgeNode/internal/js/lib_crypto_hmac_test.go
Normal 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)
|
||||
}
|
||||
48
EdgeNode/internal/js/lib_crypto_test.go
Normal file
48
EdgeNode/internal/js/lib_crypto_test.go
Normal 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()))+"]")
|
||||
}
|
||||
}
|
||||
}
|
||||
348
EdgeNode/internal/js/lib_net_http_client.go
Normal file
348
EdgeNode/internal/js/lib_net_http_client.go
Normal 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
|
||||
}
|
||||
74
EdgeNode/internal/js/lib_net_http_client_test.go
Normal file
74
EdgeNode/internal/js/lib_net_http_client_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
437
EdgeNode/internal/js/lib_net_http_request.go
Normal file
437
EdgeNode/internal/js/lib_net_http_request.go
Normal 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()
|
||||
}
|
||||
107
EdgeNode/internal/js/lib_net_http_request_test.go
Normal file
107
EdgeNode/internal/js/lib_net_http_request_test.go
Normal 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)
|
||||
}
|
||||
}()
|
||||
}
|
||||
})
|
||||
}
|
||||
203
EdgeNode/internal/js/lib_net_http_response.go
Normal file
203
EdgeNode/internal/js/lib_net_http_response.go
Normal 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()
|
||||
}
|
||||
56
EdgeNode/internal/js/lib_net_http_response_test.go
Normal file
56
EdgeNode/internal/js/lib_net_http_response_test.go
Normal 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
|
||||
}
|
||||
76
EdgeNode/internal/js/lib_net_url.go
Normal file
76
EdgeNode/internal/js/lib_net_url.go
Normal 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
|
||||
}
|
||||
38
EdgeNode/internal/js/lib_net_url_test.go
Normal file
38
EdgeNode/internal/js/lib_net_url_test.go
Normal 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)
|
||||
}
|
||||
255
EdgeNode/internal/js/lib_redis.go
Normal file
255
EdgeNode/internal/js/lib_redis.go
Normal 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
|
||||
}
|
||||
63
EdgeNode/internal/js/lib_utils.go
Normal file
63
EdgeNode/internal/js/lib_utils.go
Normal 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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
`
|
||||
12
EdgeNode/internal/js/library_interface.go
Normal file
12
EdgeNode/internal/js/library_interface.go
Normal 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时调用
|
||||
}
|
||||
30
EdgeNode/internal/js/library_manager.go
Normal file
30
EdgeNode/internal/js/library_manager.go
Normal 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
|
||||
}
|
||||
81
EdgeNode/internal/js/object.go
Normal file
81
EdgeNode/internal/js/object.go
Normal 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
|
||||
}
|
||||
130
EdgeNode/internal/js/request_fake_test.go
Normal file
130
EdgeNode/internal/js/request_fake_test.go
Normal 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() {
|
||||
|
||||
}
|
||||
38
EdgeNode/internal/js/request_interface.go
Normal file
38
EdgeNode/internal/js/request_interface.go
Normal 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()
|
||||
}
|
||||
50
EdgeNode/internal/js/response_fake_test.go
Normal file
50
EdgeNode/internal/js/response_fake_test.go
Normal 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) {
|
||||
|
||||
}
|
||||
15
EdgeNode/internal/js/response_interface.go
Normal file
15
EdgeNode/internal/js/response_interface.go
Normal 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)
|
||||
}
|
||||
15
EdgeNode/internal/js/script.go
Normal file
15
EdgeNode/internal/js/script.go
Normal 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}
|
||||
}
|
||||
51
EdgeNode/internal/js/template_object.go
Normal file
51
EdgeNode/internal/js/template_object.go
Normal 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))
|
||||
}
|
||||
Reference in New Issue
Block a user