Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ee7007f050 | |||
| 627c05fd06 | |||
| fa97cace29 |
@ -22,7 +22,10 @@ func NewCaller(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
|
||||
func makeResult(r *httpclient.Result, vm *goja.Runtime) goja.Value {
|
||||
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{}
|
||||
for k, v := range r.Response.Header {
|
||||
|
||||
12
go.mod
12
go.mod
@ -3,7 +3,7 @@ module apigo.cc/gojs/service
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
apigo.cc/gojs v0.0.30
|
||||
apigo.cc/gojs v0.0.32
|
||||
apigo.cc/gojs/console v0.0.4
|
||||
apigo.cc/gojs/file v0.0.7
|
||||
apigo.cc/gojs/http v0.0.8
|
||||
@ -14,7 +14,7 @@ require (
|
||||
github.com/ssgo/config v1.7.10
|
||||
github.com/ssgo/discover v1.7.10
|
||||
github.com/ssgo/httpclient v1.7.8
|
||||
github.com/ssgo/log v1.7.9
|
||||
github.com/ssgo/log v1.7.10
|
||||
github.com/ssgo/redis v1.7.8
|
||||
github.com/ssgo/s v1.7.25
|
||||
github.com/ssgo/standard v1.7.7
|
||||
@ -46,9 +46,9 @@ require (
|
||||
github.com/ysmood/gson v0.7.3 // indirect
|
||||
github.com/ysmood/leakless v0.9.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/crypto v0.44.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
15
request.go
15
request.go
@ -91,7 +91,10 @@ func (r *Request) MakeURL(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)
|
||||
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)
|
||||
}
|
||||
@ -102,7 +105,10 @@ func (r *Request) Read(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
data := make([]byte, size)
|
||||
n, err := r.req.Body.Read(data)
|
||||
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])
|
||||
}
|
||||
@ -110,7 +116,10 @@ func (r *Request) Read(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()
|
||||
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
|
||||
}
|
||||
|
||||
@ -93,7 +93,10 @@ func (r *Response) Write(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
n, err := r.resp.Write(args.Bytes(0))
|
||||
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)
|
||||
}
|
||||
|
||||
303
service.go
303
service.go
@ -50,10 +50,11 @@ type TplCache struct {
|
||||
Tpl *template.Template
|
||||
}
|
||||
|
||||
var tplFunc = map[string]any{}
|
||||
var tplFuncLock = sync.RWMutex{}
|
||||
var tplCache = map[string]*TplCache{}
|
||||
var tplCacheLock = sync.RWMutex{}
|
||||
// var tplFunc = map[string]any{}
|
||||
var tplReplaces = map[string]string{}
|
||||
var tplLock = sync.RWMutex{}
|
||||
|
||||
// var tplCache = map[string]*TplCache{}
|
||||
|
||||
type LimiterConfig struct {
|
||||
From string
|
||||
@ -74,6 +75,7 @@ type Config struct {
|
||||
Limiters map[string]*LimiterConfig
|
||||
LimiterRedis string
|
||||
HotLoad int
|
||||
TplSafePaths []string
|
||||
|
||||
Proxy map[string]string
|
||||
Rewrite map[string]string
|
||||
@ -93,9 +95,12 @@ func initConfig(opt *gojs.Obj, logger *log.Logger, vm *goja.Runtime) {
|
||||
s.SetWorkPath(u.String(startPath))
|
||||
}
|
||||
// 处理配置
|
||||
serviceConfig = Config{"Session", "Device", "Client", "id", "", 3600, "auth failed", "verify failed", "too many requests", nil, "", 0, map[string]string{}, map[string]string{}, map[string]string{}}
|
||||
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{}}
|
||||
if errs := config.LoadConfig("service", &serviceConfig); 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)
|
||||
// var auth goja.Callable
|
||||
@ -138,6 +143,7 @@ func initConfig(opt *gojs.Obj, logger *log.Logger, vm *goja.Runtime) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有session中的authLevel验证失败,则使用Access-Token中的authLevel(服务间调用)
|
||||
if authAccessToken && setAuthLevel < authLevel {
|
||||
setAuthLevel = s.GetAuthTokenLevel(request.Header.Get("Access-Token"))
|
||||
@ -251,7 +257,10 @@ func init() {
|
||||
// panic(vm.NewGoError(errors.New("must run service.config frist")))
|
||||
}
|
||||
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 {
|
||||
@ -290,8 +299,9 @@ func init() {
|
||||
onWatchLock.Unlock()
|
||||
}, "")
|
||||
s.SetOutFilter(func(in map[string]any, request *s.Request, response *s.Response, out any, logger *log.Logger) (newOut any, isOver bool) {
|
||||
if strings.HasPrefix(response.Header().Get("Content-Type"), "text/html") {
|
||||
outStr := u.String(out)
|
||||
contentType := response.Header().Get("Content-Type")
|
||||
outStr := u.String(out)
|
||||
if strings.HasPrefix(contentType, "text/html") || (contentType == "" && strings.Contains(outStr, "<html")) {
|
||||
if strings.Contains(outStr, "let _watchWS = null") {
|
||||
return nil, false
|
||||
}
|
||||
@ -361,11 +371,14 @@ func init() {
|
||||
},
|
||||
"stop": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
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()
|
||||
s.ResetAllSets()
|
||||
return nil
|
||||
return vm.ToValue(true)
|
||||
},
|
||||
// "uniqueId": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
// args := gojs.MakeArgs(&argsIn, vm)
|
||||
@ -479,7 +492,10 @@ func init() {
|
||||
o := args.Obj(0)
|
||||
action := args.Func(1)
|
||||
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")
|
||||
@ -573,7 +589,7 @@ func init() {
|
||||
// 无对象池,直接调用(单线程)
|
||||
s.RestfulWithOptions(authLevel, method, path, makeInnerAction(action, vm, args.This), memo, opt)
|
||||
}
|
||||
return nil
|
||||
return vm.ToValue(true)
|
||||
},
|
||||
"load": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
@ -596,11 +612,17 @@ func init() {
|
||||
fi := u.GetFileInfo(actionFile)
|
||||
if fi == nil {
|
||||
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)
|
||||
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")))
|
||||
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()
|
||||
poolExists[actionFile] = true
|
||||
@ -624,7 +646,7 @@ func init() {
|
||||
poolsMTime[actionFile] = mtime
|
||||
poolsConfig[actionFile] = poolOpt
|
||||
poolsLock.Unlock()
|
||||
return nil
|
||||
return vm.ToValue(true)
|
||||
},
|
||||
// "task": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
// args := gojs.MakeArgs(&argsIn, vm).Check(1)
|
||||
@ -653,49 +675,80 @@ func init() {
|
||||
// })
|
||||
// return nil
|
||||
// },
|
||||
"setTplFunc": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
// "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)
|
||||
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()
|
||||
}
|
||||
replaces := args.Map(0)
|
||||
tplLock.Lock()
|
||||
for k, v := range replaces {
|
||||
tplReplaces[k] = u.String(v)
|
||||
}
|
||||
tplLock.Unlock()
|
||||
return nil
|
||||
},
|
||||
"tpl": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(2)
|
||||
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)
|
||||
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)
|
||||
tplCacheLock.RLock()
|
||||
t := tplCache[filename]
|
||||
tplCacheLock.RUnlock()
|
||||
// tplLock.RLock()
|
||||
// t := tplCache[filename]
|
||||
var t *TplCache
|
||||
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 {
|
||||
for f, tm := range t.FileModTime {
|
||||
info := u.GetFileInfo(f)
|
||||
@ -706,44 +759,110 @@ func init() {
|
||||
}
|
||||
}
|
||||
if t == nil {
|
||||
tpl := template.New("main")
|
||||
if len(tplFunc) > 0 {
|
||||
tpl = tpl.Funcs(tplFunc)
|
||||
tpl := template.New(filename)
|
||||
fnList := map[string]any{}
|
||||
if fnObj := args.Obj(2); fnObj != nil {
|
||||
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{
|
||||
filename: info.ModTime.UnixMilli(),
|
||||
}
|
||||
var err error
|
||||
for _, m := range tplIncludeMatcher.FindAllStringSubmatch(u.ReadFileN(filename), -1) {
|
||||
includeFilename := m[1]
|
||||
info2 := u.GetFileInfo(includeFilename)
|
||||
if info2 == nil {
|
||||
includeFilename = filepath.Join(filepath.Dir(filename), m[1])
|
||||
info2 = u.GetFileInfo(includeFilename)
|
||||
code := tplReplace(u.ReadFileN(filename))
|
||||
for _, m := range tplIncludeMatcher.FindAllStringSubmatch(code, -1) {
|
||||
a := strings.SplitN(m[1], ":", 2)
|
||||
includeFilename := a[0]
|
||||
includeFilepath := filepath.Join(basepath, includeFilename)
|
||||
if !tplIsSafe(includeFilepath) {
|
||||
// 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 err != nil {
|
||||
panic(vm.NewGoError(err))
|
||||
|
||||
// 每个被包含的文件只解析一次(统一修正)
|
||||
if fileModTime[includeFilepath] == 0 {
|
||||
info2 := u.GetFileInfo(includeFilepath)
|
||||
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 {
|
||||
panic(vm.NewGoError(err))
|
||||
// panic(vm.NewGoError(err))
|
||||
vm.SetData("_lastError", err)
|
||||
gojs.GetLogger(vm).Error(err.Error())
|
||||
return nil
|
||||
}
|
||||
t = &TplCache{
|
||||
Tpl: tpl,
|
||||
FileModTime: fileModTime,
|
||||
}
|
||||
// tplLock.Lock()
|
||||
// tplCache[filename] = t
|
||||
// tplLock.Unlock()
|
||||
vm.SetData("TPL_"+filename, t)
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0))
|
||||
err := t.Tpl.ExecuteTemplate(buf, filepath.Base(filename), data)
|
||||
err := t.Tpl.ExecuteTemplate(buf, filename, data)
|
||||
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())
|
||||
},
|
||||
@ -783,10 +902,53 @@ 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 tplDefineMatcher = regexp.MustCompile(`{{\s*define\s+"([^"]+)"\s*}}`)
|
||||
|
||||
func verifyRegexp(regexpStr string) func(any, *goja.Runtime) bool {
|
||||
if rx, err := regexp.Compile(regexpStr); err != nil {
|
||||
if rx, err := getRegexp(regexpStr); err != nil {
|
||||
return func(value any, vm *goja.Runtime) bool {
|
||||
return rx.MatchString(u.String(value))
|
||||
}
|
||||
@ -906,6 +1068,15 @@ 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)
|
||||
var r any
|
||||
|
||||
|
||||
453
service.ts
453
service.ts
@ -1,29 +1,30 @@
|
||||
// just for develop
|
||||
|
||||
export default {
|
||||
config,
|
||||
start,
|
||||
stop,
|
||||
register,
|
||||
load,
|
||||
// task,
|
||||
newCaller,
|
||||
// dataSet,
|
||||
// dataGet,
|
||||
// dataKeys,
|
||||
// dataCount,
|
||||
// dataFetch,
|
||||
// dataRemove,
|
||||
// listPush,
|
||||
// listPop,
|
||||
// listCount,
|
||||
// listRemove,
|
||||
id,
|
||||
// idL,
|
||||
// uniqueId,
|
||||
// uniqueIdL,
|
||||
setTplFunc,
|
||||
tpl,
|
||||
config,
|
||||
start,
|
||||
stop,
|
||||
register,
|
||||
load,
|
||||
// task,
|
||||
newCaller,
|
||||
// dataSet,
|
||||
// dataGet,
|
||||
// dataKeys,
|
||||
// dataCount,
|
||||
// dataFetch,
|
||||
// dataRemove,
|
||||
// listPush,
|
||||
// listPop,
|
||||
// listCount,
|
||||
// listRemove,
|
||||
id,
|
||||
// idL,
|
||||
// uniqueId,
|
||||
// uniqueIdL,
|
||||
// setTplFunc,
|
||||
setTplReplaces,
|
||||
tpl,
|
||||
}
|
||||
|
||||
function config(config?: Config): void { }
|
||||
@ -52,242 +53,244 @@ function id(size?: number): string { return '' }
|
||||
// function idL(space: string, size?: number): string { return '' }
|
||||
// function uniqueId(size?: number): string { return '' }
|
||||
// function uniqueIdL(size?: number): string { return '' }
|
||||
function setTplFunc(fnList: Object): void { }
|
||||
function tpl(file: string, data: Object): string { return '' }
|
||||
// function setTplFunc(fnList: Object): void { }
|
||||
function setTplReplaces(replaces: Object): void { }
|
||||
function tpl(file: string, data: Object, fnList?: Object): string { return '' }
|
||||
|
||||
interface Config {
|
||||
// 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
|
||||
ssl: Map<string, CertSet> // SSL证书配置,key为域名,value为cert和key的文件路径
|
||||
noLogGets: boolean // 不记录GET请求的日志
|
||||
noLogHeaders: string // 不记录请求头中包含的这些字段,多个字段用逗号分隔,默认不记录:Accept,Accept-Encoding,Cache-Control,Pragma,Connection,Upgrade-Insecure-Requests
|
||||
logInputArrayNum: number // 请求字段中容器类型(数组、Map)在日志打印个数限制 默认为10个,多余的数据将不再日志中记录
|
||||
logInputFieldSize: number // 请求字段中单个字段在日志打印长度限制 默认为500个字符,多余的数据将不再日志中记录
|
||||
noLogOutputFields: string // 不记录响应字段中包含的这些字段(key名),多个字段用逗号分隔
|
||||
logOutputArrayNum: number // 响应字段中容器类型(数组、Map)在日志打印个数限制 默认为3个,多余的数据将不再日志中记录
|
||||
logOutputFieldSize: number // 响应字段中单个字段在日志打印长度限制 默认为100个字符,多余的数据将不再日志中记录
|
||||
logWebsocketAction: boolean // 记录Websocket中每个Action的请求日志,默认不记录
|
||||
compress: boolean // 是否启用压缩,默认不启用
|
||||
compressMinSize: number // 小于设定值的应答内容将不进行压缩,默认值:1024
|
||||
compressMaxSize: number // 大于设定值的应答内容将不进行压缩,默认值:4096000
|
||||
checkDomain: string // 心跳检测时使用域名,,默认使用IP地址,心跳检测使用 HEAD /__CHECK__ 请求,应答 299 表示正常,593 表示异常
|
||||
redirectTimeout: number // proxy和discover发起请求时的超时时间,单位ms,默认值:10000
|
||||
acceptXRealIpWithoutRequestId: boolean // 是否允许头部没有携带请求ID的X-Real-IP信息,默认不允许(防止伪造客户端IP)
|
||||
statisticTime: boolean // 是否开启请求时间统计,默认不开启
|
||||
statisticTimeInterval: number // 统计时间间隔,单位ms,默认值:10000
|
||||
fast: boolean // 是否启用快速模式(为了追求性能牺牲一部分特性),默认不启用
|
||||
maxUploadSize: number // 最大上传文件大小(multipart/form-data请求的总空间),单位字节,默认值:104857600
|
||||
cpu: number // CPU占用的核数,默认为0,即不做限制
|
||||
memory: number // 内存(单位M),默认为0,即不做限制
|
||||
cpuMonitor: boolean // 在日志中记录CPU使用情况,默认不开启
|
||||
memoryMonitor: boolean // 在日志中记录内存使用情况,默认不开启
|
||||
cpuLimitValue: number // CPU超过最高占用值(10-100)超过次数将自动重启(如果CpuMonitor开启的话),默认100
|
||||
memoryLimitValue: number // 内存超过最高占用值(10-100)超过次数将自动重启(如果MemoryMonitor开启的话),默认95
|
||||
cpuLimitTimes: number // CPU超过最高占用值超过次数(1-100)将报警(如果CpuMonitor开启的话),默认6(即30秒内连续6次)
|
||||
memoryLimitTimes: number // 内存超过最高占用值超过次数(1-100)将报警(如果MemoryMonitor开启的话),默认6(即30秒内连续6次)
|
||||
cookieScope: string // 启用Session时Cookie的有效范围,host|domain|topDomain,默认值为host
|
||||
sessionWithoutCookie: boolean // Session禁用Cookie保持,默认使用Cookie
|
||||
deviceWithoutCookie: boolean // 设备ID禁用Cookie保持,默认使用Cookie
|
||||
idServer: string // 用s.UniqueId、s.Id来生成唯一ID(雪花算法)时所需的redis服务器连接,如果不指定将不能实现跨服务的全局唯一
|
||||
keepKeyCase: boolean // 是否保持Key的首字母大小写?默认保持,设置为false则自动将首字母转为小写
|
||||
indexFiles: string[] // 访问静态文件时的索引文件,默认为 index.html
|
||||
indexDir: boolean // 访问目录时显示文件列表
|
||||
readTimeout: number // 读取请求的超时时间,单位ms
|
||||
readHeaderTimeout: number // 读取请求头的超时时间,单位ms
|
||||
writeTimeout: number // 响应写入的超时时间,单位ms
|
||||
idleTimeout: number // 连接空闲超时时间,单位ms
|
||||
maxHeaderBytes: number // 请求头的最大字节数
|
||||
maxHandlers: number // 每个连接的最大处理程序数量
|
||||
maxConcurrentStreams: number // 每个连接的最大并发流数量
|
||||
maxDecoderHeaderTableSize: number // 解码器头表的最大大小
|
||||
maxEncoderHeaderTableSize: number // 编码器头表的最大大小
|
||||
maxReadFrameSize: number // 单个帧的最大读取大小
|
||||
maxUploadBufferPerConnection: number // 每个连接的最大上传缓冲区大小
|
||||
maxUploadBufferPerStream: number // 每个流的最大上传缓冲区大小
|
||||
export interface Config {
|
||||
// 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
|
||||
ssl: Map<string, CertSet> // SSL证书配置,key为域名,value为cert和key的文件路径
|
||||
noLogGets: boolean // 不记录GET请求的日志
|
||||
noLogHeaders: string // 不记录请求头中包含的这些字段,多个字段用逗号分隔,默认不记录:Accept,Accept-Encoding,Cache-Control,Pragma,Connection,Upgrade-Insecure-Requests
|
||||
logInputArrayNum: number // 请求字段中容器类型(数组、Map)在日志打印个数限制 默认为10个,多余的数据将不再日志中记录
|
||||
logInputFieldSize: number // 请求字段中单个字段在日志打印长度限制 默认为500个字符,多余的数据将不再日志中记录
|
||||
noLogOutputFields: string // 不记录响应字段中包含的这些字段(key名),多个字段用逗号分隔
|
||||
logOutputArrayNum: number // 响应字段中容器类型(数组、Map)在日志打印个数限制 默认为3个,多余的数据将不再日志中记录
|
||||
logOutputFieldSize: number // 响应字段中单个字段在日志打印长度限制 默认为100个字符,多余的数据将不再日志中记录
|
||||
logWebsocketAction: boolean // 记录Websocket中每个Action的请求日志,默认不记录
|
||||
compress: boolean // 是否启用压缩,默认不启用
|
||||
compressMinSize: number // 小于设定值的应答内容将不进行压缩,默认值:1024
|
||||
compressMaxSize: number // 大于设定值的应答内容将不进行压缩,默认值:4096000
|
||||
checkDomain: string // 心跳检测时使用域名,,默认使用IP地址,心跳检测使用 HEAD /__CHECK__ 请求,应答 299 表示正常,593 表示异常
|
||||
redirectTimeout: number // proxy和discover发起请求时的超时时间,单位ms,默认值:10000
|
||||
acceptXRealIpWithoutRequestId: boolean // 是否允许头部没有携带请求ID的X-Real-IP信息,默认不允许(防止伪造客户端IP)
|
||||
statisticTime: boolean // 是否开启请求时间统计,默认不开启
|
||||
statisticTimeInterval: number // 统计时间间隔,单位ms,默认值:10000
|
||||
fast: boolean // 是否启用快速模式(为了追求性能牺牲一部分特性),默认不启用
|
||||
maxUploadSize: number // 最大上传文件大小(multipart/form-data请求的总空间),单位字节,默认值:104857600
|
||||
cpu: number // CPU占用的核数,默认为0,即不做限制
|
||||
memory: number // 内存(单位M),默认为0,即不做限制
|
||||
cpuMonitor: boolean // 在日志中记录CPU使用情况,默认不开启
|
||||
memoryMonitor: boolean // 在日志中记录内存使用情况,默认不开启
|
||||
cpuLimitValue: number // CPU超过最高占用值(10-100)超过次数将自动重启(如果CpuMonitor开启的话),默认100
|
||||
memoryLimitValue: number // 内存超过最高占用值(10-100)超过次数将自动重启(如果MemoryMonitor开启的话),默认95
|
||||
cpuLimitTimes: number // CPU超过最高占用值超过次数(1-100)将报警(如果CpuMonitor开启的话),默认6(即30秒内连续6次)
|
||||
memoryLimitTimes: number // 内存超过最高占用值超过次数(1-100)将报警(如果MemoryMonitor开启的话),默认6(即30秒内连续6次)
|
||||
cookieScope: string // 启用Session时Cookie的有效范围,host|domain|topDomain,默认值为host
|
||||
sessionWithoutCookie: boolean // Session禁用Cookie保持,默认使用Cookie
|
||||
deviceWithoutCookie: boolean // 设备ID禁用Cookie保持,默认使用Cookie
|
||||
idServer: string // 用s.UniqueId、s.Id来生成唯一ID(雪花算法)时所需的redis服务器连接,如果不指定将不能实现跨服务的全局唯一
|
||||
keepKeyCase: boolean // 是否保持Key的首字母大小写?默认保持,设置为false则自动将首字母转为小写
|
||||
indexFiles: string[] // 访问静态文件时的索引文件,默认为 index.html
|
||||
indexDir: boolean // 访问目录时显示文件列表
|
||||
readTimeout: number // 读取请求的超时时间,单位ms
|
||||
readHeaderTimeout: number // 读取请求头的超时时间,单位ms
|
||||
writeTimeout: number // 响应写入的超时时间,单位ms
|
||||
idleTimeout: number // 连接空闲超时时间,单位ms
|
||||
maxHeaderBytes: number // 请求头的最大字节数
|
||||
maxHandlers: number // 每个连接的最大处理程序数量
|
||||
maxConcurrentStreams: number // 每个连接的最大并发流数量
|
||||
maxDecoderHeaderTableSize: number // 解码器头表的最大大小
|
||||
maxEncoderHeaderTableSize: number // 编码器头表的最大大小
|
||||
maxReadFrameSize: number // 单个帧的最大读取大小
|
||||
maxUploadBufferPerConnection: number // 每个连接的最大上传缓冲区大小
|
||||
maxUploadBufferPerStream: number // 每个流的最大上传缓冲区大小
|
||||
|
||||
// 其他配置
|
||||
sessionKey: string // HTTP头和Cookie中SessionID的Key,客户端没有传递时服务端自动生成,Header的优先级高于Cookie,默认为 Session, 设置为空时表示不使用
|
||||
deviceKey: string // 标识设备ID的Key,客户端没有传递时服务端自动生成,Header的优先级高于Cookie,默认为 Device, 设置为空时表示不使用
|
||||
clientKey: string // 标识客户端的Key,默认为 Client,对应的Header头为 ClientName 和 ClientVersion
|
||||
userIdKey: string // session中userID的Key,用于在日志中记录用户ID信息,默认为 id
|
||||
sessionProvider: string // 指定一个redis连接(例如:redis://:sskey加密的密码@127.0.0.1:6379/15),默认使用内存存储
|
||||
sessionTimeout: number // session过期时间,单位 秒,默认为 3600秒
|
||||
authFieldMessage: string | Object // 身份验证失败时的消息,默认为 auth failed,可以设置对象来返回JSON,可以使用模版 {{TARGET_AUTHLEVEL}}、{{USER_AUTHLEVEL}}
|
||||
verifyFieldMessage: string | Object // 参数验证失败时的消息,默认为 verify failed,可以设置对象来返回JSON,可以使用模版 {{FAILED_FIELDS}}
|
||||
limitedMessage: string | Object // 访问受限时的消息,默认为 too many requests,可以设置对象来返回JSON,可以使用模版 {{LIMITED_FROM}}、{{LIMITED_VALUE}}
|
||||
limiterRedis: string // 限流器使用的Redis连接,默认使用内存存储
|
||||
limiters: Map<string, LimiterConfig> // 限流器配置,from 为数据来源,例如:ip、user、device、header.User-Agent、in.phone 等(in表示从请求参数中获取),time为时间间隔,单位ms,times为 时间间隔内允许访问的次数
|
||||
hotLoad: number // 热加载配置,单位s,默认值:0,0表示不检测热加载
|
||||
// 其他配置
|
||||
sessionKey: string // HTTP头和Cookie中SessionID的Key,客户端没有传递时服务端自动生成,Header的优先级高于Cookie,默认为 Session, 设置为空时表示不使用
|
||||
deviceKey: string // 标识设备ID的Key,客户端没有传递时服务端自动生成,Header的优先级高于Cookie,默认为 Device, 设置为空时表示不使用
|
||||
clientKey: string // 标识客户端的Key,默认为 Client,对应的Header头为 ClientName 和 ClientVersion
|
||||
userIdKey: string // session中userID的Key,用于在日志中记录用户ID信息,默认为 id
|
||||
sessionProvider: string // 指定一个redis连接(例如:redis://:sskey加密的密码@127.0.0.1:6379/15),默认使用内存存储
|
||||
sessionTimeout: number // session过期时间,单位 秒,默认为 3600秒
|
||||
authFieldMessage: string | Object // 身份验证失败时的消息,默认为 auth failed,可以设置对象来返回JSON,可以使用模版 {{TARGET_AUTHLEVEL}}、{{USER_AUTHLEVEL}}
|
||||
verifyFieldMessage: string | Object // 参数验证失败时的消息,默认为 verify failed,可以设置对象来返回JSON,可以使用模版 {{FAILED_FIELDS}}
|
||||
limitedMessage: string | Object // 访问受限时的消息,默认为 too many requests,可以设置对象来返回JSON,可以使用模版 {{LIMITED_FROM}}、{{LIMITED_VALUE}}
|
||||
limiterRedis: string // 限流器使用的Redis连接,默认使用内存存储
|
||||
limiters: Map<string, LimiterConfig> // 限流器配置,from 为数据来源,例如:ip、user、device、header.User-Agent、in.phone 等(in表示从请求参数中获取),time为时间间隔,单位ms,times为 时间间隔内允许访问的次数
|
||||
hotLoad: number // 热加载配置,单位s,默认值:0,0表示不检测热加载
|
||||
tplSafePaths: string[] // 模板文件的安全路径,默认不开启安全检查
|
||||
|
||||
// gateway 的配置参数
|
||||
proxy: Map<string, string> // 代理配置,key为[host][path],value为代理的目标应用或URL
|
||||
rewrite: Map<string, string> // 重写请求路径,key为[host][path],value为重写后的路径
|
||||
static: Map<string, string> // 静态文件目录,key为[host][path],value为目录路径
|
||||
// gateway 的配置参数
|
||||
proxy: Map<string, string> // 代理配置,key为[host][path],value为代理的目标应用或URL
|
||||
rewrite: Map<string, string> // 重写请求路径,key为[host][path],value为重写后的路径
|
||||
static: Map<string, string> // 静态文件目录,key为[host][path],value为目录路径
|
||||
|
||||
// github.com/ssgo/discover 的配置参数
|
||||
registry: string // 服务注册中心,请配置一个有效的RedisURL,默认值 redis://:@127.0.0.1:6379/15
|
||||
app: string // 设置该值将会自动将服务注册到 discover
|
||||
weight: number // 节点的权重,默认值 100
|
||||
accessTokens: Map<string, number> // 请求接口时使用指定的Access-Token进行验证,值为Token对应的authLevel(允许访问大于等于指定对应authLevel的接口)
|
||||
calls: Map<string, string> // 配置将会调用的服务,格式:[1|2]:[http|https|h2c]:[AccessToken],默认协议为:h2c,如使用h2c协议可只提供 AccessToken
|
||||
callRetryTimes: number // 调用其他服务时最大重试次数,默认值 10
|
||||
ipPrefix: string // discover服务发现时指定使用的IP网段,默认排除 172.17.(Docker)
|
||||
// github.com/ssgo/discover 的配置参数
|
||||
registry: string // 服务注册中心,请配置一个有效的RedisURL,默认值 redis://:@127.0.0.1:6379/15
|
||||
app: string // 设置该值将会自动将服务注册到 discover
|
||||
weight: number // 节点的权重,默认值 100
|
||||
accessTokens: Map<string, number> // 请求接口时使用指定的Access-Token进行验证,值为Token对应的authLevel(允许访问大于等于指定对应authLevel的接口)
|
||||
calls: Map<string, string> // 配置将会调用的服务,格式:[1|2]:[http|https|h2c]:[AccessToken],默认协议为:h2c,如使用h2c协议可只提供 AccessToken
|
||||
callRetryTimes: number // 调用其他服务时最大重试次数,默认值 10
|
||||
ipPrefix: string // discover服务发现时指定使用的IP网段,默认排除 172.17.(Docker)
|
||||
}
|
||||
|
||||
interface CertSet {
|
||||
certFile: string
|
||||
keyFile: string
|
||||
export interface CertSet {
|
||||
certFile: string
|
||||
keyFile: string
|
||||
}
|
||||
|
||||
interface PoolConfig {
|
||||
min: number
|
||||
max: string
|
||||
idle: number
|
||||
export interface PoolConfig {
|
||||
min: number
|
||||
max: string
|
||||
idle: number
|
||||
}
|
||||
|
||||
interface LimiterConfig {
|
||||
from: string
|
||||
time: number
|
||||
times: number
|
||||
export interface LimiterConfig {
|
||||
from: string
|
||||
time: number
|
||||
times: number
|
||||
}
|
||||
|
||||
interface RegisterOption {
|
||||
authLevel: number
|
||||
host: string
|
||||
method: string
|
||||
path: string
|
||||
memo: string
|
||||
noBody: boolean
|
||||
noLog200: boolean
|
||||
limiters: string[]
|
||||
verifies: Object
|
||||
requires: string[]
|
||||
ext: Object
|
||||
onMessage: (params: OnMessageParams) => void
|
||||
onClose: (params: RequestParams) => void
|
||||
export interface RegisterOption {
|
||||
authLevel: number
|
||||
host: string
|
||||
method: string
|
||||
path: string
|
||||
memo: string
|
||||
noBody: boolean
|
||||
noLog200: boolean
|
||||
limiters: string[]
|
||||
verifies: Object
|
||||
requires: string[]
|
||||
ext: Object
|
||||
onMessage: (params: OnMessageParams) => void
|
||||
onClose: (params: RequestParams) => void
|
||||
}
|
||||
|
||||
interface RequestParams {
|
||||
args: Object
|
||||
headers: Object
|
||||
request: Request
|
||||
client: WSClient
|
||||
caller: Caller
|
||||
session: Session
|
||||
response: Response
|
||||
logger: Logger
|
||||
export interface RequestParams {
|
||||
args: Object
|
||||
headers: Object
|
||||
request: Request
|
||||
client: WSClient
|
||||
caller: Caller
|
||||
session: Session
|
||||
response: Response
|
||||
logger: Logger
|
||||
}
|
||||
|
||||
interface OnMessageParams {
|
||||
type: string
|
||||
data: string | Object
|
||||
client: WSClient
|
||||
session: Session
|
||||
logger: Logger
|
||||
export interface OnMessageParams {
|
||||
type: string
|
||||
data: string | Object
|
||||
client: WSClient
|
||||
session: Session
|
||||
logger: Logger
|
||||
}
|
||||
|
||||
interface WSClient {
|
||||
id: string
|
||||
read: () => WSMessage
|
||||
write: (data: any) => void
|
||||
writeMessage: (type: string, data: any) => void
|
||||
ping: () => void
|
||||
close: () => void
|
||||
export interface WSClient {
|
||||
id: string
|
||||
read: () => WSMessage
|
||||
write: (data: any) => void
|
||||
writeMessage: (type: string, data: any) => void
|
||||
ping: () => void
|
||||
close: () => void
|
||||
}
|
||||
|
||||
interface WSMessage {
|
||||
type: string
|
||||
data: string | Object
|
||||
export interface WSMessage {
|
||||
type: string
|
||||
data: string | Object
|
||||
}
|
||||
|
||||
interface Session {
|
||||
set: (key: string | Object, value?: any) => void
|
||||
get: (...keys: string[]) => any | Object
|
||||
remove: (...keys: string[]) => void
|
||||
setAuthLevel: (authLevel: number) => void
|
||||
save: () => void
|
||||
export interface Session {
|
||||
set: (key: string | Object, value?: any) => void
|
||||
get: (...keys: string[]) => any | Object
|
||||
remove: (...keys: string[]) => void
|
||||
setAuthLevel: (authLevel: number) => void
|
||||
save: () => void
|
||||
}
|
||||
|
||||
interface Caller {
|
||||
get(url: string, headers?: Object): Result
|
||||
head(url: string, headers?: Object): Result
|
||||
post(url: string, data: any, headers?: Object): Result
|
||||
put(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
|
||||
export interface Caller {
|
||||
get(url: string, headers?: Object): Result
|
||||
head(url: string, headers?: Object): Result
|
||||
post(url: string, data: any, headers?: Object): Result
|
||||
put(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
|
||||
}
|
||||
|
||||
interface Result {
|
||||
status: string
|
||||
statusCode: number
|
||||
headers: Object
|
||||
bytes(): Uint8Array
|
||||
string(): string
|
||||
object(): Object
|
||||
export interface Result {
|
||||
status: string
|
||||
statusCode: number
|
||||
headers: Object
|
||||
bytes(): Uint8Array
|
||||
string(): string
|
||||
object(): Object
|
||||
}
|
||||
|
||||
interface CookieOption {
|
||||
path: string
|
||||
domain: string
|
||||
expires: any
|
||||
maxAge: number
|
||||
secure: boolean
|
||||
httpOnly: boolean
|
||||
export interface CookieOption {
|
||||
path: string
|
||||
domain: string
|
||||
expires: any
|
||||
maxAge: number
|
||||
secure: boolean
|
||||
httpOnly: boolean
|
||||
}
|
||||
|
||||
interface Request {
|
||||
id: string
|
||||
proto: string
|
||||
scheme: string
|
||||
host: string
|
||||
method: string
|
||||
path: string
|
||||
remoteAddr: string
|
||||
realIP: string
|
||||
referer: string
|
||||
userAgent: string
|
||||
url: string
|
||||
contentLength: number
|
||||
cookies: Object
|
||||
headers: Object
|
||||
args: Object
|
||||
files: Map<string, UploadFile>
|
||||
multiFiles: Map<string, UploadFile[]>
|
||||
makeURL: (path: string) => string
|
||||
readAll: () => any
|
||||
read: (size: number) => any
|
||||
close: () => void
|
||||
get: (key: string) => any
|
||||
set: (key: string, value: any) => void
|
||||
getHeader: (key: string) => string
|
||||
setHeader: (key: string, value: string) => void
|
||||
setUserID: (id: string) => void
|
||||
export interface Request {
|
||||
id: string
|
||||
proto: string
|
||||
scheme: string
|
||||
host: string
|
||||
method: string
|
||||
path: string
|
||||
remoteAddr: string
|
||||
realIP: string
|
||||
referer: string
|
||||
userAgent: string
|
||||
url: string
|
||||
contentLength: number
|
||||
cookies: Object
|
||||
headers: Object
|
||||
args: Object
|
||||
files: Map<string, UploadFile>
|
||||
multiFiles: Map<string, UploadFile[]>
|
||||
makeURL: (path: string) => string
|
||||
readAll: () => any
|
||||
read: (size: number) => any
|
||||
close: () => void
|
||||
get: (key: string) => any
|
||||
set: (key: string, value: any) => void
|
||||
getHeader: (key: string) => string
|
||||
setHeader: (key: string, value: string) => void
|
||||
setUserID: (id: string) => void
|
||||
}
|
||||
|
||||
interface Response {
|
||||
id: string
|
||||
setStatus: (code: number) => void
|
||||
setCookie: (name: string, value: string, option?: CookieOption) => void
|
||||
setHeader: (name: string, value: string) => void
|
||||
addHeader: (name: string, value: string) => void
|
||||
getHeader: (name: string) => string
|
||||
write: (data: any) => number
|
||||
flush: () => void
|
||||
sendFile: (contentType: string, filename: string) => void
|
||||
downloadFile: (contentType: string, filename: string, data: any) => void
|
||||
location: (url: string) => void
|
||||
end: (data: any) => void
|
||||
export interface Response {
|
||||
id: string
|
||||
setStatus: (code: number) => void
|
||||
setCookie: (name: string, value: string, option?: CookieOption) => void
|
||||
setHeader: (name: string, value: string) => void
|
||||
addHeader: (name: string, value: string) => void
|
||||
getHeader: (name: string) => string
|
||||
write: (data: any) => number
|
||||
flush: () => void
|
||||
sendFile: (contentType: string, filename: string) => void
|
||||
downloadFile: (contentType: string, filename: string, data: any) => void
|
||||
location: (url: string) => void
|
||||
end: (data: any) => void
|
||||
}
|
||||
|
||||
interface Logger {
|
||||
debug: (message: string, info?: Object) => void
|
||||
info: (message: string, info?: Object) => void
|
||||
warn: (message: string, info?: Object) => void
|
||||
error: (message: string, info?: Object) => void
|
||||
export interface Logger {
|
||||
debug: (message: string, info?: Object) => void
|
||||
info: (message: string, info?: Object) => void
|
||||
warn: (message: string, info?: Object) => void
|
||||
error: (message: string, info?: Object) => void
|
||||
}
|
||||
|
||||
interface UploadFile {
|
||||
name: string
|
||||
size: number
|
||||
data: any
|
||||
export interface UploadFile {
|
||||
name: string
|
||||
size: number
|
||||
data: any
|
||||
}
|
||||
|
||||
@ -45,7 +45,9 @@ func TestWS(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStopWSByPool(t *testing.T) {
|
||||
fmt.Println(u.BGreen("stop ws"))
|
||||
_, err := rt3.RunCode("s.stop();task.stop();")
|
||||
fmt.Println(u.BGreen("stop ws ok"), err)
|
||||
if err != nil {
|
||||
t.Fatal("stop failed", err)
|
||||
}
|
||||
|
||||
@ -3,32 +3,29 @@ import co from 'apigo.cc/gojs/console'
|
||||
import task from 'apigo.cc/gojs/task'
|
||||
|
||||
function onStart() {
|
||||
co.info('task start')
|
||||
co.info('task start')
|
||||
}
|
||||
|
||||
let i = 0
|
||||
function onRun() {
|
||||
let keys = task.keys('wsTest_')
|
||||
// let connCount = s.dataCount('wsTest')
|
||||
if (keys.length > 0) {
|
||||
// let conns = s.dataFetch('wsTest')
|
||||
let conns = task.getAll('wsTest_')
|
||||
for (let id in conns) {
|
||||
let conn = conns[id]
|
||||
try {
|
||||
conn.write(i++)
|
||||
} catch (e) {
|
||||
co.error(e)
|
||||
task.remove(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
co.info('task run', keys.length)
|
||||
let keys = task.keys('wsTest_')
|
||||
// let connCount = s.dataCount('wsTest')
|
||||
if (keys.length > 0) {
|
||||
// let conns = s.dataFetch('wsTest')
|
||||
let conns = task.getAll('wsTest_')
|
||||
for (let id in conns) {
|
||||
let conn = conns[id]
|
||||
if (!conn.write(i++)) {
|
||||
task.remove(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
co.info('task run', keys.length)
|
||||
}
|
||||
|
||||
|
||||
function onStop() {
|
||||
// s.dataRemove('wsTest')
|
||||
task.removeAll('wsTest_')
|
||||
co.info('task stop')
|
||||
// s.dataRemove('wsTest')
|
||||
task.removeAll('wsTest_')
|
||||
co.info('task stop')
|
||||
}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import s from "apigo.cc/gojs/service"
|
||||
|
||||
function main(args) {
|
||||
s.setTplFunc({
|
||||
bb: text => { return '<b>' + text + '</b>' }
|
||||
})
|
||||
return s.tpl('tpl/page.html', { title: 'Abc' })
|
||||
return s.tpl('tpl/page.html', { title: 'Abc' }, {
|
||||
bb: text => { return '<b>' + text + '</b>' }
|
||||
})
|
||||
}
|
||||
@ -1,3 +1,3 @@
|
||||
<header>
|
||||
<h1>Welcome to {{bb .title}}</h1>
|
||||
<h1>Welcome to {{bb .title}}</h1>
|
||||
</header>
|
||||
@ -2,15 +2,15 @@
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>{{.title}}</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>{{.title}}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{template "header.html" .}}
|
||||
<pre>
|
||||
{{template "header.html" .}}
|
||||
<pre>
|
||||
hello world
|
||||
</pre>
|
||||
</body>
|
||||
|
||||
33
ws.go
33
ws.go
@ -18,7 +18,10 @@ func MakeWSClient(client *websocket.Conn, id string) gojs.Map {
|
||||
"read": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
typ, data, err := readWSMessage(client)
|
||||
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{
|
||||
"type": typ,
|
||||
@ -37,16 +40,22 @@ func MakeWSClient(client *websocket.Conn, id string) gojs.Map {
|
||||
err = client.WriteMessage(websocket.TextMessage, u.JsonBytes(args.Any(0)))
|
||||
}
|
||||
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 nil
|
||||
return vm.ToValue(true)
|
||||
},
|
||||
"ping": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
err := client.WriteMessage(websocket.PingMessage, 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 nil
|
||||
return vm.ToValue(true)
|
||||
},
|
||||
"writeMessage": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
args := gojs.MakeArgs(&argsIn, vm).Check(2)
|
||||
@ -68,16 +77,22 @@ func MakeWSClient(client *websocket.Conn, id string) gojs.Map {
|
||||
err = client.WriteMessage(websocket.TextMessage, args.Bytes(1))
|
||||
}
|
||||
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 nil
|
||||
return vm.ToValue(true)
|
||||
},
|
||||
"close": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||
err := client.Close()
|
||||
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 nil
|
||||
return vm.ToValue(true)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user