572 lines
17 KiB
Go
572 lines
17 KiB
Go
|
package service
|
|||
|
|
|||
|
import (
|
|||
|
_ "embed"
|
|||
|
"encoding/json"
|
|||
|
"errors"
|
|||
|
"fmt"
|
|||
|
"reflect"
|
|||
|
"regexp"
|
|||
|
"strings"
|
|||
|
"sync"
|
|||
|
"time"
|
|||
|
|
|||
|
"apigo.cc/gojs"
|
|||
|
"apigo.cc/gojs/goja"
|
|||
|
"github.com/gorilla/websocket"
|
|||
|
"github.com/ssgo/config"
|
|||
|
"github.com/ssgo/discover"
|
|||
|
"github.com/ssgo/log"
|
|||
|
"github.com/ssgo/redis"
|
|||
|
"github.com/ssgo/s"
|
|||
|
"github.com/ssgo/standard"
|
|||
|
"github.com/ssgo/u"
|
|||
|
)
|
|||
|
|
|||
|
//go:embed service.ts
|
|||
|
var serviceTS string
|
|||
|
|
|||
|
//go:embed README.md
|
|||
|
var serviceMD string
|
|||
|
|
|||
|
var server *s.AsyncServer
|
|||
|
|
|||
|
var pools = map[string]*gojs.Pool{}
|
|||
|
|
|||
|
var poolExists = map[string]bool{}
|
|||
|
var poolActionRegistered = map[string]bool{}
|
|||
|
var poolsLock = sync.RWMutex{}
|
|||
|
var waitChan chan bool
|
|||
|
|
|||
|
type LimiterConfig struct {
|
|||
|
From string
|
|||
|
Time int
|
|||
|
Times int
|
|||
|
}
|
|||
|
|
|||
|
type Config struct {
|
|||
|
SessionKey string
|
|||
|
DeviceKey string
|
|||
|
ClientKey string
|
|||
|
UserIdKey string
|
|||
|
SessionProvider string
|
|||
|
SessionTimeout int64
|
|||
|
AuthFieldMessage string
|
|||
|
VerifyFieldMessage string
|
|||
|
LimitedMessage string
|
|||
|
Limiters map[string]*LimiterConfig
|
|||
|
LimiterRedis string
|
|||
|
|
|||
|
Proxy map[string]string
|
|||
|
Rewrite map[string]string
|
|||
|
Static map[string]string
|
|||
|
}
|
|||
|
|
|||
|
var serviceConfig Config
|
|||
|
var onStop goja.Callable
|
|||
|
|
|||
|
var limiters = map[string]*s.Limiter{}
|
|||
|
|
|||
|
func init() {
|
|||
|
obj := map[string]any{
|
|||
|
"config": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
|||
|
s.Init()
|
|||
|
|
|||
|
// 处理配置
|
|||
|
args := gojs.MakeArgs(&argsIn, vm)
|
|||
|
serviceConfig = Config{"Session", "Device", "Client", "userId", "", 3600, "auth failed", "verify failed", "too many requests", nil, "", map[string]string{}, map[string]string{}, map[string]string{}}
|
|||
|
if errs := config.LoadConfig("service", &serviceConfig); errs != nil && len(errs) > 0 {
|
|||
|
panic(vm.NewGoError(errs[0]))
|
|||
|
}
|
|||
|
config.LoadConfig("service", &discover.Config)
|
|||
|
// var auth goja.Callable
|
|||
|
if conf := args.Obj(0); conf != nil {
|
|||
|
u.Convert(conf.O.Export(), &serviceConfig)
|
|||
|
u.Convert(conf.O.Export(), &s.Config)
|
|||
|
u.Convert(conf.O.Export(), &discover.Config)
|
|||
|
// auth = conf.Func("auth")
|
|||
|
onStop = conf.Func("onStop")
|
|||
|
|
|||
|
if serviceConfig.SessionProvider != "" {
|
|||
|
sessionRedis = redis.GetRedis(serviceConfig.SessionProvider, args.Logger)
|
|||
|
}
|
|||
|
sessionTimeout = serviceConfig.SessionTimeout
|
|||
|
if sessionTimeout < 0 {
|
|||
|
sessionTimeout = 0
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 身份验证和Session
|
|||
|
authAccessToken := s.Config.AccessTokens != nil && len(s.Config.AccessTokens) > 0
|
|||
|
s.SetClientKeys(serviceConfig.DeviceKey, serviceConfig.ClientKey, serviceConfig.SessionKey)
|
|||
|
if serviceConfig.SessionKey != "" {
|
|||
|
s.SetAuthChecker(func(authLevel int, logger *log.Logger, url *string, args map[string]any, request *s.Request, response *s.Response, options *s.WebServiceOptions) (pass bool, object any) {
|
|||
|
var session *Session
|
|||
|
setAuthLevel := 0
|
|||
|
if serviceConfig.SessionKey != "" {
|
|||
|
sessionID := request.GetSessionId()
|
|||
|
if sessionID != "" {
|
|||
|
session = NewSession(sessionID, logger)
|
|||
|
}
|
|||
|
if userId, ok := session.data[serviceConfig.UserIdKey]; ok {
|
|||
|
request.SetUserId(u.String(userId))
|
|||
|
}
|
|||
|
// 优先使用session中的authLevel
|
|||
|
if authLevel > 0 {
|
|||
|
if authLevelBySession, ok := session.data["_authLevel"]; ok {
|
|||
|
setAuthLevel = u.Int(authLevelBySession)
|
|||
|
}
|
|||
|
}
|
|||
|
// if auth != nil {
|
|||
|
// requestParams, _ := makeRequestParams(args, nil, request, response, nil, session, logger)
|
|||
|
// requestParams["authLevel"] = authLevel
|
|||
|
// if r, err := auth(nil, vm.ToValue(requestParams)); err == nil {
|
|||
|
// if r.ExportType().Kind() == reflect.Bool {
|
|||
|
// if r.ToBoolean() {
|
|||
|
// return true, session
|
|||
|
// } else {
|
|||
|
// return false, nil
|
|||
|
// }
|
|||
|
// } else {
|
|||
|
// return false, r.Export()
|
|||
|
// }
|
|||
|
// } else {
|
|||
|
// logger.Error(err.Error())
|
|||
|
// return false, nil
|
|||
|
// }
|
|||
|
// } else {
|
|||
|
// return true, session
|
|||
|
// }
|
|||
|
}
|
|||
|
|
|||
|
// 如果没有session或session中的authLevel为0,则使用Access-Token中的authLevel(服务间调用)
|
|||
|
if authAccessToken && setAuthLevel == 0 && authLevel > 0 {
|
|||
|
setAuthLevel = s.GetAuthTokenLevel(request.Header.Get("Access-Token"))
|
|||
|
}
|
|||
|
if setAuthLevel >= authLevel {
|
|||
|
return true, session
|
|||
|
} else {
|
|||
|
msg := serviceConfig.AuthFieldMessage
|
|||
|
if strings.Contains(msg, "{{") {
|
|||
|
msg = strings.ReplaceAll(msg, "{{TARGET_AUTHLEVEL}}", u.String(authLevel))
|
|||
|
msg = strings.ReplaceAll(msg, "{{USER_AUTHLEVEL}}", u.String(setAuthLevel))
|
|||
|
}
|
|||
|
var obj any
|
|||
|
if json.Unmarshal([]byte(msg), &obj) == nil {
|
|||
|
return false, obj
|
|||
|
} else {
|
|||
|
return false, msg
|
|||
|
}
|
|||
|
}
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
// 限流器
|
|||
|
if serviceConfig.Limiters != nil {
|
|||
|
var limiterRedis *redis.Redis
|
|||
|
if serviceConfig.LimiterRedis != "" {
|
|||
|
limiterRedis = redis.GetRedis(serviceConfig.LimiterRedis, args.Logger)
|
|||
|
}
|
|||
|
for name, limiter := range serviceConfig.Limiters {
|
|||
|
switch limiter.From {
|
|||
|
case "ip":
|
|||
|
limiter.From = "header." + standard.DiscoverHeaderClientIp
|
|||
|
case "user":
|
|||
|
limiter.From = "header." + standard.DiscoverHeaderUserId
|
|||
|
case "device":
|
|||
|
limiter.From = "header." + standard.DiscoverHeaderDeviceId
|
|||
|
}
|
|||
|
if limiterRedis != nil {
|
|||
|
limiters[name] = s.NewLimiter(name, limiter.From, time.Duration(limiter.Time)*time.Millisecond, limiter.Times, limiterRedis)
|
|||
|
} else {
|
|||
|
limiters[name] = s.NewLocalLimiter(name, limiter.From, time.Duration(limiter.Time)*time.Millisecond, limiter.Times)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return nil
|
|||
|
},
|
|||
|
"start": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
|||
|
if server != nil {
|
|||
|
panic(vm.NewGoError(errors.New("server already started")))
|
|||
|
}
|
|||
|
|
|||
|
// 处理静态文件
|
|||
|
if len(serviceConfig.Static) > 0 {
|
|||
|
updateStatic(serviceConfig.Static)
|
|||
|
}
|
|||
|
if len(serviceConfig.Rewrite) > 0 {
|
|||
|
updateRewrite(serviceConfig.Rewrite)
|
|||
|
s.SetRewriteBy(rewrite)
|
|||
|
}
|
|||
|
if len(serviceConfig.Proxy) > 0 {
|
|||
|
updateProxy(serviceConfig.Proxy)
|
|||
|
s.SetProxyBy(proxy)
|
|||
|
}
|
|||
|
|
|||
|
// 启动服务
|
|||
|
server = s.AsyncStart()
|
|||
|
waitChan = make(chan bool, 1)
|
|||
|
server.OnStop(func() {
|
|||
|
if onStop != nil {
|
|||
|
onStop(nil)
|
|||
|
}
|
|||
|
})
|
|||
|
server.OnStopped(func() {
|
|||
|
if waitChan != nil {
|
|||
|
waitChan <- true
|
|||
|
}
|
|||
|
})
|
|||
|
return vm.ToValue(server.Addr)
|
|||
|
},
|
|||
|
"stop": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
|||
|
if server == nil {
|
|||
|
panic(vm.NewGoError(errors.New("server not started")))
|
|||
|
}
|
|||
|
server.Stop()
|
|||
|
pools = map[string]*gojs.Pool{}
|
|||
|
server = nil
|
|||
|
return nil
|
|||
|
},
|
|||
|
"register": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
|||
|
args := gojs.MakeArgs(&argsIn, vm).Check(2)
|
|||
|
o := args.Obj(0)
|
|||
|
action := args.Func(1)
|
|||
|
if action == nil {
|
|||
|
panic(vm.NewGoError(errors.New("action must be a callback function")))
|
|||
|
}
|
|||
|
|
|||
|
authLevel := o.Int("authLevel")
|
|||
|
host := o.Str("host")
|
|||
|
method := strings.ToUpper(o.Str("method"))
|
|||
|
path := o.Str("path")
|
|||
|
memo := o.Str("memo")
|
|||
|
var usedLimiters []*s.Limiter
|
|||
|
for _, limiterName := range o.Array("limiters") {
|
|||
|
if limiter1, ok := limiters[u.String(limiterName)]; ok {
|
|||
|
usedLimiters = append(usedLimiters, limiter1)
|
|||
|
}
|
|||
|
}
|
|||
|
if verifiesObj := o.Obj("verifies"); verifiesObj != nil {
|
|||
|
verifiesSet := map[string]func(any, *goja.Runtime) bool{}
|
|||
|
for _, field := range verifiesObj.Keys() {
|
|||
|
v := verifiesObj.Get(field)
|
|||
|
// 根据类型设置验证器,存储到vm中,在请求到达时进行有效性验证
|
|||
|
switch v.ExportType().Kind() {
|
|||
|
case reflect.String:
|
|||
|
verifiesSet[field] = verifyRegexp(u.String(v.Export()))
|
|||
|
case reflect.Int, reflect.Int64:
|
|||
|
verifiesSet[field] = verifyLen(u.Int(v.Export()))
|
|||
|
case reflect.Bool:
|
|||
|
verifiesSet[field] = verifyRequire()
|
|||
|
case reflect.Slice:
|
|||
|
list := make([]string, 0)
|
|||
|
vm.ForOf(v, func(v1 goja.Value) bool {
|
|||
|
list = append(list, u.String(v1.Export()))
|
|||
|
return true
|
|||
|
})
|
|||
|
verifiesSet[field] = verifyIn(list)
|
|||
|
default:
|
|||
|
if fn, ok := goja.AssertFunction(v); ok {
|
|||
|
verifiesSet[field] = verifyFunc(fn, verifiesObj)
|
|||
|
} else if obj := v.ToObject(vm); obj != nil {
|
|||
|
// 支持传入js的正则表达式对象
|
|||
|
if testV := obj.Get("test"); testV != nil {
|
|||
|
if testFn, ok := goja.AssertFunction(testV); ok {
|
|||
|
verifiesSet[field] = verifyFunc(testFn, obj)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
vm.GoData[fmt.Sprint("VERIFY_"+host, method, path)] = verifiesSet
|
|||
|
}
|
|||
|
|
|||
|
opt := s.WebServiceOptions{
|
|||
|
NoBody: o.Bool("noBody"),
|
|||
|
NoLog200: o.Bool("noLog200"),
|
|||
|
Host: host,
|
|||
|
//Ext: nil,
|
|||
|
Limiters: usedLimiters,
|
|||
|
}
|
|||
|
|
|||
|
startFile := u.String(vm.GoData["startFile"])
|
|||
|
poolsLock.RLock()
|
|||
|
poolExist := poolExists[startFile]
|
|||
|
poolsLock.RUnlock()
|
|||
|
if poolExist {
|
|||
|
// 从对象调用(支持并发)
|
|||
|
actionKey := "REGISTER_" + host + method + path
|
|||
|
vm.GoData[actionKey] = action
|
|||
|
vm.GoData[actionKey+"This"] = args.This
|
|||
|
if method == "WS" {
|
|||
|
vm.GoData[actionKey+"onMessage"] = o.Func("onMessage")
|
|||
|
vm.GoData[actionKey+"onClose"] = o.Func("onClose")
|
|||
|
}
|
|||
|
poolsLock.Lock()
|
|||
|
actionRegistered := poolActionRegistered[actionKey]
|
|||
|
if !actionRegistered {
|
|||
|
poolActionRegistered[actionKey] = true
|
|||
|
}
|
|||
|
poolsLock.Unlock()
|
|||
|
if !actionRegistered {
|
|||
|
if strings.ToUpper(method) == "WS" {
|
|||
|
s.RegisterWebsocketWithOptions(authLevel, path, &websocket.Upgrader{}, makeWSAction(startFile, actionKey), nil, nil, nil, true, "", opt)
|
|||
|
} else {
|
|||
|
s.RestfulWithOptions(authLevel, method, path, makeOuterAction(startFile, actionKey), memo, opt)
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
// 无对象池,直接调用(单线程)
|
|||
|
s.RestfulWithOptions(authLevel, method, path, makeInnerAction(action, vm, args.This), memo, opt)
|
|||
|
}
|
|||
|
return nil
|
|||
|
},
|
|||
|
"load": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
|||
|
args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
|||
|
actionFile := args.Path(0)
|
|||
|
opt := args.Obj(1)
|
|||
|
var mi, ma, idle uint
|
|||
|
//var num uint
|
|||
|
mainArgs := make([]any, 0)
|
|||
|
debug := false
|
|||
|
if opt != nil {
|
|||
|
mi = uint(opt.Int("min"))
|
|||
|
ma = uint(opt.Int("max"))
|
|||
|
idle = uint(opt.Int("idle"))
|
|||
|
//num = uint(opt.Int("num"))
|
|||
|
debug = opt.Bool("debug")
|
|||
|
if mainArgs1 := opt.Array("args"); mainArgs1 != nil {
|
|||
|
mainArgs = mainArgs1
|
|||
|
}
|
|||
|
}
|
|||
|
if !u.FileExists(actionFile) {
|
|||
|
panic(vm.NewGoError(errors.New("actionFile must be a js file path")))
|
|||
|
}
|
|||
|
actionCode := u.ReadFileN(actionFile)
|
|||
|
if !strings.Contains(actionCode, "function main(") || !strings.Contains(actionCode, ".register(") {
|
|||
|
panic(vm.NewGoError(errors.New("actionFile must be a js file with main function and call service.register")))
|
|||
|
}
|
|||
|
poolsLock.Lock()
|
|||
|
poolExists[actionFile] = true
|
|||
|
poolsLock.Unlock()
|
|||
|
p := gojs.NewPoolByCode(actionCode, actionFile, gojs.PoolConfig{
|
|||
|
Min: mi,
|
|||
|
Max: ma,
|
|||
|
Idle: idle,
|
|||
|
Debug: debug,
|
|||
|
Args: mainArgs,
|
|||
|
}, args.Logger)
|
|||
|
//p := gojs.NewLBByCode(actionCode, actionFile, gojs.LBConfig{
|
|||
|
// Num: num,
|
|||
|
// Debug: debug,
|
|||
|
// Args: mainArgs,
|
|||
|
//}, args.Logger)
|
|||
|
poolsLock.Lock()
|
|||
|
pools[actionFile] = p
|
|||
|
poolsLock.Unlock()
|
|||
|
return nil
|
|||
|
},
|
|||
|
"newCaller": NewCaller,
|
|||
|
}
|
|||
|
|
|||
|
gojs.Register("apigo.cc/gojs/service", gojs.Module{
|
|||
|
Object: obj,
|
|||
|
TsCode: serviceTS,
|
|||
|
Example: serviceMD,
|
|||
|
WaitForStop: func() {
|
|||
|
if waitChan != nil {
|
|||
|
<-waitChan
|
|||
|
}
|
|||
|
},
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
func verifyRegexp(regexpStr string) func(any, *goja.Runtime) bool {
|
|||
|
if rx, err := regexp.Compile(regexpStr); err != nil {
|
|||
|
return func(value any, vm *goja.Runtime) bool {
|
|||
|
return rx.MatchString(u.String(value))
|
|||
|
}
|
|||
|
}
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
func verifyLen(checkLength int) func(any, *goja.Runtime) bool {
|
|||
|
return func(value any, vm *goja.Runtime) bool {
|
|||
|
v := u.FinalType(reflect.ValueOf(value))
|
|||
|
if v.Kind() == reflect.Slice || v.Kind() == reflect.Map {
|
|||
|
return v.Len() >= checkLength
|
|||
|
}
|
|||
|
return len(u.String(value)) >= checkLength
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func verifyRequire() func(any, *goja.Runtime) bool {
|
|||
|
return func(value any, vm *goja.Runtime) bool {
|
|||
|
value = u.FixPtr(value)
|
|||
|
switch realValue := value.(type) {
|
|||
|
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
|||
|
return u.Int64(realValue) != 0
|
|||
|
case bool:
|
|||
|
return realValue
|
|||
|
default:
|
|||
|
v := u.FinalType(reflect.ValueOf(value))
|
|||
|
if v.Kind() == reflect.Slice || v.Kind() == reflect.Map {
|
|||
|
return v.Len() >= 0
|
|||
|
}
|
|||
|
return len(u.String(value)) >= 0
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func verifyIn(list []string) func(any, *goja.Runtime) bool {
|
|||
|
return func(value any, vm *goja.Runtime) bool {
|
|||
|
return u.StringIn(list, u.String(value))
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func verifyFunc(callback goja.Callable, thisObj goja.Value) func(any, *goja.Runtime) bool {
|
|||
|
return func(value any, vm *goja.Runtime) bool {
|
|||
|
if r, err := callback(thisObj, vm.ToValue(value)); err == nil {
|
|||
|
return u.Bool(r.Export())
|
|||
|
} else {
|
|||
|
log.DefaultLogger.Error(err.Error())
|
|||
|
}
|
|||
|
return false
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func makeRequestParams(args map[string]any, headers map[string]string, request *s.Request, response *s.Response, client *websocket.Conn, caller *discover.Caller, session *Session, logger *log.Logger) (gojs.Map, *Response) {
|
|||
|
resp := &Response{
|
|||
|
resp: response,
|
|||
|
endCh: make(chan bool, 1),
|
|||
|
}
|
|||
|
params := gojs.Map{
|
|||
|
"args": args,
|
|||
|
"logger": gojs.MakeLogger(logger),
|
|||
|
"request": MakeRequest(request, args, headers),
|
|||
|
"response": gojs.MakeMap(resp),
|
|||
|
"client": MakeWSClient(client),
|
|||
|
}
|
|||
|
if headers != nil {
|
|||
|
params["headers"] = headers
|
|||
|
}
|
|||
|
if session != nil {
|
|||
|
params["session"] = gojs.MakeMap(session)
|
|||
|
}
|
|||
|
if caller != nil {
|
|||
|
params["caller"] = gojs.MakeMap(Caller{client: caller})
|
|||
|
}
|
|||
|
return params, resp
|
|||
|
}
|
|||
|
|
|||
|
func runAction(action goja.Callable, vm *goja.Runtime, thisArg goja.Value, args map[string]any, headers map[string]string, request *s.Request, response *s.Response, client *websocket.Conn, caller *discover.Caller, session *Session, logger *log.Logger) (any, error) {
|
|||
|
vm.CallbackLocker.Lock()
|
|||
|
defer vm.CallbackLocker.Unlock()
|
|||
|
|
|||
|
// 验证请求参数的有效性
|
|||
|
if verifies, ok := vm.GoData["VERIFY_"+u.String(request.Get("registerTag"))].(map[string]func(any, *goja.Runtime) bool); ok {
|
|||
|
failedFields := make([]string, 0)
|
|||
|
for k, v := range args {
|
|||
|
if verifier, ok1 := verifies[k]; ok1 {
|
|||
|
if !verifier(v, vm) {
|
|||
|
failedFields = append(failedFields, k)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
// 数据有效性验证失败
|
|||
|
if len(failedFields) > 0 {
|
|||
|
response.WriteHeader(400)
|
|||
|
msg := serviceConfig.VerifyFieldMessage
|
|||
|
if strings.Contains(msg, "{{") {
|
|||
|
msg = strings.ReplaceAll(msg, "{{FAILED_FIELDS}}", strings.Join(failedFields, ", "))
|
|||
|
}
|
|||
|
var obj any
|
|||
|
if json.Unmarshal([]byte(msg), &obj) == nil {
|
|||
|
return obj, nil
|
|||
|
} else {
|
|||
|
return msg, nil
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
requestParams, resp := makeRequestParams(args, headers, request, response, client, caller, session, logger)
|
|||
|
var r any
|
|||
|
|
|||
|
r1, err := action(thisArg, vm.ToValue(requestParams))
|
|||
|
if err == nil && r1 != nil {
|
|||
|
r = r1.Export()
|
|||
|
}
|
|||
|
if err != nil {
|
|||
|
logger.Error(err.Error())
|
|||
|
}
|
|||
|
if response != nil && r == nil && err == nil {
|
|||
|
<-resp.endCh
|
|||
|
r = resp.result
|
|||
|
}
|
|||
|
return r, err
|
|||
|
}
|
|||
|
|
|||
|
func makeInnerAction(action goja.Callable, vm *goja.Runtime, thisArg goja.Value) any {
|
|||
|
return func(args map[string]any, headers map[string]string, request *s.Request, response *s.Response, caller *discover.Caller, session *Session, logger *log.Logger) any {
|
|||
|
r, _ := runAction(action, vm, thisArg, args, headers, request, response, nil, caller, session, logger)
|
|||
|
return r
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
func getPool(startFile string) *gojs.Pool {
|
|||
|
var pool *gojs.Pool
|
|||
|
for i := 0; i < 10; i++ {
|
|||
|
poolsLock.RLock()
|
|||
|
pool = pools[startFile]
|
|||
|
poolsLock.RUnlock()
|
|||
|
if pool != nil {
|
|||
|
return pool
|
|||
|
}
|
|||
|
time.Sleep(time.Millisecond * 100)
|
|||
|
}
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
func makeOuterAction(startFile string, actionKey string) any {
|
|||
|
return func(args map[string]any, headers map[string]string, request *s.Request, response *s.Response, caller *discover.Caller, session *Session, logger *log.Logger) any {
|
|||
|
if pool := getPool(startFile); pool != nil {
|
|||
|
rt := pool.Get()
|
|||
|
defer pool.Put(rt)
|
|||
|
if action, ok := rt.GetGoData(actionKey).(goja.Callable); ok {
|
|||
|
var thisArg goja.Value
|
|||
|
if thisArgV, ok := rt.GetGoData(actionKey + "This").(goja.Value); ok {
|
|||
|
thisArg = thisArgV
|
|||
|
}
|
|||
|
r, _ := rt.RunVM(func(vm *goja.Runtime) (any, error) {
|
|||
|
r2, err2 := runAction(action, vm, thisArg, args, headers, request, response, nil, caller, session, logger)
|
|||
|
return r2, err2
|
|||
|
|
|||
|
})
|
|||
|
return r
|
|||
|
}
|
|||
|
}
|
|||
|
return nil
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
type PoolStatus struct {
|
|||
|
Total uint
|
|||
|
MaxTotal uint
|
|||
|
MaxWaiting uint
|
|||
|
CreateTimes uint
|
|||
|
}
|
|||
|
|
|||
|
func GetPoolStatus() map[string]PoolStatus {
|
|||
|
out := map[string]PoolStatus{}
|
|||
|
for k, v := range pools {
|
|||
|
total, maxTotal, maxWaiting, createTimes := v.Count()
|
|||
|
out[k] = PoolStatus{
|
|||
|
Total: total,
|
|||
|
MaxTotal: maxTotal,
|
|||
|
MaxWaiting: maxWaiting,
|
|||
|
CreateTimes: createTimes,
|
|||
|
}
|
|||
|
}
|
|||
|
return out
|
|||
|
}
|