diff --git a/.gitignore b/.gitignore index 8f15271..0cebc97 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,6 @@ !.gitignore go.sum /build -/node_modules -/package.json /bak node_modules package.json diff --git a/go.mod b/go.mod index 67f3830..010a9ba 100644 --- a/go.mod +++ b/go.mod @@ -3,29 +3,32 @@ module apigo.cc/gojs/service go 1.18 require ( - apigo.cc/gojs v0.0.4 - apigo.cc/gojs/console v0.0.1 + apigo.cc/gojs v0.0.7 + apigo.cc/gojs/console v0.0.2 apigo.cc/gojs/http v0.0.3 - apigo.cc/gojs/util v0.0.3 + apigo.cc/gojs/util v0.0.7 github.com/gorilla/websocket v1.5.3 - github.com/ssgo/config v1.7.8 + github.com/ssgo/config v1.7.9 github.com/ssgo/discover v1.7.9 github.com/ssgo/httpclient v1.7.8 github.com/ssgo/log v1.7.7 github.com/ssgo/redis v1.7.7 - github.com/ssgo/s v1.7.18 + github.com/ssgo/s v1.7.20 github.com/ssgo/standard v1.7.7 - github.com/ssgo/u v1.7.9 + github.com/ssgo/u v1.7.12 ) require ( + github.com/ZZMarquis/gm v1.3.2 // indirect github.com/dlclark/regexp2 v1.11.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/emmansun/gmsm v0.29.4 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect github.com/gomodule/redigo v1.9.2 // indirect github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect + github.com/obscuren/ecies v0.0.0-20150213224233-7c0f4a9b18d9 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/shirou/gopsutil/v3 v3.24.5 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -33,8 +36,9 @@ require ( github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.9.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/service.go b/service.go index 74fceb5..e8861a0 100644 --- a/service.go +++ b/service.go @@ -1,15 +1,19 @@ package service import ( + "bytes" _ "embed" "encoding/json" "errors" "fmt" + "os" "path/filepath" "reflect" "regexp" "strings" "sync" + "syscall" + "text/template" "time" "apigo.cc/gojs" @@ -39,6 +43,16 @@ var poolActionRegistered = map[string]bool{} var poolsLock = sync.RWMutex{} var waitChan chan bool +type TplCache struct { + FileModTime map[string]int64 + Tpl *template.Template +} + +var tplFunc = map[string]any{} +var tplFuncLock = sync.RWMutex{} +var tplCache = map[string]*TplCache{} +var tplCacheLock = sync.RWMutex{} + type LimiterConfig struct { From string Time int @@ -69,112 +83,117 @@ var onStop goja.Callable var limiters = map[string]*s.Limiter{} var configed = false +func initConfig(opt *gojs.Obj, logger *log.Logger, vm *goja.Runtime) { + configed = true + s.InitConfig() + if startPath, ok := vm.GoData["startPath"]; ok { + s.SetWorkPath(u.String(startPath)) + } + // 处理配置 + serviceConfig = Config{"Session", "Device", "Client", "userId", "", 3600, "auth failed", "verify failed", "too many requests", nil, "", map[string]string{}, map[string]string{}, map[string]string{}} + if errs := config.LoadConfig("service", &serviceConfig); errs != nil && len(errs) > 0 { + panic(vm.NewGoError(errs[0])) + } + config.LoadConfig("service", &discover.Config) + // var auth goja.Callable + if opt != nil { + u.Convert(opt.O.Export(), &serviceConfig) + u.Convert(opt.O.Export(), &s.Config) + u.Convert(opt.O.Export(), &discover.Config) + // auth = conf.Func("auth") + onStop = opt.Func("onStop") + + if serviceConfig.SessionProvider != "" { + sessionRedis = redis.GetRedis(serviceConfig.SessionProvider, logger) + } + sessionTimeout = serviceConfig.SessionTimeout + if sessionTimeout < 0 { + sessionTimeout = 0 + } + } + + // 身份验证和Session + authAccessToken := len(s.Config.AccessTokens) > 0 + s.SetClientKeys(serviceConfig.DeviceKey, serviceConfig.ClientKey, serviceConfig.SessionKey) + if serviceConfig.SessionKey != "" { + s.SetAuthChecker(func(authLevel int, logger *log.Logger, url *string, args map[string]any, request *s.Request, response *s.Response, options *s.WebServiceOptions) (pass bool, object any) { + var session *Session + setAuthLevel := 0 + if serviceConfig.SessionKey != "" { + sessionID := request.GetSessionId() + + if sessionID != "" { + session = NewSession(sessionID, logger) + } + if userId, ok := session.data[serviceConfig.UserIdKey]; ok { + request.SetUserId(u.String(userId)) + } + // 优先使用session中的authLevel + if authLevel > 0 { + if authLevelBySession, ok := session.data["_authLevel"]; ok { + setAuthLevel = u.Int(authLevelBySession) + } + } + } + // 如果没有session中的authLevel验证失败,则使用Access-Token中的authLevel(服务间调用) + if authAccessToken && setAuthLevel < authLevel { + setAuthLevel = s.GetAuthTokenLevel(request.Header.Get("Access-Token")) + } + if setAuthLevel >= authLevel { + return true, session + } else { + msg := serviceConfig.AuthFieldMessage + if strings.Contains(msg, "{{") { + msg = strings.ReplaceAll(msg, "{{TARGET_AUTHLEVEL}}", u.String(authLevel)) + msg = strings.ReplaceAll(msg, "{{USER_AUTHLEVEL}}", u.String(setAuthLevel)) + } + var obj any + if json.Unmarshal([]byte(msg), &obj) == nil { + return false, obj + } else { + return false, msg + } + } + }) + s.Init() + } + + // 限流器 + if serviceConfig.Limiters != nil { + var limiterRedis *redis.Redis + if serviceConfig.LimiterRedis != "" { + limiterRedis = redis.GetRedis(serviceConfig.LimiterRedis, logger) + } + for name, limiter := range serviceConfig.Limiters { + switch limiter.From { + case "ip": + limiter.From = "header." + standard.DiscoverHeaderClientIp + case "user": + limiter.From = "header." + standard.DiscoverHeaderUserId + case "device": + limiter.From = "header." + standard.DiscoverHeaderDeviceId + } + if limiterRedis != nil { + limiters[name] = s.NewLimiter(name, limiter.From, time.Duration(limiter.Time)*time.Millisecond, limiter.Times, limiterRedis) + } else { + limiters[name] = s.NewLocalLimiter(name, limiter.From, time.Duration(limiter.Time)*time.Millisecond, limiter.Times) + } + } + } +} + func init() { s.Config.KeepKeyCase = true obj := map[string]any{ "config": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { - configed = true - s.InitConfig() - if startPath, ok := vm.GoData["startPath"]; ok { - s.SetWorkPath(u.String(startPath)) - } - // 处理配置 args := gojs.MakeArgs(&argsIn, vm) - serviceConfig = Config{"Session", "Device", "Client", "userId", "", 3600, "auth failed", "verify failed", "too many requests", nil, "", map[string]string{}, map[string]string{}, map[string]string{}} - if errs := config.LoadConfig("service", &serviceConfig); errs != nil && len(errs) > 0 { - panic(vm.NewGoError(errs[0])) - } - config.LoadConfig("service", &discover.Config) - // var auth goja.Callable - if conf := args.Obj(0); conf != nil { - u.Convert(conf.O.Export(), &serviceConfig) - u.Convert(conf.O.Export(), &s.Config) - u.Convert(conf.O.Export(), &discover.Config) - // auth = conf.Func("auth") - onStop = conf.Func("onStop") - - if serviceConfig.SessionProvider != "" { - sessionRedis = redis.GetRedis(serviceConfig.SessionProvider, args.Logger) - } - sessionTimeout = serviceConfig.SessionTimeout - if sessionTimeout < 0 { - sessionTimeout = 0 - } - } - - // 身份验证和Session - authAccessToken := len(s.Config.AccessTokens) > 0 - s.SetClientKeys(serviceConfig.DeviceKey, serviceConfig.ClientKey, serviceConfig.SessionKey) - if serviceConfig.SessionKey != "" { - s.SetAuthChecker(func(authLevel int, logger *log.Logger, url *string, args map[string]any, request *s.Request, response *s.Response, options *s.WebServiceOptions) (pass bool, object any) { - var session *Session - setAuthLevel := 0 - if serviceConfig.SessionKey != "" { - sessionID := request.GetSessionId() - - if sessionID != "" { - session = NewSession(sessionID, logger) - } - if userId, ok := session.data[serviceConfig.UserIdKey]; ok { - request.SetUserId(u.String(userId)) - } - // 优先使用session中的authLevel - if authLevel > 0 { - if authLevelBySession, ok := session.data["_authLevel"]; ok { - setAuthLevel = u.Int(authLevelBySession) - } - } - } - // 如果没有session中的authLevel验证失败,则使用Access-Token中的authLevel(服务间调用) - if authAccessToken && setAuthLevel < authLevel { - setAuthLevel = s.GetAuthTokenLevel(request.Header.Get("Access-Token")) - } - if setAuthLevel >= authLevel { - return true, session - } else { - msg := serviceConfig.AuthFieldMessage - if strings.Contains(msg, "{{") { - msg = strings.ReplaceAll(msg, "{{TARGET_AUTHLEVEL}}", u.String(authLevel)) - msg = strings.ReplaceAll(msg, "{{USER_AUTHLEVEL}}", u.String(setAuthLevel)) - } - var obj any - if json.Unmarshal([]byte(msg), &obj) == nil { - return false, obj - } else { - return false, msg - } - } - }) - s.Init() - } - - // 限流器 - if serviceConfig.Limiters != nil { - var limiterRedis *redis.Redis - if serviceConfig.LimiterRedis != "" { - limiterRedis = redis.GetRedis(serviceConfig.LimiterRedis, args.Logger) - } - for name, limiter := range serviceConfig.Limiters { - switch limiter.From { - case "ip": - limiter.From = "header." + standard.DiscoverHeaderClientIp - case "user": - limiter.From = "header." + standard.DiscoverHeaderUserId - case "device": - limiter.From = "header." + standard.DiscoverHeaderDeviceId - } - if limiterRedis != nil { - limiters[name] = s.NewLimiter(name, limiter.From, time.Duration(limiter.Time)*time.Millisecond, limiter.Times, limiterRedis) - } else { - limiters[name] = s.NewLocalLimiter(name, limiter.From, time.Duration(limiter.Time)*time.Millisecond, limiter.Times) - } - } - } + initConfig(args.Obj(0), args.Logger, vm) return nil }, "start": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { if !configed { - panic(vm.NewGoError(errors.New("must run service.config frist"))) + initConfig(nil, gojs.GetLogger(vm), vm) + // panic(vm.NewGoError(errors.New("must run service.config frist"))) } if server != nil { panic(vm.NewGoError(errors.New("server already started"))) @@ -192,6 +211,55 @@ func init() { s.SetProxyBy(proxy) } + // 处理Watch + if vm.GoData["inWatch"] == true { + onWatchConn := map[string]*websocket.Conn{} + onWatchLock := sync.RWMutex{} + vm.GoData["onWatch"] = func(filename string) { + conns := map[string]*websocket.Conn{} + onWatchLock.RLock() + for id, conn := range onWatchConn { + conns[id] = conn + } + onWatchLock.RUnlock() + for id, conn := range conns { + if err := conn.WriteMessage(websocket.TextMessage, []byte(filename)); err != nil { + onWatchLock.Lock() + delete(onWatchConn, id) + onWatchLock.Unlock() + } else { + } + } + } + s.AddShutdownHook(func() { + for _, conn := range onWatchConn { + conn.Close() + } + }) + s.RegisterSimpleWebsocket(0, "/_watch", func(request *s.Request, conn *websocket.Conn) { + onWatchLock.Lock() + onWatchConn[request.Id] = conn + 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) + // 注入自动刷新的代码 + outStr = strings.ReplaceAll(outStr, "", ` +`) + return []byte(outStr), false + } + return nil, false + }) + } + // 启动服务 server = s.AsyncStart() waitChan = make(chan bool, 1) @@ -203,6 +271,8 @@ func init() { server.OnStopped(func() { ClearRewritesAndProxies() pools = map[string]*gojs.Pool{} + poolExists = map[string]bool{} + poolActionRegistered = map[string]bool{} server = nil if waitChan != nil { waitChan <- true @@ -217,6 +287,72 @@ func init() { server.Stop() 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)) + }, + "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 { args := gojs.MakeArgs(&argsIn, vm).Check(2) o := args.Obj(0) @@ -391,6 +527,100 @@ func init() { }) 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() + } + } + return nil + }, + "tpl": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := gojs.MakeArgs(&argsIn, vm).Check(2) + filename := args.Path(0) + info := u.GetFileInfo(filename) + if info == nil { + panic(vm.NewGoError(errors.New("tpl file " + filename + " not exists"))) + } + + data := args.Any(1) + tplCacheLock.RLock() + t := tplCache[filename] + tplCacheLock.RUnlock() + if t != nil { + for f, tm := range t.FileModTime { + info := u.GetFileInfo(f) + if info == nil || info.ModTime.UnixMilli() != tm { + t = nil + break + } + } + } + if t == nil { + tpl := template.New("main") + if len(tplFunc) > 0 { + tpl = tpl.Funcs(tplFunc) + } + + 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) + } + if info2 != nil { + tpl, err = tpl.Parse(`{{ define "` + m[1] + `" }}` + u.ReadFileN(includeFilename) + `{{ end }}`) + if err != nil { + panic(vm.NewGoError(err)) + } + fileModTime[includeFilename] = info2.ModTime.UnixMilli() + } + } + tpl, err = tpl.ParseFiles(filename) + if err != nil { + panic(vm.NewGoError(err)) + } + t = &TplCache{ + Tpl: tpl, + FileModTime: fileModTime, + } + } + + buf := bytes.NewBuffer(make([]byte, 0)) + err := t.Tpl.ExecuteTemplate(buf, filepath.Base(filename), data) + if err != nil { + panic(vm.NewGoError(err)) + } + return vm.ToValue(buf.String()) + }, "dataSet": DataSet, "dataGet": DataGet, "dataKeys": DataKeys, @@ -413,6 +643,12 @@ func init() { server.Stop() } }, + OnSignal: func(sig os.Signal) { + switch sig { + case syscall.SIGUSR1: + + } + }, WaitForStop: func() { if waitChan != nil { <-waitChan @@ -421,6 +657,8 @@ func init() { }) } +var tplIncludeMatcher = regexp.MustCompile(`{{\s*template\s+"([^"]+)"`) + func verifyRegexp(regexpStr string) func(any, *goja.Runtime) bool { if rx, err := regexp.Compile(regexpStr); err != nil { return func(value any, vm *goja.Runtime) bool { diff --git a/service.ts b/service.ts index a180d68..f6b6b74 100644 --- a/service.ts +++ b/service.ts @@ -18,6 +18,12 @@ export default { listPop, listCount, listRemove, + id, + idL, + uniqueId, + uniqueIdL, + setTplFunc, + tpl, } function config(config?: Config): void { } @@ -41,7 +47,12 @@ function listPush(scope: string, key: string, value: any): void { } function listPop(scope: string, key: string): any { return null } function listCount(scope: string): number { return 0 } function listRemove(scope: string): void { } - +function id(space: string, 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 '' } interface Config { // github.com/ssgo/s 的配置参数 diff --git a/tests/service_test.go b/tests/service_test.go index 65a879d..29ab9f8 100644 --- a/tests/service_test.go +++ b/tests/service_test.go @@ -1,6 +1,7 @@ package service_test import ( + "fmt" "testing" "time" @@ -9,6 +10,7 @@ import ( _ "apigo.cc/gojs/http" _ "apigo.cc/gojs/service" _ "apigo.cc/gojs/util" + "github.com/ssgo/httpclient" "github.com/ssgo/u" ) @@ -41,80 +43,80 @@ func TestStatic(t *testing.T) { } } -// func TestJsEcho(t *testing.T) { -// for i := 0; i < runTimes; i++ { -// name := u.UniqueId() -// r, err := rt.RunCode("test('" + name + "')") -// if err != nil { -// t.Fatal("test js get failed, got error", err) -// } else if r != name { -// t.Fatal("test js get failed, name not match", r, name) -// } -// } -// } +func TestJsEcho(t *testing.T) { + for i := 0; i < runTimes; i++ { + name := u.UniqueId() + r, err := rt.RunCode("test('" + name + "')") + if err != nil { + t.Fatal("test js get failed, got error", err) + } else if r != name { + t.Fatal("test js get failed, name not match", r, name) + } + } +} -// func TestGoEcho(t *testing.T) { -// hc := httpclient.GetClientH2C(0) -// for i := 0; i < runTimes; i++ { -// name := u.UniqueId() -// r := hc.Get("http://" + addr + "/echo?name=" + name) -// if r.Error != nil { -// t.Fatal("test go get failed, got error", r.Error) -// } else if r.String() != name { -// t.Fatal("test go get failed, name not match", r, name) -// } -// } -// } +func TestGoEcho(t *testing.T) { + hc := httpclient.GetClientH2C(0) + for i := 0; i < runTimes; i++ { + name := u.UniqueId() + r := hc.Get("http://" + addr + "/echo?name=" + name) + if r.Error != nil { + t.Fatal("test go get failed, got error", r.Error) + } else if r.String() != name { + t.Fatal("test go get failed, name not match", r, name) + } + } +} -// func TestJsAsyncEcho(t *testing.T) { -// ch := make(chan bool, runTimes) -// t1 := time.Now().UnixMilli() -// for i := 0; i < runTimes; i++ { -// go func() { -// name := u.UniqueId() -// r, err := rt.RunCode("test('" + name + "')") -// ch <- true -// if err != nil { -// t.Fatal("test js async get failed, got error", err) -// } else if r != name { -// t.Fatal("test js async get failed, name not match", r, name) -// } -// }() -// } -// for i := 0; i < runTimes; i++ { -// <-ch -// } -// t2 := time.Now().UnixMilli() - t1 -// fmt.Println(u.BGreen("js async test time:"), t2, "ms") -// } +func TestJsAsyncEcho(t *testing.T) { + ch := make(chan bool, runTimes) + t1 := time.Now().UnixMilli() + for i := 0; i < runTimes; i++ { + go func() { + name := u.UniqueId() + r, err := rt.RunCode("test('" + name + "')") + ch <- true + if err != nil { + t.Fatal("test js async get failed, got error", err) + } else if r != name { + t.Fatal("test js async get failed, name not match", r, name) + } + }() + } + for i := 0; i < runTimes; i++ { + <-ch + } + t2 := time.Now().UnixMilli() - t1 + fmt.Println(u.BGreen("js async test time:"), t2, "ms") +} -// func TestGoAsyncEcho(t *testing.T) { -// hc := httpclient.GetClientH2C(0) -// ch := make(chan bool, runTimes*10) -// t1 := time.Now().UnixMilli() -// lastName := "" -// lastResult := "" -// for i := 0; i < runTimes*10; i++ { -// name := fmt.Sprint("N", i) -// lastName = name -// go func() { -// r := hc.Get("http://" + addr + "/echo?name=" + name) -// lastResult = r.String() -// ch <- true -// if r.Error != nil { -// t.Fatal("test go async get failed, got error", r.Error) -// } else if r.String() != name { -// t.Fatal("test go async get failed, name not match", r, name) -// } -// }() -// } -// for i := 0; i < runTimes*10; i++ { -// <-ch -// } -// t2 := time.Now().UnixMilli() - t1 -// fmt.Println(u.BGreen("go async test time:"), t2, "ms") -// fmt.Println(u.BGreen("last name:"), lastName, lastResult) -// } +func TestGoAsyncEcho(t *testing.T) { + hc := httpclient.GetClientH2C(0) + ch := make(chan bool, runTimes*10) + t1 := time.Now().UnixMilli() + lastName := "" + lastResult := "" + for i := 0; i < runTimes*10; i++ { + name := fmt.Sprint("N", i) + lastName = name + go func() { + r := hc.Get("http://" + addr + "/echo?name=" + name) + lastResult = r.String() + ch <- true + if r.Error != nil { + t.Fatal("test go async get failed, got error", r.Error) + } else if r.String() != name { + t.Fatal("test go async get failed, name not match", r, name) + } + }() + } + for i := 0; i < runTimes*10; i++ { + <-ch + } + t2 := time.Now().UnixMilli() - t1 + fmt.Println(u.BGreen("go async test time:"), t2, "ms") + fmt.Println(u.BGreen("last name:"), lastName, lastResult) +} func TestStop(t *testing.T) { go func() { diff --git a/tests/tpl.js b/tests/tpl.js new file mode 100644 index 0000000..2b47860 --- /dev/null +++ b/tests/tpl.js @@ -0,0 +1,8 @@ +import s from "apigo.cc/gojs/service" + +function main(args) { + s.setTplFunc({ + bb: text => { return '' + text + '' } + }) + return s.tpl('tpl/page.html', { title: 'Abc' }) +} \ No newline at end of file diff --git a/tests/tpl/header.html b/tests/tpl/header.html new file mode 100644 index 0000000..a175b49 --- /dev/null +++ b/tests/tpl/header.html @@ -0,0 +1,3 @@ +
+

Welcome to {{bb .title}}

+
\ No newline at end of file diff --git a/tests/tpl/page.html b/tests/tpl/page.html new file mode 100644 index 0000000..939ba35 --- /dev/null +++ b/tests/tpl/page.html @@ -0,0 +1,18 @@ + + + + + + + + {{.title}} + + + + {{template "header.html" .}} +
+      hello world
+    
+ + + \ No newline at end of file diff --git a/tests/tpl_test.go b/tests/tpl_test.go new file mode 100644 index 0000000..8d7b9ab --- /dev/null +++ b/tests/tpl_test.go @@ -0,0 +1,20 @@ +package service_test + +import ( + "strings" + "testing" + + "apigo.cc/gojs" + _ "apigo.cc/gojs/service" + "github.com/ssgo/u" +) + +func TestTpl(t *testing.T) { + r, err := gojs.RunFile("tpl.js") + if err != nil { + t.Fatal("test static failed, got error", err) + } + if !strings.Contains(u.String(r), "

Welcome to Abc

") { + t.Fatal("test tpl failed, name not match", r) + } +}