Compare commits

..

No commits in common. "main" and "v0.0.9" have entirely different histories.
main ... v0.0.9

20 changed files with 672 additions and 1107 deletions

View File

@ -22,10 +22,7 @@ func NewCaller(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
func makeResult(r *httpclient.Result, vm *goja.Runtime) goja.Value { func makeResult(r *httpclient.Result, vm *goja.Runtime) goja.Value {
if r.Error != nil { if r.Error != nil {
// panic(vm.NewGoError(r.Error)) panic(vm.NewGoError(r.Error))
vm.SetData("_lastError", r.Error)
gojs.GetLogger(vm).Error(r.Error.Error())
return nil
} }
headers := map[string]string{} headers := map[string]string{}
for k, v := range r.Response.Header { for k, v := range r.Response.Header {

56
go.mod
View File

@ -1,54 +1,44 @@
module apigo.cc/gojs/service module apigo.cc/gojs/service
go 1.24.0 go 1.18
require ( require (
apigo.cc/gojs v0.0.32 apigo.cc/gojs v0.0.12
apigo.cc/gojs/console v0.0.4 apigo.cc/gojs/console v0.0.2
apigo.cc/gojs/file v0.0.7 apigo.cc/gojs/http v0.0.3
apigo.cc/gojs/http v0.0.8 apigo.cc/gojs/util v0.0.8
apigo.cc/gojs/runtime v0.0.4
apigo.cc/gojs/task v0.0.8
apigo.cc/gojs/util v0.0.16
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/ssgo/config v1.7.10 github.com/ssgo/config v1.7.9
github.com/ssgo/discover v1.7.10 github.com/ssgo/discover v1.7.9
github.com/ssgo/httpclient v1.7.8 github.com/ssgo/httpclient v1.7.8
github.com/ssgo/log v1.7.10 github.com/ssgo/log v1.7.7
github.com/ssgo/redis v1.7.8 github.com/ssgo/redis v1.7.7
github.com/ssgo/s v1.7.25 github.com/ssgo/s v1.7.22
github.com/ssgo/standard v1.7.7 github.com/ssgo/standard v1.7.7
github.com/ssgo/u v1.7.23 github.com/ssgo/u v1.7.13
) )
require ( require (
github.com/ZZMarquis/gm v1.3.2 // indirect github.com/ZZMarquis/gm v1.3.2 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/emmansun/gmsm v0.40.0 // indirect github.com/emmansun/gmsm v0.29.6 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-rod/rod v0.116.2 // indirect
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
github.com/gomodule/redigo v1.9.2 // indirect github.com/gomodule/redigo v1.9.2 // indirect
github.com/google/pprof v0.0.0-20250903194437-c28834ac2320 // indirect github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
github.com/obscuren/ecies v0.0.0-20150213224233-7c0f4a9b18d9 // indirect github.com/obscuren/ecies v0.0.0-20150213224233-7c0f4a9b18d9 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/shirou/gopsutil/v3 v3.24.5 // indirect github.com/shirou/gopsutil/v3 v3.24.5 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/ssgo/tool v0.4.29 // indirect github.com/ssgo/tool v0.4.28 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect github.com/tklauser/numcpus v0.9.0 // indirect
github.com/ysmood/fetchup v0.2.3 // indirect
github.com/ysmood/goob v0.4.0 // indirect
github.com/ysmood/got v0.40.0 // indirect
github.com/ysmood/gson v0.7.3 // indirect
github.com/ysmood/leakless v0.9.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/crypto v0.46.0 // indirect golang.org/x/crypto v0.31.0 // indirect
golang.org/x/net v0.48.0 // indirect golang.org/x/net v0.32.0 // indirect
golang.org/x/sys v0.39.0 // indirect golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.32.0 // indirect golang.org/x/text v0.21.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

View File

@ -91,10 +91,7 @@ func (r *Request) MakeURL(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value
func (r *Request) ReadAll(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { func (r *Request) ReadAll(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
data, err := io.ReadAll(r.req.Body) data, err := io.ReadAll(r.req.Body)
if err != nil { if err != nil {
// panic(vm.NewGoError(err)) panic(vm.NewGoError(err))
vm.SetData("_lastError", err)
gojs.GetLogger(vm).Error(err.Error())
return nil
} }
return vm.ToValue(data) return vm.ToValue(data)
} }
@ -105,10 +102,7 @@ func (r *Request) Read(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
data := make([]byte, size) data := make([]byte, size)
n, err := r.req.Body.Read(data) n, err := r.req.Body.Read(data)
if err != nil { if err != nil {
// panic(vm.NewGoError(err)) panic(vm.NewGoError(err))
vm.SetData("_lastError", err)
gojs.GetLogger(vm).Error(err.Error())
return nil
} }
return vm.ToValue(data[0:n]) return vm.ToValue(data[0:n])
} }
@ -116,10 +110,7 @@ func (r *Request) Read(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
func (r *Request) Close(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { func (r *Request) Close(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
err := r.req.Body.Close() err := r.req.Body.Close()
if err != nil { if err != nil {
// panic(vm.NewGoError(err)) panic(vm.NewGoError(err))
vm.SetData("_lastError", err)
gojs.GetLogger(vm).Error(err.Error())
return nil
} }
return nil return nil
} }

View File

@ -93,10 +93,7 @@ func (r *Response) Write(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value
args := gojs.MakeArgs(&argsIn, vm).Check(1) args := gojs.MakeArgs(&argsIn, vm).Check(1)
n, err := r.resp.Write(args.Bytes(0)) n, err := r.resp.Write(args.Bytes(0))
if err != nil { if err != nil {
// panic(vm.NewGoError(err)) panic(vm.NewGoError(err))
vm.SetData("_lastError", err)
gojs.GetLogger(vm).Error(err.Error())
return vm.ToValue(false)
} }
return vm.ToValue(n) return vm.ToValue(n)
} }

View File

@ -37,8 +37,6 @@ var serviceMD string
var server *s.AsyncServer var server *s.AsyncServer
var pools = map[string]*gojs.Pool{} var pools = map[string]*gojs.Pool{}
var poolsMTime = map[string]int64{}
var poolsConfig = map[string]gojs.PoolConfig{}
var poolExists = map[string]bool{} var poolExists = map[string]bool{}
var poolActionRegistered = map[string]bool{} var poolActionRegistered = map[string]bool{}
@ -50,11 +48,10 @@ type TplCache struct {
Tpl *template.Template Tpl *template.Template
} }
// var tplFunc = map[string]any{} var tplFunc = map[string]any{}
var tplReplaces = map[string]string{} var tplFuncLock = sync.RWMutex{}
var tplLock = sync.RWMutex{} var tplCache = map[string]*TplCache{}
var tplCacheLock = sync.RWMutex{}
// var tplCache = map[string]*TplCache{}
type LimiterConfig struct { type LimiterConfig struct {
From string From string
@ -74,8 +71,6 @@ type Config struct {
LimitedMessage string LimitedMessage string
Limiters map[string]*LimiterConfig Limiters map[string]*LimiterConfig
LimiterRedis string LimiterRedis string
HotLoad int
TplSafePaths []string
Proxy map[string]string Proxy map[string]string
Rewrite map[string]string Rewrite map[string]string
@ -91,16 +86,13 @@ var configed = false
func initConfig(opt *gojs.Obj, logger *log.Logger, vm *goja.Runtime) { func initConfig(opt *gojs.Obj, logger *log.Logger, vm *goja.Runtime) {
configed = true configed = true
s.InitConfig() s.InitConfig()
if startPath := vm.GetData("startPath"); startPath != nil { if startPath, ok := vm.GoData["startPath"]; ok {
s.SetWorkPath(u.String(startPath)) s.SetWorkPath(u.String(startPath))
} }
// 处理配置 // 处理配置
serviceConfig = Config{"Session", "Device", "Client", "id", "", 3600, "auth failed", "verify failed", "too many requests", nil, "", 0, []string{}, map[string]string{}, map[string]string{}, map[string]string{}} serviceConfig = Config{"Session", "Device", "Client", "id", "", 3600, "auth failed", "verify failed", "too many requests", nil, "", map[string]string{}, map[string]string{}, map[string]string{}}
if errs := config.LoadConfig("service", &serviceConfig); len(errs) > 0 { if errs := config.LoadConfig("service", &serviceConfig); errs != nil && len(errs) > 0 {
// panic(vm.NewGoError(errs[0])) panic(vm.NewGoError(errs[0]))
vm.SetData("_lastError", errs[0])
gojs.GetLogger(vm).Error(errs[0].Error())
return
} }
config.LoadConfig("service", &discover.Config) config.LoadConfig("service", &discover.Config)
// var auth goja.Callable // var auth goja.Callable
@ -123,6 +115,7 @@ func initConfig(opt *gojs.Obj, logger *log.Logger, vm *goja.Runtime) {
// 身份验证和Session // 身份验证和Session
authAccessToken := len(s.Config.AccessTokens) > 0 authAccessToken := len(s.Config.AccessTokens) > 0
s.SetClientKeys(serviceConfig.DeviceKey, serviceConfig.ClientKey, serviceConfig.SessionKey) s.SetClientKeys(serviceConfig.DeviceKey, serviceConfig.ClientKey, serviceConfig.SessionKey)
fmt.Println(u.BMagenta("=====>>>>> 1"), serviceConfig.SessionKey)
if 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) { 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 var session *Session
@ -143,38 +136,24 @@ func initConfig(opt *gojs.Obj, logger *log.Logger, vm *goja.Runtime) {
} }
} }
} }
// 如果没有session中的authLevel验证失败则使用Access-Token中的authLevel服务间调用 // 如果没有session中的authLevel验证失败则使用Access-Token中的authLevel服务间调用
if authAccessToken && setAuthLevel < authLevel { if authAccessToken && setAuthLevel < authLevel {
setAuthLevel = s.GetAuthTokenLevel(request.Header.Get("Access-Token")) setAuthLevel = s.GetAuthTokenLevel(request.Header.Get("Access-Token"))
} }
authOk := false
if setAuthLevel >= authLevel { if setAuthLevel >= authLevel {
authOk = true // 默认通过
if options != nil && options.Ext != nil && options.Ext["funcs"] != nil {
if needFuncs, ok := options.Ext["funcs"].([]string); ok && len(needFuncs) > 0 {
// 指定了细化的权限验证要求
request.Set("funcs", needFuncs)
authOk = session.authFuncs(needFuncs)
}
}
}
if authOk {
return true, session return true, session
}
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 { } else {
return false, msg 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
}
} }
}) })
s.Init() s.Init()
@ -202,49 +181,10 @@ func initConfig(opt *gojs.Obj, logger *log.Logger, vm *goja.Runtime) {
} }
} }
} }
if serviceConfig.HotLoad > 0 {
s.NewTimerServer("hotload", time.Duration(serviceConfig.HotLoad)*time.Second, func(b *bool) {
tmpPoolsMTimes := map[string]int64{}
poolsLock.RLock()
for actionFile, mtime := range poolsMTime {
tmpPoolsMTimes[actionFile] = mtime
}
poolsLock.RUnlock()
for actionFile, mtime := range tmpPoolsMTimes {
fi := u.GetFileInfo(actionFile)
if fi == nil {
continue
}
newMtime := fi.ModTime.Unix()
if newMtime > mtime {
// 重新加载文件
actionCode := u.ReadFileN(actionFile)
if !strings.Contains(actionCode, "function main(") { // || !strings.Contains(actionCode, ".register(")
logger.Error("hotload file %s failed, must be a js file with main function", actionFile)
continue
}
poolsLock.RLock()
oldOpt := poolsConfig[actionFile]
poolsLock.RUnlock()
p := gojs.NewPoolByCode(actionCode, actionFile, oldOpt, logger)
poolsLock.Lock()
pools[actionFile] = p
poolsMTime[actionFile] = newMtime
poolsLock.Unlock()
logger.Info("hotload file %s success", actionFile, "mtime", fi.ModTime)
}
}
}, nil, nil)
}
} }
func init() { func init() {
s.Config.KeepKeyCase = true s.Config.KeepKeyCase = true
s.DontStartLogAuto = true
obj := map[string]any{ obj := map[string]any{
"config": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { "config": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm) args := gojs.MakeArgs(&argsIn, vm)
@ -257,10 +197,7 @@ func init() {
// panic(vm.NewGoError(errors.New("must run service.config frist"))) // panic(vm.NewGoError(errors.New("must run service.config frist")))
} }
if server != nil { if server != nil {
// panic(vm.NewGoError(errors.New("server already started"))) panic(vm.NewGoError(errors.New("server already started")))
vm.SetData("_lastError", errors.New("server already started"))
gojs.GetLogger(vm).Error("server already started")
return vm.ToValue(false)
} }
// 处理静态文件 // 处理静态文件
if len(serviceConfig.Static) > 0 { if len(serviceConfig.Static) > 0 {
@ -276,10 +213,10 @@ func init() {
} }
// 处理Watch // 处理Watch
if inWatch := vm.GetData("inWatch"); inWatch != nil && inWatch.(bool) { if vm.GoData["inWatch"] == true {
onWatchConn := map[string]*websocket.Conn{} onWatchConn := map[string]*websocket.Conn{}
onWatchLock := sync.Mutex{} onWatchLock := sync.Mutex{}
vm.SetData("onWatch", func(filename string) { vm.GoData["onWatch"] = func(filename string) {
onWatchLock.Lock() onWatchLock.Lock()
defer onWatchLock.Unlock() defer onWatchLock.Unlock()
for id, conn := range onWatchConn { for id, conn := range onWatchConn {
@ -287,7 +224,7 @@ func init() {
delete(onWatchConn, id) delete(onWatchConn, id)
} }
} }
}) }
s.AddShutdownHook(func() { s.AddShutdownHook(func() {
for _, conn := range onWatchConn { for _, conn := range onWatchConn {
conn.Close() conn.Close()
@ -299,9 +236,8 @@ func init() {
onWatchLock.Unlock() onWatchLock.Unlock()
}, "") }, "")
s.SetOutFilter(func(in map[string]any, request *s.Request, response *s.Response, out any, logger *log.Logger) (newOut any, isOver bool) { s.SetOutFilter(func(in map[string]any, request *s.Request, response *s.Response, out any, logger *log.Logger) (newOut any, isOver bool) {
contentType := response.Header().Get("Content-Type") if strings.HasPrefix(response.Header().Get("Content-Type"), "text/html") {
outStr := u.String(out) outStr := u.String(out)
if strings.HasPrefix(contentType, "text/html") || (contentType == "" && strings.Contains(outStr, "<html")) {
if strings.Contains(outStr, "let _watchWS = null") { if strings.Contains(outStr, "let _watchWS = null") {
return nil, false return nil, false
} }
@ -371,131 +307,84 @@ func init() {
}, },
"stop": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { "stop": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
if server == nil { if server == nil {
// panic(vm.NewGoError(errors.New("server not started"))) panic(vm.NewGoError(errors.New("server not started")))
vm.SetData("_lastError", errors.New("server not started"))
gojs.GetLogger(vm).Error("server not started")
return vm.ToValue(false)
} }
server.Stop() server.Stop()
s.ResetAllSets() s.ResetAllSets()
return vm.ToValue(true) return nil
},
"uniqueId": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm)
size := args.Int(0)
var id string
if size >= 20 {
id = s.UniqueId20()
} else if size >= 16 {
id = s.UniqueId16()
} else if size >= 14 {
id = s.UniqueId14()
} else if size >= 12 {
id = s.UniqueId14()[0:12]
} else {
id = s.UniqueId()
}
return vm.ToValue(id)
},
"uniqueIdL": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm)
size := args.Int(0)
var id string
if size >= 20 {
id = s.UniqueId20()
} else if size >= 16 {
id = s.UniqueId16()
} else if size >= 14 {
id = s.UniqueId14()
} else if size >= 12 {
id = s.UniqueId14()[0:12]
} else {
id = s.UniqueId()
}
return vm.ToValue(strings.ToLower(id))
}, },
// "uniqueId": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm)
// size := args.Int(0)
// var id string
// if size >= 20 {
// id = s.UniqueId20()
// } else if size >= 16 {
// id = s.UniqueId16()
// } else if size >= 14 {
// id = s.UniqueId14()
// } else if size >= 12 {
// id = s.UniqueId14()[0:12]
// } else {
// id = s.UniqueId()
// }
// return vm.ToValue(id)
// },
// "uniqueIdL": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm)
// size := args.Int(0)
// var id string
// if size >= 20 {
// id = s.UniqueId20()
// } else if size >= 16 {
// id = s.UniqueId16()
// } else if size >= 14 {
// id = s.UniqueId14()
// } else if size >= 12 {
// id = s.UniqueId14()[0:12]
// } else {
// id = s.UniqueId()
// }
// return vm.ToValue(strings.ToLower(id))
// },
"id": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { "id": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1) args := gojs.MakeArgs(&argsIn, vm).Check(1)
size := args.Int(0) space := args.Str(0)
if size == 0 { size := args.Int(1)
size = 12 var id string
if size >= 12 {
id = s.Id12(space)
} else if size >= 10 {
id = s.Id10(space)
} else if size >= 8 {
id = s.Id8(space)
} else {
id = s.Id6(space)
} }
if size > 20 { return vm.ToValue(id)
size = 20 },
} "idL": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
rd := sessionRedis space := args.Str(0)
if rd == nil { size := args.Int(1)
// 使用u.Id var id string
return vm.ToValue(u.UniqueId()[0:size]) if size >= 12 {
} id = s.Id12(space)
} else if size >= 10 {
tm := time.Now() id = s.Id10(space)
// 计算从2000年开始到现在的索引值2000年时间戳9466560005位62进制最小值14776336可以表示901356495个即142.9年至2142年 } else if size >= 8 {
secIndex := (tm.Unix()-946656000)/5 + 14776336 id = s.Id8(space)
secTag := int(tm.UnixMicro() % 5) } else {
uid := u.AppendInt(nil, uint64(secIndex)) id = s.Id6(space)
secIndexKey := fmt.Sprintf("_SecIdx_%d", secIndex) }
inSecIndex := rd.INCR(secIndexKey) return vm.ToValue(strings.ToLower(id))
if inSecIndex == 1 {
rd.EXPIRE(secIndexKey, 6)
}
inSecIndexBytes := u.EncodeInt(uint64(inSecIndex))
uid = u.AppendInt(uid, uint64(secTag*12+len(inSecIndexBytes))) // 长度位,防止不同长度+随机值发生碰撞(用毫秒的%4*12位基础+位数可以随机表示12位长度看起来有变化
uid = append(uid, inSecIndexBytes...)
// 用随机数填充
uid = u.FillInt(uid, size)
// 交叉乱序
uid = u.ExchangeInt(uid)
// 散列乱序
uid = u.HashInt(uid)
return vm.ToValue(string(uid))
}, },
// "id": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm).Check(1)
// space := args.Str(0)
// size := args.Int(1)
// var id string
// if size >= 12 {
// id = s.Id12(space)
// } else if size >= 10 {
// id = s.Id10(space)
// } else if size >= 8 {
// id = s.Id8(space)
// } else {
// id = s.Id6(space)
// }
// return vm.ToValue(id)
// },
// "idL": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm).Check(1)
// space := args.Str(0)
// size := args.Int(1)
// var id string
// if size >= 12 {
// id = s.Id12(space)
// } else if size >= 10 {
// id = s.Id10(space)
// } else if size >= 8 {
// id = s.Id8(space)
// } else {
// id = s.Id6(space)
// }
// return vm.ToValue(strings.ToLower(id))
// },
"register": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { "register": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(2) args := gojs.MakeArgs(&argsIn, vm).Check(2)
o := args.Obj(0) o := args.Obj(0)
action := args.Func(1) action := args.Func(1)
if action == nil { if action == nil {
// panic(vm.NewGoError(errors.New("action must be a callback function"))) panic(vm.NewGoError(errors.New("action must be a callback function")))
vm.SetData("_lastError", errors.New("action must be a callback function"))
gojs.GetLogger(vm).Error("action must be a callback function")
return vm.ToValue(false)
} }
authLevel := o.Int("authLevel") authLevel := o.Int("authLevel")
@ -514,7 +403,7 @@ func init() {
for i, require := range requiresObj { for i, require := range requiresObj {
requires[i] = u.String(require) requires[i] = u.String(require)
} }
vm.SetData(fmt.Sprint("REQUIRE_"+host, method, path), requires) vm.GoData[fmt.Sprint("REQUIRE_"+host, method, path)] = requires
} }
if verifiesObj := o.Obj("verifies"); verifiesObj != nil { if verifiesObj := o.Obj("verifies"); verifiesObj != nil {
verifiesSet := map[string]func(any, *goja.Runtime) bool{} verifiesSet := map[string]func(any, *goja.Runtime) bool{}
@ -548,29 +437,29 @@ func init() {
} }
} }
} }
vm.SetData(fmt.Sprint("VERIFY_"+host, method, path), verifiesSet) vm.GoData[fmt.Sprint("VERIFY_"+host, method, path)] = verifiesSet
} }
opt := s.WebServiceOptions{ opt := s.WebServiceOptions{
NoBody: o.Bool("noBody"), NoBody: o.Bool("noBody"),
NoLog200: o.Bool("noLog200"), NoLog200: o.Bool("noLog200"),
Host: host, Host: host,
Ext: o.Map("ext"), //Ext: nil,
Limiters: usedLimiters, Limiters: usedLimiters,
} }
startFile := u.String(vm.GetData("startFile")) startFile := u.String(vm.GoData["startFile"])
poolsLock.RLock() poolsLock.RLock()
poolExist := poolExists[startFile] poolExist := poolExists[startFile]
poolsLock.RUnlock() poolsLock.RUnlock()
if poolExist { if poolExist {
// 从对象调用(支持并发) // 从对象调用(支持并发)
actionKey := "REGISTER_" + host + method + path actionKey := "REGISTER_" + host + method + path
vm.SetData(actionKey, action) vm.GoData[actionKey] = action
vm.SetData(actionKey+"This", args.This) vm.GoData[actionKey+"This"] = args.This
if method == "WS" { if method == "WS" {
vm.SetData(actionKey+"onMessage", o.Func("onMessage")) vm.GoData[actionKey+"onMessage"] = o.Func("onMessage")
vm.SetData(actionKey+"onClose", o.Func("onClose")) vm.GoData[actionKey+"onClose"] = o.Func("onClose")
} }
poolsLock.Lock() poolsLock.Lock()
actionRegistered := poolActionRegistered[actionKey] actionRegistered := poolActionRegistered[actionKey]
@ -589,7 +478,7 @@ func init() {
// 无对象池,直接调用(单线程) // 无对象池,直接调用(单线程)
s.RestfulWithOptions(authLevel, method, path, makeInnerAction(action, vm, args.This), memo, opt) s.RestfulWithOptions(authLevel, method, path, makeInnerAction(action, vm, args.This), memo, opt)
} }
return vm.ToValue(true) return nil
}, },
"load": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { "load": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1) args := gojs.MakeArgs(&argsIn, vm).Check(1)
@ -609,146 +498,104 @@ func init() {
mainArgs = mainArgs1 mainArgs = mainArgs1
} }
} }
fi := u.GetFileInfo(actionFile) if !u.FileExists(actionFile) {
if fi == nil {
fullActionFile, _ := filepath.Abs(actionFile) fullActionFile, _ := filepath.Abs(actionFile)
// panic(vm.NewGoError(errors.New("actionFile must be a js file path: " + fullActionFile))) panic(vm.NewGoError(errors.New("actionFile must be a js file path: " + fullActionFile)))
vm.SetData("_lastError", errors.New("actionFile must be a js file path: "+fullActionFile))
gojs.GetLogger(vm).Error("actionFile must be a js file path: " + fullActionFile)
return vm.ToValue(false)
} }
actionCode := u.ReadFileN(actionFile) actionCode := u.ReadFileN(actionFile)
if !strings.Contains(actionCode, "function main(") { // || !strings.Contains(actionCode, ".register(") if !strings.Contains(actionCode, "function main(") || !strings.Contains(actionCode, ".register(") {
// panic(vm.NewGoError(errors.New("actionFile must be a js file with main function"))) panic(vm.NewGoError(errors.New("actionFile must be a js file with main function and call service.register")))
vm.SetData("_lastError", errors.New("actionFile must be a js file with main function"))
gojs.GetLogger(vm).Error("actionFile must be a js file with main function")
return vm.ToValue(false)
} }
poolsLock.Lock() poolsLock.Lock()
poolExists[actionFile] = true poolExists[actionFile] = true
poolsLock.Unlock() poolsLock.Unlock()
poolOpt := gojs.PoolConfig{ p := gojs.NewPoolByCode(actionCode, actionFile, gojs.PoolConfig{
Min: mi, Min: mi,
Max: ma, Max: ma,
Idle: idle, Idle: idle,
Debug: debug, Debug: debug,
Args: mainArgs, Args: mainArgs,
} }, args.Logger)
p := gojs.NewPoolByCode(actionCode, actionFile, poolOpt, args.Logger)
//p := gojs.NewLBByCode(actionCode, actionFile, gojs.LBConfig{ //p := gojs.NewLBByCode(actionCode, actionFile, gojs.LBConfig{
// Num: num, // Num: num,
// Debug: debug, // Debug: debug,
// Args: mainArgs, // Args: mainArgs,
//}, args.Logger) //}, args.Logger)
mtime := fi.ModTime.Unix()
poolsLock.Lock() poolsLock.Lock()
pools[actionFile] = p pools[actionFile] = p
poolsMTime[actionFile] = mtime
poolsConfig[actionFile] = poolOpt
poolsLock.Unlock() poolsLock.Unlock()
return vm.ToValue(true) return nil
}, },
// "task": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { "task": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm).Check(1)
// taskFile := args.Path(0)
// interval := args.Int(1)
// if interval == 0 {
// interval = 1000
// }
// if interval < 100 {
// interval = 100
// }
// if !u.FileExists(taskFile) {
// panic(vm.NewGoError(errors.New("taskFile must be a js file path")))
// }
// rt := gojs.New()
// _, err := rt.RunFile(taskFile)
// if err != nil {
// panic(vm.NewGoError(err))
// }
// s.NewTimerServer(taskFile, time.Duration(interval)*time.Millisecond, func(isRunning *bool) {
// rt.RunCode("if(onRun)onRun()")
// }, func() {
// rt.RunCode("if(onStart)onStart()")
// }, func() {
// rt.RunCode("if(onStop)onStop()")
// })
// return nil
// },
// "setTplFunc": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm).Check(1)
// fnObj := args.Obj(0)
// if fnObj != nil {
// fnList := map[string]any{}
// for _, k := range fnObj.O.Keys() {
// if jsFunc := fnObj.Func(k); jsFunc != nil {
// fn := func(args ...any) any {
// jsArgs := make([]goja.Value, len(args))
// for i := 0; i < len(args); i++ {
// jsArgs[i] = vm.ToValue(args[i])
// }
// // vm.CallbackLocker.Lock()
// r, err := jsFunc(argsIn.This, jsArgs...)
// // vm.CallbackLocker.Unlock()
// if err == nil {
// return r.Export()
// } else {
// panic(vm.NewGoError(err))
// }
// }
// fnList[k] = fn
// }
// }
// if len(fnList) > 0 {
// tplLock.Lock()
// for k, v := range fnList {
// tplFunc[k] = v
// }
// tplLock.Unlock()
// }
// }
// return nil
// },
"setTplReplaces": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1) args := gojs.MakeArgs(&argsIn, vm).Check(1)
replaces := args.Map(0) taskFile := args.Path(0)
tplLock.Lock() interval := args.Int(1)
for k, v := range replaces { if interval == 0 {
tplReplaces[k] = u.String(v) interval = 1000
}
if interval < 100 {
interval = 100
}
if !u.FileExists(taskFile) {
panic(vm.NewGoError(errors.New("taskFile must be a js file path")))
}
rt := gojs.New()
_, err := rt.RunFile(taskFile)
if err != nil {
panic(vm.NewGoError(err))
}
s.NewTimerServer(taskFile, time.Duration(interval)*time.Millisecond, func(isRunning *bool) {
rt.RunCode("if(onRun)onRun()")
}, func() {
rt.RunCode("if(onStart)onStart()")
}, func() {
rt.RunCode("if(onStop)onStop()")
})
return nil
},
"setTplFunc": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
fnObj := args.Obj(0)
if fnObj != nil {
fnList := map[string]any{}
for _, k := range fnObj.O.Keys() {
if jsFunc := fnObj.Func(k); jsFunc != nil {
fn := func(args ...any) any {
jsArgs := make([]goja.Value, len(args))
for i := 0; i < len(args); i++ {
jsArgs[i] = vm.ToValue(args[i])
}
if r, err := jsFunc(argsIn.This, jsArgs...); err == nil {
return r.Export()
} else {
panic(vm.NewGoError(err))
}
}
fnList[k] = fn
}
}
if len(fnList) > 0 {
tplFuncLock.Lock()
for k, v := range fnList {
tplFunc[k] = v
}
tplFuncLock.Unlock()
}
} }
tplLock.Unlock()
return nil return nil
}, },
"tpl": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { "tpl": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(2) args := gojs.MakeArgs(&argsIn, vm).Check(2)
filename := args.Path(0) filename := args.Path(0)
if !tplIsSafe(filename) {
// panic(vm.NewGoError(errors.New("tpl file " + filename + " not in safe paths")))
vm.SetData("_lastError", errors.New("tpl file "+filename+" not in safe paths"))
gojs.GetLogger(vm).Error("tpl file " + filename + " not in safe paths")
return nil
}
basepath := filepath.Dir(filename)
info := u.GetFileInfo(filename) info := u.GetFileInfo(filename)
if info == nil { if info == nil {
// panic(vm.NewGoError(errors.New("tpl file " + filename + " not exists"))) panic(vm.NewGoError(errors.New("tpl file " + filename + " not exists")))
vm.SetData("_lastError", errors.New("tpl file "+filename+" not exists"))
gojs.GetLogger(vm).Error("tpl file " + filename + " not exists")
return nil
} }
data := args.Any(1) data := args.Any(1)
// tplLock.RLock() tplCacheLock.RLock()
// t := tplCache[filename] t := tplCache[filename]
var t *TplCache tplCacheLock.RUnlock()
if t1 := vm.GetData("TPL_" + filename); t1 != nil {
if t2, ok := t1.(*TplCache); ok {
t = t2
}
}
// t := vm.GetData("TPL_" + filename).(*TplCache)
// tplLock.RUnlock()
if t != nil { if t != nil {
for f, tm := range t.FileModTime { for f, tm := range t.FileModTime {
info := u.GetFileInfo(f) info := u.GetFileInfo(f)
@ -759,124 +606,58 @@ func init() {
} }
} }
if t == nil { if t == nil {
tpl := template.New(filename) tpl := template.New("main")
fnList := map[string]any{} if len(tplFunc) > 0 {
if fnObj := args.Obj(2); fnObj != nil { tpl = tpl.Funcs(tplFunc)
for _, k := range fnObj.O.Keys() {
if jsFunc := fnObj.Func(k); jsFunc != nil {
fn := func(args ...any) any {
jsArgs := make([]goja.Value, len(args))
for i := 0; i < len(args); i++ {
jsArgs[i] = vm.ToValue(args[i])
}
r, err := jsFunc(argsIn.This, jsArgs...)
if err == nil {
return r.Export()
} else {
// panic(vm.NewGoError(err))
vm.SetData("_lastError", err)
gojs.GetLogger(vm).Error(err.Error())
return nil
}
}
fnList[k] = fn
}
}
}
if len(fnList) > 0 {
tpl = tpl.Funcs(fnList)
} }
fileModTime := map[string]int64{ fileModTime := map[string]int64{
filename: info.ModTime.UnixMilli(), filename: info.ModTime.UnixMilli(),
} }
var err error var err error
code := tplReplace(u.ReadFileN(filename)) for _, m := range tplIncludeMatcher.FindAllStringSubmatch(u.ReadFileN(filename), -1) {
for _, m := range tplIncludeMatcher.FindAllStringSubmatch(code, -1) { includeFilename := m[1]
a := strings.SplitN(m[1], ":", 2) info2 := u.GetFileInfo(includeFilename)
includeFilename := a[0] if info2 == nil {
includeFilepath := filepath.Join(basepath, includeFilename) includeFilename = filepath.Join(filepath.Dir(filename), m[1])
if !tplIsSafe(includeFilepath) { info2 = u.GetFileInfo(includeFilename)
// panic(vm.NewGoError(errors.New("tpl file " + includeFilepath + " not in safe paths")))
vm.SetData("_lastError", errors.New("tpl file "+includeFilepath+" not in safe paths"))
gojs.GetLogger(vm).Error("tpl file " + includeFilepath + " not in safe paths")
return nil
} }
if info2 != nil {
// 每个被包含的文件只解析一次(统一修正) tpl, err = tpl.Parse(`{{ define "` + m[1] + `" }}` + u.ReadFileN(includeFilename) + `{{ end }}`)
if fileModTime[includeFilepath] == 0 { if err != nil {
info2 := u.GetFileInfo(includeFilepath) panic(vm.NewGoError(err))
if info2 != nil {
includeCode := tplReplace(u.ReadFileN(includeFilepath))
foundBlock := false
includeCode = tplDefineMatcher.ReplaceAllStringFunc(includeCode, func(str string) string {
if m := tplDefineMatcher.FindStringSubmatch(str); len(m) > 1 {
foundBlock = true
if m[1] == "main" {
return `{{ define "` + includeFilename + `" }}`
}
return `{{ define "` + includeFilename + ":" + m[1] + `" }}`
}
return str
})
if !foundBlock {
includeCode = `{{ define "` + includeFilename + `" }}` + includeCode + `{{ end }}`
}
tpl, err = tpl.Parse(includeCode)
if err != nil {
// panic(vm.NewGoError(err))
vm.SetData("_lastError", err)
gojs.GetLogger(vm).Error(err.Error())
return nil
}
fileModTime[includeFilepath] = info2.ModTime.UnixMilli()
} else {
// panic(vm.NewGoError(errors.New("tpl file " + includeFilepath + " not exists")))
vm.SetData("_lastError", errors.New("tpl file "+includeFilepath+" not exists"))
gojs.GetLogger(vm).Error("tpl file " + includeFilepath + " not exists")
return nil
} }
fileModTime[includeFilename] = info2.ModTime.UnixMilli()
} }
} }
tpl, err = tpl.ParseFiles(filename)
tpl, err = tpl.Parse(code)
if err != nil { if err != nil {
// panic(vm.NewGoError(err)) panic(vm.NewGoError(err))
vm.SetData("_lastError", err)
gojs.GetLogger(vm).Error(err.Error())
return nil
} }
t = &TplCache{ t = &TplCache{
Tpl: tpl, Tpl: tpl,
FileModTime: fileModTime, FileModTime: fileModTime,
} }
// tplLock.Lock()
// tplCache[filename] = t
// tplLock.Unlock()
vm.SetData("TPL_"+filename, t)
} }
buf := bytes.NewBuffer(make([]byte, 0)) buf := bytes.NewBuffer(make([]byte, 0))
err := t.Tpl.ExecuteTemplate(buf, filename, data) err := t.Tpl.ExecuteTemplate(buf, filepath.Base(filename), data)
if err != nil { if err != nil {
// panic(vm.NewGoError(err)) panic(vm.NewGoError(err))
vm.SetData("_lastError", err)
gojs.GetLogger(vm).Error(err.Error())
return nil
} }
return vm.ToValue(buf.String()) return vm.ToValue(buf.String())
}, },
// "dataSet": DataSet, "dataSet": DataSet,
// "dataGet": DataGet, "dataGet": DataGet,
// "dataKeys": DataKeys, "dataKeys": DataKeys,
// "dataCount": DataCount, "dataCount": DataCount,
// "dataFetch": DataFetch, "dataFetch": DataFetch,
// "dataRemove": DataRemove, "dataRemove": DataRemove,
// "listPop": ListPop, "listPop": ListPop,
// "listPush": ListPush, "listPush": ListPush,
// "listCount": ListCount, "listCount": ListCount,
// "listRemove": ListRemove, "listRemove": ListRemove,
"newCaller": NewCaller, "newCaller": NewCaller,
} }
gojs.Register("apigo.cc/gojs/service", gojs.Module{ gojs.Register("apigo.cc/gojs/service", gojs.Module{
@ -902,53 +683,10 @@ func init() {
}) })
} }
var regexpCache = map[string]*regexp.Regexp{}
var regexpLock = sync.RWMutex{}
func getRegexp(regexpStr string) (*regexp.Regexp, error) {
regexpLock.RLock()
rx, ok := regexpCache[regexpStr]
regexpLock.RUnlock()
if ok {
return rx, nil
}
rx, err := regexp.Compile(regexpStr)
if err == nil {
regexpLock.Lock()
defer regexpLock.Unlock()
regexpCache[regexpStr] = rx
}
return rx, err
}
func tplReplace(code string) string {
for k, v := range tplReplaces {
if rx, err := getRegexp(k); err == nil {
code = rx.ReplaceAllString(code, v)
}
}
return code
}
func tplIsSafe(filename string) bool {
if len(serviceConfig.TplSafePaths) > 0 {
isSafe := false
for _, safePath := range serviceConfig.TplSafePaths {
if strings.HasPrefix(filename, safePath) {
isSafe = true
break
}
}
return isSafe
}
return true
}
var tplIncludeMatcher = regexp.MustCompile(`{{\s*template\s+"([^"]+)"`) var tplIncludeMatcher = regexp.MustCompile(`{{\s*template\s+"([^"]+)"`)
var tplDefineMatcher = regexp.MustCompile(`{{\s*define\s+"([^"]+)"\s*}}`)
func verifyRegexp(regexpStr string) func(any, *goja.Runtime) bool { func verifyRegexp(regexpStr string) func(any, *goja.Runtime) bool {
if rx, err := getRegexp(regexpStr); err != nil { if rx, err := regexp.Compile(regexpStr); err != nil {
return func(value any, vm *goja.Runtime) bool { return func(value any, vm *goja.Runtime) bool {
return rx.MatchString(u.String(value)) return rx.MatchString(u.String(value))
} }
@ -1034,10 +772,10 @@ func runAction(action goja.Callable, vm *goja.Runtime, thisArg goja.Value, args
defer vm.CallbackLocker.Unlock() defer vm.CallbackLocker.Unlock()
// 验证请求参数的有效性 // 验证请求参数的有效性
if verifies, ok := vm.GetData("VERIFY_" + u.String(request.Get("registerTag"))).(map[string]func(any, *goja.Runtime) bool); ok { if verifies, ok := vm.GoData["VERIFY_"+u.String(request.Get("registerTag"))].(map[string]func(any, *goja.Runtime) bool); ok {
failedFields := make([]string, 0) failedFields := make([]string, 0)
// 检查必填字段 // 检查必填字段
if requires, ok := vm.GetData("REQUIRE_" + u.String(request.Get("registerTag"))).([]string); ok { if requires, ok := vm.GoData["REQUIRE_"+u.String(request.Get("registerTag"))].([]string); ok {
for _, requireField := range requires { for _, requireField := range requires {
if _, ok := args[requireField]; !ok { if _, ok := args[requireField]; !ok {
failedFields = append(failedFields, requireField) failedFields = append(failedFields, requireField)
@ -1068,15 +806,6 @@ func runAction(action goja.Callable, vm *goja.Runtime, thisArg goja.Value, args
} }
} }
// force set userId to vm
vmUserId := "_guest"
if session != nil {
if userId, ok := session.data[serviceConfig.UserIdKey]; ok {
vmUserId = u.String(userId)
}
}
vm.SetData("userId", vmUserId)
requestParams, resp := makeRequestParams(args, headers, request, response, client, caller, session, logger) requestParams, resp := makeRequestParams(args, headers, request, response, client, caller, session, logger)
var r any var r any

View File

@ -1,30 +1,29 @@
// just for develop // just for develop
export default { export default {
config, config,
start, start,
stop, stop,
register, register,
load, load,
// task, task,
newCaller, newCaller,
// dataSet, dataSet,
// dataGet, dataGet,
// dataKeys, dataKeys,
// dataCount, dataCount,
// dataFetch, dataFetch,
// dataRemove, dataRemove,
// listPush, listPush,
// listPop, listPop,
// listCount, listCount,
// listRemove, listRemove,
id, id,
// idL, idL,
// uniqueId, uniqueId,
// uniqueIdL, uniqueIdL,
// setTplFunc, setTplFunc,
setTplReplaces, tpl,
tpl,
} }
function config(config?: Config): void { } function config(config?: Config): void { }
@ -33,264 +32,259 @@ function stop(): void { }
function register(option: RegisterOption, callback: (params: RequestParams) => void): any { return null } function register(option: RegisterOption, callback: (params: RequestParams) => void): any { return null }
function load(serviceFile: string, poolConfig?: PoolConfig): void { } function load(serviceFile: string, poolConfig?: PoolConfig): void { }
// function task(taskFile: string, interval: number = 1000): void { } function task(taskFile: string, interval: number = 1000): void { }
function newCaller(): Caller { return null as any } function newCaller(): Caller { return null as any }
// function dataSet(scope: string, key: string, value: any): void { } function dataSet(scope: string, key: string, value: any): void { }
// function dataGet(scope: string, key: string): any { return null } function dataGet(scope: string, key: string): any { return null }
// function dataKeys(scope: string): string[] { return [] } function dataKeys(scope: string): string[] { return [] }
// function dataCount(scope: string): number { return 0 } function dataCount(scope: string): number { return 0 }
// function dataFetch(scope: string): Map<string, any> { return null as any } function dataFetch(scope: string): Map<string, any> { return null as any }
// function dataRemove(scope: string, key?: string): void { } function dataRemove(scope: string, key?: string): void { }
// function listPush(scope: string, key: string, value: any): void { } function listPush(scope: string, key: string, value: any): void { }
// function listPop(scope: string, key: string): any { return null } function listPop(scope: string, key: string): any { return null }
// function listCount(scope: string): number { return 0 } function listCount(scope: string): number { return 0 }
// function listRemove(scope: string): void { } function listRemove(scope: string): void { }
function id(size?: number): string { return '' } function id(space: string, size?: number): string { return '' }
// function id(space: string, size?: number): string { return '' } function idL(space: string, size?: number): string { return '' }
// function idL(space: string, size?: number): string { return '' } function uniqueId(size?: number): string { return '' }
// function uniqueId(size?: number): string { return '' } function uniqueIdL(size?: number): string { return '' }
// function uniqueIdL(size?: number): string { return '' } function setTplFunc(fnList: Object): void { }
// function setTplFunc(fnList: Object): void { } function tpl(file: string, data: Object): string { return '' }
function setTplReplaces(replaces: Object): void { }
function tpl(file: string, data: Object, fnList?: Object): string { return '' }
export interface Config { interface Config {
// github.com/ssgo/s 的配置参数 // github.com/ssgo/s 的配置参数
listen: string // 监听端口(|隔开多个监听)(,隔开多个选项如果不指定IP则监听在0.0.0.0如果不指定端口则使用h2c协议监听在随机端口80端口默认使用http协议443端口默认使用https协议例如 80,http|443|443:h2|127.0.0.1:8080,h2c listen: string // 监听端口(|隔开多个监听)(,隔开多个选项如果不指定IP则监听在0.0.0.0如果不指定端口则使用h2c协议监听在随机端口80端口默认使用http协议443端口默认使用https协议例如 80,http|443|443:h2|127.0.0.1:8080,h2c
ssl: Map<string, CertSet> // SSL证书配置key为域名value为cert和key的文件路径 ssl: Map<string, CertSet> // SSL证书配置key为域名value为cert和key的文件路径
noLogGets: boolean // 不记录GET请求的日志 noLogGets: boolean // 不记录GET请求的日志
noLogHeaders: string // 不记录请求头中包含的这些字段多个字段用逗号分隔默认不记录Accept,Accept-Encoding,Cache-Control,Pragma,Connection,Upgrade-Insecure-Requests noLogHeaders: string // 不记录请求头中包含的这些字段多个字段用逗号分隔默认不记录Accept,Accept-Encoding,Cache-Control,Pragma,Connection,Upgrade-Insecure-Requests
logInputArrayNum: number // 请求字段中容器类型数组、Map在日志打印个数限制 默认为10个多余的数据将不再日志中记录 logInputArrayNum: number // 请求字段中容器类型数组、Map在日志打印个数限制 默认为10个多余的数据将不再日志中记录
logInputFieldSize: number // 请求字段中单个字段在日志打印长度限制 默认为500个字符多余的数据将不再日志中记录 logInputFieldSize: number // 请求字段中单个字段在日志打印长度限制 默认为500个字符多余的数据将不再日志中记录
noLogOutputFields: string // 不记录响应字段中包含的这些字段key名多个字段用逗号分隔 noLogOutputFields: string // 不记录响应字段中包含的这些字段key名多个字段用逗号分隔
logOutputArrayNum: number // 响应字段中容器类型数组、Map在日志打印个数限制 默认为3个多余的数据将不再日志中记录 logOutputArrayNum: number // 响应字段中容器类型数组、Map在日志打印个数限制 默认为3个多余的数据将不再日志中记录
logOutputFieldSize: number // 响应字段中单个字段在日志打印长度限制 默认为100个字符多余的数据将不再日志中记录 logOutputFieldSize: number // 响应字段中单个字段在日志打印长度限制 默认为100个字符多余的数据将不再日志中记录
logWebsocketAction: boolean // 记录Websocket中每个Action的请求日志默认不记录 logWebsocketAction: boolean // 记录Websocket中每个Action的请求日志默认不记录
compress: boolean // 是否启用压缩,默认不启用 compress: boolean // 是否启用压缩,默认不启用
compressMinSize: number // 小于设定值的应答内容将不进行压缩默认值1024 compressMinSize: number // 小于设定值的应答内容将不进行压缩默认值1024
compressMaxSize: number // 大于设定值的应答内容将不进行压缩默认值4096000 compressMaxSize: number // 大于设定值的应答内容将不进行压缩默认值4096000
checkDomain: string // 心跳检测时使用域名默认使用IP地址心跳检测使用 HEAD /__CHECK__ 请求,应答 299 表示正常593 表示异常 checkDomain: string // 心跳检测时使用域名默认使用IP地址心跳检测使用 HEAD /__CHECK__ 请求,应答 299 表示正常593 表示异常
redirectTimeout: number // proxy和discover发起请求时的超时时间单位ms默认值10000 redirectTimeout: number // proxy和discover发起请求时的超时时间单位ms默认值10000
acceptXRealIpWithoutRequestId: boolean // 是否允许头部没有携带请求ID的X-Real-IP信息默认不允许防止伪造客户端IP acceptXRealIpWithoutRequestId: boolean // 是否允许头部没有携带请求ID的X-Real-IP信息默认不允许防止伪造客户端IP
statisticTime: boolean // 是否开启请求时间统计,默认不开启 statisticTime: boolean // 是否开启请求时间统计,默认不开启
statisticTimeInterval: number // 统计时间间隔单位ms默认值10000 statisticTimeInterval: number // 统计时间间隔单位ms默认值10000
fast: boolean // 是否启用快速模式(为了追求性能牺牲一部分特性),默认不启用 fast: boolean // 是否启用快速模式(为了追求性能牺牲一部分特性),默认不启用
maxUploadSize: number // 最大上传文件大小multipart/form-data请求的总空间单位字节默认值104857600 maxUploadSize: number // 最大上传文件大小multipart/form-data请求的总空间单位字节默认值104857600
cpu: number // CPU占用的核数默认为0即不做限制 cpu: number // CPU占用的核数默认为0即不做限制
memory: number // 内存单位M默认为0即不做限制 memory: number // 内存单位M默认为0即不做限制
cpuMonitor: boolean // 在日志中记录CPU使用情况默认不开启 cpuMonitor: boolean // 在日志中记录CPU使用情况默认不开启
memoryMonitor: boolean // 在日志中记录内存使用情况,默认不开启 memoryMonitor: boolean // 在日志中记录内存使用情况,默认不开启
cpuLimitValue: number // CPU超过最高占用值10-100超过次数将自动重启如果CpuMonitor开启的话默认100 cpuLimitValue: number // CPU超过最高占用值10-100超过次数将自动重启如果CpuMonitor开启的话默认100
memoryLimitValue: number // 内存超过最高占用值10-100超过次数将自动重启如果MemoryMonitor开启的话默认95 memoryLimitValue: number // 内存超过最高占用值10-100超过次数将自动重启如果MemoryMonitor开启的话默认95
cpuLimitTimes: number // CPU超过最高占用值超过次数1-100将报警如果CpuMonitor开启的话默认6即30秒内连续6次 cpuLimitTimes: number // CPU超过最高占用值超过次数1-100将报警如果CpuMonitor开启的话默认6即30秒内连续6次
memoryLimitTimes: number // 内存超过最高占用值超过次数1-100将报警如果MemoryMonitor开启的话默认6即30秒内连续6次 memoryLimitTimes: number // 内存超过最高占用值超过次数1-100将报警如果MemoryMonitor开启的话默认6即30秒内连续6次
cookieScope: string // 启用Session时Cookie的有效范围host|domain|topDomain默认值为host cookieScope: string // 启用Session时Cookie的有效范围host|domain|topDomain默认值为host
sessionWithoutCookie: boolean // Session禁用Cookie保持默认使用Cookie sessionWithoutCookie: boolean // Session禁用Cookie保持默认使用Cookie
deviceWithoutCookie: boolean // 设备ID禁用Cookie保持默认使用Cookie deviceWithoutCookie: boolean // 设备ID禁用Cookie保持默认使用Cookie
idServer: string // 用s.UniqueId、s.Id来生成唯一ID雪花算法时所需的redis服务器连接如果不指定将不能实现跨服务的全局唯一 idServer: string // 用s.UniqueId、s.Id来生成唯一ID雪花算法时所需的redis服务器连接如果不指定将不能实现跨服务的全局唯一
keepKeyCase: boolean // 是否保持Key的首字母大小写默认保持设置为false则自动将首字母转为小写 keepKeyCase: boolean // 是否保持Key的首字母大小写默认保持设置为false则自动将首字母转为小写
indexFiles: string[] // 访问静态文件时的索引文件,默认为 index.html indexFiles: string[] // 访问静态文件时的索引文件,默认为 index.html
indexDir: boolean // 访问目录时显示文件列表 indexDir: boolean // 访问目录时显示文件列表
readTimeout: number // 读取请求的超时时间单位ms readTimeout: number // 读取请求的超时时间单位ms
readHeaderTimeout: number // 读取请求头的超时时间单位ms readHeaderTimeout: number // 读取请求头的超时时间单位ms
writeTimeout: number // 响应写入的超时时间单位ms writeTimeout: number // 响应写入的超时时间单位ms
idleTimeout: number // 连接空闲超时时间单位ms idleTimeout: number // 连接空闲超时时间单位ms
maxHeaderBytes: number // 请求头的最大字节数 maxHeaderBytes: number // 请求头的最大字节数
maxHandlers: number // 每个连接的最大处理程序数量 maxHandlers: number // 每个连接的最大处理程序数量
maxConcurrentStreams: number // 每个连接的最大并发流数量 maxConcurrentStreams: number // 每个连接的最大并发流数量
maxDecoderHeaderTableSize: number // 解码器头表的最大大小 maxDecoderHeaderTableSize: number // 解码器头表的最大大小
maxEncoderHeaderTableSize: number // 编码器头表的最大大小 maxEncoderHeaderTableSize: number // 编码器头表的最大大小
maxReadFrameSize: number // 单个帧的最大读取大小 maxReadFrameSize: number // 单个帧的最大读取大小
maxUploadBufferPerConnection: number // 每个连接的最大上传缓冲区大小 maxUploadBufferPerConnection: number // 每个连接的最大上传缓冲区大小
maxUploadBufferPerStream: number // 每个流的最大上传缓冲区大小 maxUploadBufferPerStream: number // 每个流的最大上传缓冲区大小
// 其他配置 // 其他配置
sessionKey: string // HTTP头和Cookie中SessionID的Key客户端没有传递时服务端自动生成Header的优先级高于Cookie默认为 Session, 设置为空时表示不使用 sessionKey: string // HTTP头和Cookie中SessionID的Key客户端没有传递时服务端自动生成Header的优先级高于Cookie默认为 Session, 设置为空时表示不使用
deviceKey: string // 标识设备ID的Key客户端没有传递时服务端自动生成Header的优先级高于Cookie默认为 Device 设置为空时表示不使用 deviceKey: string // 标识设备ID的Key客户端没有传递时服务端自动生成Header的优先级高于Cookie默认为 Device 设置为空时表示不使用
clientKey: string // 标识客户端的Key默认为 Client对应的Header头为 ClientName 和 ClientVersion clientKey: string // 标识客户端的Key默认为 Client对应的Header头为 ClientName 和 ClientVersion
userIdKey: string // session中userID的Key用于在日志中记录用户ID信息默认为 id userIdKey: string // session中userID的Key用于在日志中记录用户ID信息默认为 id
sessionProvider: string // 指定一个redis连接例如redis://:sskey加密的密码@127.0.0.1:6379/15默认使用内存存储 sessionProvider: string // 指定一个redis连接例如redis://:sskey加密的密码@127.0.0.1:6379/15默认使用内存存储
sessionTimeout: number // session过期时间单位 秒,默认为 3600秒 sessionTimeout: number // session过期时间单位 秒,默认为 3600秒
authFieldMessage: string | Object // 身份验证失败时的消息,默认为 auth failed可以设置对象来返回JSON可以使用模版 {{TARGET_AUTHLEVEL}}、{{USER_AUTHLEVEL}} authFieldMessage: string | Object // 身份验证失败时的消息,默认为 auth failed可以设置对象来返回JSON可以使用模版 {{TARGET_AUTHLEVEL}}、{{USER_AUTHLEVEL}}
verifyFieldMessage: string | Object // 参数验证失败时的消息,默认为 verify failed可以设置对象来返回JSON可以使用模版 {{FAILED_FIELDS}} verifyFieldMessage: string | Object // 参数验证失败时的消息,默认为 verify failed可以设置对象来返回JSON可以使用模版 {{FAILED_FIELDS}}
limitedMessage: string | Object // 访问受限时的消息,默认为 too many requests可以设置对象来返回JSON可以使用模版 {{LIMITED_FROM}}、{{LIMITED_VALUE}} limitedMessage: string | Object // 访问受限时的消息,默认为 too many requests可以设置对象来返回JSON可以使用模版 {{LIMITED_FROM}}、{{LIMITED_VALUE}}
limiterRedis: string // 限流器使用的Redis连接默认使用内存存储 limiterRedis: string // 限流器使用的Redis连接默认使用内存存储
limiters: Map<string, LimiterConfig> // 限流器配置from 为数据来源例如ip、user、device、header.User-Agent、in.phone 等in表示从请求参数中获取time为时间间隔单位mstimes为 时间间隔内允许访问的次数 limiters: Map<string, LimiterConfig> // 限流器配置from 为数据来源例如ip、user、device、header.User-Agent、in.phone 等in表示从请求参数中获取time为时间间隔单位mstimes为 时间间隔内允许访问的次数
hotLoad: number // 热加载配置单位s默认值00表示不检测热加载
tplSafePaths: string[] // 模板文件的安全路径,默认不开启安全检查
// gateway 的配置参数 // gateway 的配置参数
proxy: Map<string, string> // 代理配置key为[host][path]value为代理的目标应用或URL proxy: Map<string, string> // 代理配置key为[host][path]value为代理的目标应用或URL
rewrite: Map<string, string> // 重写请求路径key为[host][path]value为重写后的路径 rewrite: Map<string, string> // 重写请求路径key为[host][path]value为重写后的路径
static: Map<string, string> // 静态文件目录key为[host][path]value为目录路径 static: Map<string, string> // 静态文件目录key为[host][path]value为目录路径
// github.com/ssgo/discover 的配置参数 // github.com/ssgo/discover 的配置参数
registry: string // 服务注册中心请配置一个有效的RedisURL默认值 redis://:@127.0.0.1:6379/15 registry: string // 服务注册中心请配置一个有效的RedisURL默认值 redis://:@127.0.0.1:6379/15
app: string // 设置该值将会自动将服务注册到 discover app: string // 设置该值将会自动将服务注册到 discover
weight: number // 节点的权重,默认值 100 weight: number // 节点的权重,默认值 100
accessTokens: Map<string, number> // 请求接口时使用指定的Access-Token进行验证值为Token对应的authLevel允许访问大于等于指定对应authLevel的接口 accessTokens: Map<string, number> // 请求接口时使用指定的Access-Token进行验证值为Token对应的authLevel允许访问大于等于指定对应authLevel的接口
calls: Map<string, string> // 配置将会调用的服务,格式:[1|2]:[http|https|h2c]:[AccessToken]默认协议为h2c如使用h2c协议可只提供 AccessToken calls: Map<string, string> // 配置将会调用的服务,格式:[1|2]:[http|https|h2c]:[AccessToken]默认协议为h2c如使用h2c协议可只提供 AccessToken
callRetryTimes: number // 调用其他服务时最大重试次数,默认值 10 callRetryTimes: number // 调用其他服务时最大重试次数,默认值 10
ipPrefix: string // discover服务发现时指定使用的IP网段默认排除 172.17.Docker ipPrefix: string // discover服务发现时指定使用的IP网段默认排除 172.17.Docker
} }
export interface CertSet { interface CertSet {
certFile: string certFile: string
keyFile: string keyFile: string
} }
export interface PoolConfig { interface PoolConfig {
min: number min: number
max: string max: string
idle: number idle: number
} }
export interface LimiterConfig { interface LimiterConfig {
from: string from: string
time: number time: number
times: number times: number
} }
export interface RegisterOption { interface RegisterOption {
authLevel: number authLevel: number
host: string host: string
method: string method: string
path: string path: string
memo: string memo: string
noBody: boolean noBody: boolean
noLog200: boolean noLog200: boolean
limiters: string[] limiters: string[]
verifies: Object verifies: Object
requires: string[] requires: string[]
ext: Object onMessage: (params: OnMessageParams) => void
onMessage: (params: OnMessageParams) => void onClose: (params: RequestParams) => void
onClose: (params: RequestParams) => void
} }
export interface RequestParams { interface RequestParams {
args: Object args: Object
headers: Object headers: Object
request: Request request: Request
client: WSClient client: WSClient
caller: Caller caller: Caller
session: Session session: Session
response: Response response: Response
logger: Logger logger: Logger
} }
export interface OnMessageParams { interface OnMessageParams {
type: string type: string
data: string | Object data: string | Object
client: WSClient client: WSClient
session: Session session: Session
logger: Logger logger: Logger
} }
export interface WSClient { interface WSClient {
id: string id: string
read: () => WSMessage read: () => WSMessage
write: (data: any) => void write: (data: any) => void
writeMessage: (type: string, data: any) => void writeMessage: (type: string, data: any) => void
ping: () => void ping: () => void
close: () => void close: () => void
} }
export interface WSMessage { interface WSMessage {
type: string type: string
data: string | Object data: string | Object
} }
export interface Session { interface Session {
set: (key: string | Object, value?: any) => void set: (key: string | Object, value?: any) => void
get: (...keys: string[]) => any | Object get: (...keys: string[]) => any | Object
remove: (...keys: string[]) => void remove: (...keys: string[]) => void
setAuthLevel: (authLevel: number) => void setAuthLevel: (authLevel: number) => void
save: () => void save: () => void
} }
export interface Caller { interface Caller {
get(url: string, headers?: Object): Result get(url: string, headers?: Object): Result
head(url: string, headers?: Object): Result head(url: string, headers?: Object): Result
post(url: string, data: any, headers?: Object): Result post(url: string, data: any, headers?: Object): Result
put(url: string, data: any, headers?: Object): Result put(url: string, data: any, headers?: Object): Result
delete(url: string, data: any, headers?: Object): Result delete(url: string, data: any, headers?: Object): Result
do(method: string, url: string, data: any, callback?: (data: string) => void, headers?: Object): Result do(method: string, url: string, data: any, callback?: (data: string) => void, headers?: Object): Result
} }
export interface Result { interface Result {
status: string status: string
statusCode: number statusCode: number
headers: Object headers: Object
bytes(): Uint8Array bytes(): Uint8Array
string(): string string(): string
object(): Object object(): Object
} }
export interface CookieOption { interface CookieOption {
path: string path: string
domain: string domain: string
expires: any expires: any
maxAge: number maxAge: number
secure: boolean secure: boolean
httpOnly: boolean httpOnly: boolean
} }
export interface Request { interface Request {
id: string id: string
proto: string proto: string
scheme: string scheme: string
host: string host: string
method: string method: string
path: string path: string
remoteAddr: string remoteAddr: string
realIP: string realIP: string
referer: string referer: string
userAgent: string userAgent: string
url: string url: string
contentLength: number contentLength: number
cookies: Object cookies: Object
headers: Object headers: Object
args: Object args: Object
files: Map<string, UploadFile> files: Map<string, UploadFile>
multiFiles: Map<string, UploadFile[]> multiFiles: Map<string, UploadFile[]>
makeURL: (path: string) => string makeURL: (path: string) => string
readAll: () => any readAll: () => any
read: (size: number) => any read: (size: number) => any
close: () => void close: () => void
get: (key: string) => any get: (key: string) => any
set: (key: string, value: any) => void set: (key: string, value: any) => void
getHeader: (key: string) => string getHeader: (key: string) => string
setHeader: (key: string, value: string) => void setHeader: (key: string, value: string) => void
setUserID: (id: string) => void setUserID: (id: string) => void
} }
export interface Response { interface Response {
id: string id: string
setStatus: (code: number) => void setStatus: (code: number) => void
setCookie: (name: string, value: string, option?: CookieOption) => void setCookie: (name: string, value: string, option?: CookieOption) => void
setHeader: (name: string, value: string) => void setHeader: (name: string, value: string) => void
addHeader: (name: string, value: string) => void addHeader: (name: string, value: string) => void
getHeader: (name: string) => string getHeader: (name: string) => string
write: (data: any) => number write: (data: any) => number
flush: () => void flush: () => void
sendFile: (contentType: string, filename: string) => void sendFile: (contentType: string, filename: string) => void
downloadFile: (contentType: string, filename: string, data: any) => void downloadFile: (contentType: string, filename: string, data: any) => void
location: (url: string) => void location: (url: string) => void
end: (data: any) => void end: (data: any) => void
} }
export interface Logger { interface Logger {
debug: (message: string, info?: Object) => void debug: (message: string, info?: Object) => void
info: (message: string, info?: Object) => void info: (message: string, info?: Object) => void
warn: (message: string, info?: Object) => void warn: (message: string, info?: Object) => void
error: (message: string, info?: Object) => void error: (message: string, info?: Object) => void
} }
export interface UploadFile { interface UploadFile {
name: string name: string
size: number size: number
data: any data: any
} }

View File

@ -2,7 +2,6 @@ package service
import ( import (
"reflect" "reflect"
"strings"
"sync" "sync"
"time" "time"
@ -10,14 +9,12 @@ import (
"apigo.cc/gojs/goja" "apigo.cc/gojs/goja"
"github.com/ssgo/log" "github.com/ssgo/log"
"github.com/ssgo/redis" "github.com/ssgo/redis"
"github.com/ssgo/u"
) )
type Session struct { type Session struct {
id string id string
conn *redis.Redis conn *redis.Redis
data map[string]any data map[string]any
funcAuthCache map[string]bool
} }
var sessionRedis *redis.Redis var sessionRedis *redis.Redis
@ -43,10 +40,9 @@ func NewSession(id string, logger *log.Logger) *Session {
} }
} }
return &Session{ return &Session{
id: id, id: id,
conn: conn, conn: conn,
data: data, data: data,
funcAuthCache: map[string]bool{},
} }
} }
@ -106,73 +102,6 @@ func (session *Session) SetAuthLevel(argsIn goja.FunctionCall, vm *goja.Runtime)
return nil return nil
} }
func (session *Session) authFuncs(needFuncs []string) bool {
cacheKey := strings.Join(needFuncs, "; ")
if cachedResult, ok := session.funcAuthCache[cacheKey]; ok {
return cachedResult
}
normalAuthOk := 0
requiredAuthTotal := 0
requiredAuthOk := 0
isOk := false
if userFuncs, ok := session.data["funcs"].([]string); ok && len(userFuncs) > 0 {
if u.StringIn(userFuncs, "system.superAdmin.") {
isOk = true
} else {
// 统计必须有几个权限
for _, needFunc := range needFuncs {
if needFunc[0] == '&' {
requiredAuthTotal++
}
}
// 检查是否有匹配的权限(左匹配)
for _, needFunc := range needFuncs {
isRequired := false
if needFunc[0] == '&' {
// 必须有此权限
isRequired = true
needFunc = needFunc[1:]
}
for _, userFunc := range userFuncs {
if strings.HasPrefix(userFunc, needFunc) {
// 匹配成功
if isRequired {
// 必须有此权限并且匹配成功
requiredAuthOk++
} else {
// 普通权限匹配成功
normalAuthOk++
}
break
}
}
if normalAuthOk > 0 && requiredAuthOk == requiredAuthTotal {
// 普通权限匹配成功,并且必须有权限也匹配成功,直接返回
isOk = true
break
}
}
}
}
session.funcAuthCache[cacheKey] = isOk
return isOk
}
func (session *Session) AuthFuncs(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
var needFuncs []string
if args.Arguments[0].ExportType().Kind() == reflect.Slice {
// 第一个参数是数组
needFuncs = args.Arr(0).StrArray(0)
} else {
// 平铺的参数
needFuncs = args.StrArray(0)
}
return vm.ToValue(session.authFuncs(needFuncs))
}
func (session *Session) Save(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { func (session *Session) Save(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
if session.conn == nil { if session.conn == nil {
now := time.Now().Unix() now := time.Now().Unix()

299
task.go
View File

@ -1,165 +1,162 @@
package service package service
// type Task struct { import (
// spec string "container/list"
// file string "sync"
// vm *gojs.Runtime
// lock sync.RWMutex
// mtime time.Time
// // policy string
// }
// var tasks []Task
// var tasksLock = sync.RWMutex{}
// var taskData = map[string]map[string]any{} "apigo.cc/gojs"
// var taskDataLock = sync.RWMutex{} "apigo.cc/gojs/goja"
)
// var taskList = map[string]*list.List{} var taskData = map[string]map[string]any{}
// var taskListLock = sync.RWMutex{} var taskDataLock = sync.RWMutex{}
// func DataSet(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { var taskList = map[string]*list.List{}
// args := gojs.MakeArgs(&argsIn, vm).Check(3) var taskListLock = sync.RWMutex{}
// scope := args.Str(0)
// key := args.Str(1)
// value := args.Any(2)
// taskDataLock.Lock()
// defer taskDataLock.Unlock()
// if taskData[scope] == nil {
// taskData[scope] = map[string]any{}
// }
// taskData[scope][key] = value
// return nil
// }
// func DataGet(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { func DataSet(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm).Check(2) args := gojs.MakeArgs(&argsIn, vm).Check(3)
// scope := args.Str(0) scope := args.Str(0)
// key := args.Str(1) key := args.Str(1)
// taskDataLock.RLock() value := args.Any(2)
// defer taskDataLock.RUnlock() taskDataLock.Lock()
// if taskData[scope] != nil { defer taskDataLock.Unlock()
// return vm.ToValue(taskData[scope][key]) if taskData[scope] == nil {
// } taskData[scope] = map[string]any{}
// return nil }
// } taskData[scope][key] = value
return nil
}
// func DataKeys(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { func DataGet(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm).Check(1) args := gojs.MakeArgs(&argsIn, vm).Check(2)
// scope := args.Str(0) scope := args.Str(0)
// taskDataLock.RLock() key := args.Str(1)
// defer taskDataLock.RUnlock() taskDataLock.RLock()
// if taskData[scope] != nil { defer taskDataLock.RUnlock()
// keys := make([]string, len(taskData[scope])) if taskData[scope] != nil {
// i := 0 return vm.ToValue(taskData[scope][key])
// for key := range taskData[scope] { }
// keys[i] = key return nil
// i++ }
// }
// return vm.ToValue(keys)
// }
// return nil
// }
// func DataCount(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { func DataKeys(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm).Check(1) args := gojs.MakeArgs(&argsIn, vm).Check(1)
// scope := args.Str(0) scope := args.Str(0)
// taskDataLock.RLock() taskDataLock.RLock()
// defer taskDataLock.RUnlock() defer taskDataLock.RUnlock()
// if taskData[scope] != nil { if taskData[scope] != nil {
// return vm.ToValue(len(taskData[scope])) keys := make([]string, len(taskData[scope]))
// } i := 0
// return vm.ToValue(0) for key := range taskData[scope] {
// } keys[i] = key
i++
}
return vm.ToValue(keys)
}
return nil
}
// func DataFetch(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { func DataCount(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm).Check(1) args := gojs.MakeArgs(&argsIn, vm).Check(1)
// scope := args.Str(0) scope := args.Str(0)
// taskDataLock.RLock() taskDataLock.RLock()
// defer taskDataLock.RUnlock() defer taskDataLock.RUnlock()
// if taskData[scope] != nil { if taskData[scope] != nil {
// all := make(map[string]any) return vm.ToValue(len(taskData[scope]))
// for k, v := range taskData[scope] { }
// all[k] = v return vm.ToValue(0)
// } }
// return vm.ToValue(all)
// }
// return nil
// }
// func DataRemove(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { func DataFetch(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm).Check(1) args := gojs.MakeArgs(&argsIn, vm).Check(1)
// scope := args.Str(0) scope := args.Str(0)
// key := args.Str(1) taskDataLock.RLock()
// taskDataLock.Lock() defer taskDataLock.RUnlock()
// defer taskDataLock.Unlock() if taskData[scope] != nil {
// if taskData[scope] != nil { all := make(map[string]any)
// if key != "" { for k, v := range taskData[scope] {
// delete(taskData[scope], key) all[k] = v
// } else { }
// delete(taskData, scope) return vm.ToValue(all)
// } }
// } return nil
// return nil }
// }
// func ListPush(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { func DataRemove(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm).Check(2) args := gojs.MakeArgs(&argsIn, vm).Check(1)
// scope := args.Str(0) scope := args.Str(0)
// value := args.Any(1) key := args.Str(1)
// fromHead := args.Bool(2) taskDataLock.Lock()
// taskListLock.Lock() defer taskDataLock.Unlock()
// defer taskListLock.Unlock() if taskData[scope] != nil {
// list1 := taskList[scope] if key != "" {
// if list1 == nil { delete(taskData[scope], key)
// list1 = list.New() } else {
// taskList[scope] = list1 delete(taskData, scope)
// } }
// if fromHead { }
// list1.PushFront(value) return nil
// } else { }
// list1.PushBack(value)
// }
// return nil
// }
// func ListPop(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { func ListPush(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm).Check(1) args := gojs.MakeArgs(&argsIn, vm).Check(2)
// scope := args.Str(0) scope := args.Str(0)
// fromEnd := args.Bool(1) value := args.Any(1)
// taskListLock.Lock() fromHead := args.Bool(2)
// var item *list.Element taskListLock.Lock()
// defer taskListLock.Unlock() defer taskListLock.Unlock()
// list1 := taskList[scope] list1 := taskList[scope]
// if list1 != nil { if list1 == nil {
// if fromEnd { list1 = list.New()
// item = list1.Front() taskList[scope] = list1
// } else { }
// item = list1.Back() if fromHead {
// } list1.PushFront(value)
// if item != nil { } else {
// list1.Remove(item) list1.PushBack(value)
// return vm.ToValue(item.Value) }
// } return nil
// } }
// return nil
// }
// func ListCount(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { func ListPop(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm).Check(1) args := gojs.MakeArgs(&argsIn, vm).Check(1)
// scope := args.Str(0) scope := args.Str(0)
// taskListLock.RLock() fromEnd := args.Bool(1)
// defer taskListLock.RUnlock() taskListLock.Lock()
// if taskList[scope] != nil { var item *list.Element
// return vm.ToValue(taskList[scope].Len()) defer taskListLock.Unlock()
// } list1 := taskList[scope]
// return vm.ToValue(0) if list1 != nil {
// } if fromEnd {
item = list1.Front()
} else {
item = list1.Back()
}
if item != nil {
list1.Remove(item)
return vm.ToValue(item.Value)
}
}
return nil
}
// func ListRemove(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { func ListCount(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
// args := gojs.MakeArgs(&argsIn, vm).Check(1) args := gojs.MakeArgs(&argsIn, vm).Check(1)
// scope := args.Str(0) scope := args.Str(0)
// taskListLock.Lock() taskListLock.RLock()
// defer taskListLock.Unlock() defer taskListLock.RUnlock()
// delete(taskList, scope) if taskList[scope] != nil {
// return nil return vm.ToValue(taskList[scope].Len())
// } }
return vm.ToValue(0)
}
func ListRemove(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
scope := args.Str(0)
taskListLock.Lock()
defer taskListLock.Unlock()
delete(taskList, scope)
return nil
}

View File

@ -1,12 +1,13 @@
import service from "apigo.cc/gojs/service" import service from "apigo.cc/gojs/service"
import rt from "apigo.cc/gojs/runtime" import co from "apigo.cc/gojs/console"
import u from "apigo.cc/gojs/util"
function main() { function main() {
service.register({ path: '/echo2', noLog200: true }, ({ args, response }) => { service.register({ path: '/echo2', noLog200: true }, ({ args, response }) => {
// setTimeout(() => { // setTimeout(() => {
// response.end(args.name) // response.end(args.name)
// }, 10) // }, 10)
rt.sleep(10) u.sleep(10)
return args.name return args.name
}) })
} }

View File

@ -1,6 +1,5 @@
import s from "apigo.cc/gojs/service" import s from "apigo.cc/gojs/service"
import co from "apigo.cc/gojs/console" import co from "apigo.cc/gojs/console"
import task from "apigo.cc/gojs/task"
function main() { function main() {
s.register({ s.register({
@ -10,13 +9,11 @@ function main() {
}, },
onClose: ({ client }) => { onClose: ({ client }) => {
co.info('ws closed', client.id) co.info('ws closed', client.id)
// s.dataRemove('wsTest', client.id) s.dataRemove('wsTest', client.id)
task.remove('wsTest_' + client.id)
} }
}, ({ client }) => { }, ({ client }) => {
co.info('ws connected', client.id) co.info('ws connected', client.id)
// s.dataSet('wsTest', client.id, client) s.dataSet('wsTest', client.id, client)
task.set('wsTest_' + client.id, client)
client.write('Hello, World!') client.write('Hello, World!')
}) })
} }

View File

@ -2,14 +2,12 @@ package service_test
import ( import (
"fmt" "fmt"
"os"
"sync" "sync"
"time" "time"
"apigo.cc/gojs" "apigo.cc/gojs"
_ "apigo.cc/gojs/console" _ "apigo.cc/gojs/console"
_ "apigo.cc/gojs/http" _ "apigo.cc/gojs/http"
_ "apigo.cc/gojs/runtime"
"apigo.cc/gojs/service" "apigo.cc/gojs/service"
_ "apigo.cc/gojs/service" _ "apigo.cc/gojs/service"
_ "apigo.cc/gojs/util" _ "apigo.cc/gojs/util"
@ -40,7 +38,6 @@ func TestStartByPool(t *testing.T) {
gojs.ExportForDev() gojs.ExportForDev()
rt2 = gojs.New() rt2 = gojs.New()
u.CopyFile("api/echo.js", "api/echo_tmp.js")
err := rt2.StartFromFile("start.js") err := rt2.StartFromFile("start.js")
if err != nil { if err != nil {
t.Fatal("start failed", err) t.Fatal("start failed", err)
@ -219,7 +216,7 @@ func TestStopByPool(t *testing.T) {
t.Fatal("stop failed", err) t.Fatal("stop failed", err)
} }
gojs.WaitAll() gojs.WaitAll()
os.Remove("api/echo_tmp.js")
runtime.GC() runtime.GC()
ms3 := runtime.MemStats{} ms3 := runtime.MemStats{}
runtime.ReadMemStats(&ms3) runtime.ReadMemStats(&ms3)

View File

@ -2,16 +2,12 @@ package service_test
import ( import (
"fmt" "fmt"
"os"
"strings"
"testing" "testing"
"time" "time"
"apigo.cc/gojs" "apigo.cc/gojs"
_ "apigo.cc/gojs/console" _ "apigo.cc/gojs/console"
_ "apigo.cc/gojs/file"
_ "apigo.cc/gojs/http" _ "apigo.cc/gojs/http"
_ "apigo.cc/gojs/runtime"
_ "apigo.cc/gojs/service" _ "apigo.cc/gojs/service"
_ "apigo.cc/gojs/util" _ "apigo.cc/gojs/util"
"github.com/ssgo/httpclient" "github.com/ssgo/httpclient"
@ -26,7 +22,6 @@ const runTimes = 100
func TestStart(t *testing.T) { func TestStart(t *testing.T) {
gojs.ExportForDev() gojs.ExportForDev()
rt = gojs.New() rt = gojs.New()
u.CopyFile("api/echo.js", "api/echo_tmp.js")
err := rt.StartFromFile("start.js") err := rt.StartFromFile("start.js")
if err != nil { if err != nil {
t.Fatal("start failed", err) t.Fatal("start failed", err)
@ -60,19 +55,6 @@ func TestJsEcho(t *testing.T) {
} }
} }
func TestJsEcho2(t *testing.T) {
for i := 0; i < runTimes; i++ {
name := u.UniqueId()
r, err := rt.RunCode("test2('" + name + "')")
if err != nil {
t.Fatal("test2 js get failed, got error", err)
} else if r != name {
t.Fatal("test2 js get failed, name not match", r, name)
}
}
u.WriteFile("api/echo_tmp.js", strings.Replace(u.ReadFileN("api/echo.js"), "return args.name", "return args.name+'!!'", 1))
}
func TestGoEcho(t *testing.T) { func TestGoEcho(t *testing.T) {
hc := httpclient.GetClientH2C(0) hc := httpclient.GetClientH2C(0)
for i := 0; i < runTimes; i++ { for i := 0; i < runTimes; i++ {
@ -136,19 +118,6 @@ func TestGoAsyncEcho(t *testing.T) {
fmt.Println(u.BGreen("last name:"), lastName, lastResult) fmt.Println(u.BGreen("last name:"), lastName, lastResult)
} }
func TestJsEcho2WithHotLoad(t *testing.T) {
time.Sleep(time.Second)
for i := 0; i < runTimes; i++ {
name := u.UniqueId()
r, err := rt.RunCode("test2('" + name + "')")
if err != nil {
t.Fatal("test2 js get failed, got error", err)
} else if r != name+"!!" {
t.Fatal("test2 js get failed, name not match", r, name)
}
}
}
func TestStop(t *testing.T) { func TestStop(t *testing.T) {
go func() { go func() {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
@ -156,7 +125,6 @@ func TestStop(t *testing.T) {
}() }()
gojs.WaitAll() gojs.WaitAll()
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
os.Remove("api/echo_tmp.js")
// if err != nil { // if err != nil {
// t.Fatal("stop failed", err) // t.Fatal("stop failed", err)
// } // }

View File

@ -6,9 +6,7 @@ import (
"apigo.cc/gojs" "apigo.cc/gojs"
_ "apigo.cc/gojs/console" _ "apigo.cc/gojs/console"
_ "apigo.cc/gojs/http" _ "apigo.cc/gojs/http"
_ "apigo.cc/gojs/runtime"
_ "apigo.cc/gojs/service" _ "apigo.cc/gojs/service"
_ "apigo.cc/gojs/task"
_ "apigo.cc/gojs/util" _ "apigo.cc/gojs/util"
"github.com/ssgo/u" "github.com/ssgo/u"
@ -45,9 +43,7 @@ func TestWS(t *testing.T) {
} }
func TestStopWSByPool(t *testing.T) { func TestStopWSByPool(t *testing.T) {
fmt.Println(u.BGreen("stop ws")) _, err := rt3.RunCode("s.stop()")
_, err := rt3.RunCode("s.stop();task.stop();")
fmt.Println(u.BGreen("stop ws ok"), err)
if err != nil { if err != nil {
t.Fatal("stop failed", err) t.Fatal("stop failed", err)
} }

View File

@ -1,8 +1,7 @@
import s from "apigo.cc/gojs/service" import s from "apigo.cc/gojs/service"
import http from "apigo.cc/gojs/http" import http from "apigo.cc/gojs/http"
import rt from "apigo.cc/gojs/runtime" import u from "apigo.cc/gojs/util"
import co from "apigo.cc/gojs/console" import co from "apigo.cc/gojs/console"
import file from "apigo.cc/gojs/file"
let h2c = http let h2c = http
let urlPrefix let urlPrefix
@ -34,17 +33,16 @@ function main() {
}, },
rewrite: { rewrite: {
'/echo2.js': '/echo.js' '/echo2.js': '/echo.js'
}, }
hotLoad: 1,
}) })
s.register({ path: '/echo', noLog200: true }, ({ args, response }) => { s.register({ path: '/echo', noLog200: true }, ({ args, response }) => {
// setTimeout(() => { // setTimeout(() => {
// response.end(args.name) // response.end(args.name)
// }, 1) // }, 1)
rt.sleep(1) u.sleep(1)
return args.name return args.name
}) })
s.load('api/echo_tmp.js', { min: 20, max: 1000, idle: 100 }) s.load('api/echo.js', { min: 20, max: 1000, idle: 100 })
s.load('api/user.js') s.load('api/user.js')
let host = s.start() let host = s.start()
h2c = http.newH2C({ h2c = http.newH2C({
@ -91,7 +89,7 @@ function testUser() {
if (r.statusCode != 429) { if (r.statusCode != 429) {
return r return r
} }
rt.sleep(100) u.sleep(100)
// 测试限流器过期后允许的 1 次请求 // 测试限流器过期后允许的 1 次请求
r = h2c.get('/userInfo').object() r = h2c.get('/userInfo').object()

View File

@ -1,16 +1,13 @@
import s from "apigo.cc/gojs/service" import s from "apigo.cc/gojs/service"
import http from "apigo.cc/gojs/http" import http from "apigo.cc/gojs/http"
import rt from "apigo.cc/gojs/runtime" import u from "apigo.cc/gojs/util"
import co from "apigo.cc/gojs/console" import co from "apigo.cc/gojs/console"
import task from "apigo.cc/gojs/task"
let hc = http let hc = http
let urlPrefix let urlPrefix
function main() { function main() {
s.load('api/ws.js') s.load('api/ws.js')
// s.task('task.js', 100) s.task('task.js', 100)
task.addTask("@every 1s", 'task.js')
task.start()
let host = s.start() let host = s.start()
hc = http.new({ baseURL: 'http://' + host }) hc = http.new({ baseURL: 'http://' + host })
return host return host
@ -34,7 +31,7 @@ function testWS() {
co.info('test ws abc ok') co.info('test ws abc ok')
// ws.ping() // ws.ping()
rt.sleep(10) u.sleep(10)
let pc = ws.pingCount() let pc = ws.pingCount()
co.info('test ws ping ok', pc.pingTimes, pc.pongTimes) co.info('test ws ping ok', pc.pingTimes, pc.pongTimes)
@ -54,13 +51,15 @@ function testWS() {
return r return r
} }
co.info('test ws json ok') co.info('test ws json ok')
rt.sleep(2000)
u.sleep(1000)
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
let j = ws.read() let j = ws.read()
if (i !== j.data) { if (i !== j.data) {
return j return j
} }
} }
ws.close() ws.close()
return true return true
} }

View File

@ -1,31 +1,30 @@
import s from 'apigo.cc/gojs/service' import s from 'apigo.cc/gojs/service'
import co from 'apigo.cc/gojs/console' import co from 'apigo.cc/gojs/console'
import task from 'apigo.cc/gojs/task'
function onStart() { function onStart() {
co.info('task start') co.info('task start')
} }
let i = 0 let i = 0
function onRun() { function onRun() {
let keys = task.keys('wsTest_') let connCount = s.dataCount('wsTest')
// let connCount = s.dataCount('wsTest') if (connCount > 0) {
if (keys.length > 0) { let conns = s.dataFetch('wsTest')
// let conns = s.dataFetch('wsTest') for (let id in conns) {
let conns = task.getAll('wsTest_') let conn = conns[id]
for (let id in conns) { try {
let conn = conns[id] conn.write(i++)
if (!conn.write(i++)) { } catch (e) {
task.remove(id) co.error(e)
} s.dataRemove('wsTest', id)
} }
} }
co.info('task run', keys.length) }
co.info('task run', connCount)
} }
function onStop() { function onStop() {
// s.dataRemove('wsTest') s.dataRemove('wsTest')
task.removeAll('wsTest_') co.info('task stop', s.dataCount('wsTest'))
co.info('task stop')
} }

View File

@ -1,7 +1,8 @@
import s from "apigo.cc/gojs/service" import s from "apigo.cc/gojs/service"
function main(args) { function main(args) {
return s.tpl('tpl/page.html', { title: 'Abc' }, { s.setTplFunc({
bb: text => { return '<b>' + text + '</b>' } bb: text => { return '<b>' + text + '</b>' }
}) })
return s.tpl('tpl/page.html', { title: 'Abc' })
} }

View File

@ -1,3 +1,3 @@
<header> <header>
<h1>Welcome to {{bb .title}}</h1> <h1>Welcome to {{bb .title}}</h1>
</header> </header>

View File

@ -2,15 +2,15 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{.title}}</title> <title>{{.title}}</title>
</head> </head>
<body> <body>
{{template "header.html" .}} {{template "header.html" .}}
<pre> <pre>
hello world hello world
</pre> </pre>
</body> </body>

33
ws.go
View File

@ -18,10 +18,7 @@ func MakeWSClient(client *websocket.Conn, id string) gojs.Map {
"read": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { "read": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
typ, data, err := readWSMessage(client) typ, data, err := readWSMessage(client)
if err != nil { if err != nil {
// panic(vm.NewGoError(err)) panic(vm.NewGoError(err))
vm.SetData("_lastError", err)
gojs.GetLogger(vm).Error(err.Error())
return vm.ToValue(false)
} }
return vm.ToValue(gojs.Map{ return vm.ToValue(gojs.Map{
"type": typ, "type": typ,
@ -40,22 +37,16 @@ func MakeWSClient(client *websocket.Conn, id string) gojs.Map {
err = client.WriteMessage(websocket.TextMessage, u.JsonBytes(args.Any(0))) err = client.WriteMessage(websocket.TextMessage, u.JsonBytes(args.Any(0)))
} }
if err != nil { if err != nil {
// panic(vm.NewGoError(err)) panic(vm.NewGoError(err))
vm.SetData("_lastError", err)
gojs.GetLogger(vm).Error(err.Error())
return vm.ToValue(false)
} }
return vm.ToValue(true) return nil
}, },
"ping": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { "ping": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
err := client.WriteMessage(websocket.PingMessage, nil) err := client.WriteMessage(websocket.PingMessage, nil)
if err != nil { if err != nil {
// panic(vm.NewGoError(err)) panic(vm.NewGoError(err))
vm.SetData("_lastError", err)
gojs.GetLogger(vm).Error(err.Error())
return vm.ToValue(false)
} }
return vm.ToValue(true) return nil
}, },
"writeMessage": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { "writeMessage": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(2) args := gojs.MakeArgs(&argsIn, vm).Check(2)
@ -77,22 +68,16 @@ func MakeWSClient(client *websocket.Conn, id string) gojs.Map {
err = client.WriteMessage(websocket.TextMessage, args.Bytes(1)) err = client.WriteMessage(websocket.TextMessage, args.Bytes(1))
} }
if err != nil { if err != nil {
// panic(vm.NewGoError(err)) panic(vm.NewGoError(err))
vm.SetData("_lastError", err)
gojs.GetLogger(vm).Error(err.Error())
return vm.ToValue(false)
} }
return vm.ToValue(true) return nil
}, },
"close": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { "close": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
err := client.Close() err := client.Close()
if err != nil { if err != nil {
// panic(vm.NewGoError(err)) panic(vm.NewGoError(err))
vm.SetData("_lastError", err)
gojs.GetLogger(vm).Error(err.Error())
return vm.ToValue(false)
} }
return vm.ToValue(true) return nil
}, },
} }
} }