commit c78548f50de1a861877d98b8dcea19f987108ce2 Author: Star Date: Tue Oct 15 10:45:04 2024 +0800 1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..867e853 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.* +!.gitignore +go.sum +node_modules +package.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d894367 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2024 apigo + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ad59916 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# http for GoJS + +## usage + +```go +import ( + "apigo.cc/gojs" + _ "apigo.cc/gojs/http" +) + +func main() { + r, err := gojs.Run(` +import http from 'apigo.cc/gojs/http' + +function main(args){ + return http.get('http://......') +} + `, "test.js") + + fmt.Println(r, err) +} +``` + +## websocket + +```javascript +import http from 'apigo.cc/gojs/http' +let conn http.connect('ws://......') +conn.write('hello world') +let r = conn.read() +conn.close() +``` + +## module.exports + +```ts +function new(config?: Config): Client +function newH2C(config?: Config): Client +function get(url: string, headers?: Object): Result +function head(url: string, headers?: Object): Result +function post(url: string, data: any, headers?: Object): Result +function put(url: string, data: any, headers?: Object): Result +function delete(url: string, data: any, headers?: Object): Result +function do(method: string, url: string, data: any, callback?: (data: string) => void, headers?: Object): Result +function upload(url: string, form: Object, files: Object, headers?: Object): Result +function download(filename: string, url: string, callback?: (finished: number, total: number) => void, headers?: Object): Result +function connect(url: string, config?: WSConfig): WS + +``` + +## full api see [http.ts](https://apigo.cc/gojs/http/http.ts) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3dd6592 --- /dev/null +++ b/go.mod @@ -0,0 +1,41 @@ +module apigo.cc/gojs/http + +go 1.18 + +require ( + apigo.cc/gojs v0.0.1 + github.com/ssgo/httpclient v1.7.8 + github.com/ssgo/u v1.7.9 +) + +require ( + apigo.cc/gojs/console v0.0.1 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/gomodule/redigo v1.9.2 // indirect + github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // 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 + github.com/ssgo/discover v1.7.8 // indirect + github.com/ssgo/redis v1.7.7 // indirect + github.com/tklauser/go-sysconf v0.3.14 // indirect + github.com/tklauser/numcpus v0.8.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect +) + +require ( + github.com/dlclark/regexp2 v1.11.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect + github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect + github.com/gorilla/websocket v1.5.3 + github.com/ssgo/config v1.7.7 // indirect + github.com/ssgo/log v1.7.7 + github.com/ssgo/s v1.7.13 + github.com/ssgo/standard v1.7.7 // indirect + github.com/ssgo/tool v0.4.27 // 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 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/http.go b/http.go new file mode 100644 index 0000000..82b42e5 --- /dev/null +++ b/http.go @@ -0,0 +1,524 @@ +package http + +import ( + _ "embed" + "net/http" + "reflect" + "strings" + "sync" + "time" + + "apigo.cc/gojs" + "apigo.cc/gojs/goja" + "github.com/gorilla/websocket" + "github.com/ssgo/httpclient" + "github.com/ssgo/log" + "github.com/ssgo/u" +) + +//go:embed http.ts +var httpTS string + +type Http struct { + client *httpclient.ClientPool + baseURL string + globalHeaders map[string]string +} + +var defaultHttp = &Http{ + client: httpclient.GetClient(60000 * time.Millisecond), + globalHeaders: make(map[string]string), +} + +// TODO ws +func init() { + obj := map[string]any{ + "new": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + return newClient("HTTP", argsIn, vm) + }, + "newH2C": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + return newClient("H2C", argsIn, vm) + }, + "get": defaultHttp.Get, + "head": defaultHttp.Head, + "post": defaultHttp.Post, + "put": defaultHttp.Put, + "delete": defaultHttp.Delete, + "do": defaultHttp.Do, + "upload": defaultHttp.Upload, + "download": defaultHttp.Download, + "connect": defaultHttp.Connect, + } + + gojs.Register("apigo.cc/gojs/http", gojs.Module{ + Object: obj, + TsCode: httpTS, + Desc: "", + Example: "", + }) +} + +func newClient(portal string, argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := gojs.MakeArgs(&argsIn, vm).Check(0) + opt := args.Obj(0) + timeout := 60000 * time.Millisecond + if opt != nil { + timeout = time.Duration(opt.Int64("timeout")) * time.Millisecond + } + var client *httpclient.ClientPool + if portal == "H2C" { + client = httpclient.GetClientH2C(timeout) + } else { + client = httpclient.GetClient(timeout) + } + cli := &Http{ + client: client, + globalHeaders: make(map[string]string), + } + setConfig(cli, opt) + return vm.ToValue(gojs.MakeMap(cli)) +} + +func setConfig(cli *Http, opt *gojs.Obj) { + if opt != nil { + if globalHeaders := opt.Map("globalHeaders"); globalHeaders != nil { + for k, v := range globalHeaders { + cli.globalHeaders[k] = u.String(v) + } + } + if baseURL := opt.Str("baseURL"); baseURL != "" { + cli.baseURL = baseURL + } + if downloadPartSize := opt.Int64("downloadPartSize"); downloadPartSize != 0 { + cli.client.DownloadPartSize = downloadPartSize + } + if redirect := opt.Bool("redirect"); redirect { + cli.client.EnableRedirect() + } + } +} + +func makeResult(r *httpclient.Result, vm *goja.Runtime) goja.Value { + if r.Error != nil { + panic(vm.NewGoError(r.Error)) + } + headers := map[string]string{} + for k, v := range r.Response.Header { + headers[k] = v[0] + } + return vm.ToValue(map[string]any{ + "status": r.Response.Status, + "statusCode": r.Response.StatusCode, + "headers": headers, + "_data": r.Bytes(), + "bytes": toBytes, + "string": toString, + "object": toObject, + }) +} + +func toBytes(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + dataValue := argsIn.This.ToObject(vm).Get("_data") + if _, ok := dataValue.Export().([]byte); ok { + return dataValue + } + return nil +} + +func toString(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + dataValue := argsIn.This.ToObject(vm).Get("_data") + if data, ok := dataValue.Export().([]byte); ok { + return vm.ToValue(string(data)) + } + return nil +} + +func toObject(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + dataValue := argsIn.This.ToObject(vm).Get("_data") + if data, ok := dataValue.Export().([]byte); ok { + obj := u.UnJsonBytes(data, nil) + v := u.FinalValue(reflect.ValueOf(obj)) + return vm.ToValue(v.Interface()) + } + return nil +} + +func (hc *Http) makeURL(url string) string { + if !strings.Contains(url, "://") && hc.baseURL != "" { + if strings.HasSuffix(hc.baseURL, "/") && strings.HasPrefix(url, "/") { + return hc.baseURL + url[1:] + } else if !strings.HasSuffix(hc.baseURL, "/") && !strings.HasPrefix(url, "/") { + return hc.baseURL + "/" + url + } + return hc.baseURL + url + } + return url +} + +func (hc *Http) makeHeaderArray(in map[string]any) []string { + out := make([]string, 0) + if hc.globalHeaders != nil { + for k, v := range hc.globalHeaders { + out = append(out, k, v) + } + } + if in != nil { + for k, v := range in { + out = append(out, k, u.String(v)) + } + } + return out +} + +func (hc *Http) Get(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := gojs.MakeArgs(&argsIn, vm).Check(1) + return makeResult(hc.client.Get(hc.makeURL(args.Str(0)), hc.makeHeaderArray(args.Map(1))...), vm) +} + +func (hc *Http) Head(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := gojs.MakeArgs(&argsIn, vm).Check(1) + return makeResult(hc.client.Head(hc.makeURL(args.Str(0)), hc.makeHeaderArray(args.Map(1))...), vm) +} + +func (hc *Http) Post(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := gojs.MakeArgs(&argsIn, vm).Check(2) + return makeResult(hc.client.Post(hc.makeURL(args.Str(0)), args.Any(1), hc.makeHeaderArray(args.Map(2))...), vm) +} + +func (hc *Http) Put(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := gojs.MakeArgs(&argsIn, vm).Check(2) + return makeResult(hc.client.Put(hc.makeURL(args.Str(0)), args.Any(1), hc.makeHeaderArray(args.Map(2))...), vm) +} + +func (hc *Http) Delete(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := gojs.MakeArgs(&argsIn, vm).Check(2) + return makeResult(hc.client.Delete(hc.makeURL(args.Str(0)), args.Any(1), hc.makeHeaderArray(args.Map(2))...), vm) +} + +func (hc *Http) Do(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := gojs.MakeArgs(&argsIn, vm).Check(3) + if len(argsIn.Arguments) == 3 { + argsIn.Arguments = append(argsIn.Arguments, vm.ToValue(nil)) + } + var r *httpclient.Result + if cb, ok := goja.AssertFunction(argsIn.Arguments[3]); ok { + r = hc.client.ManualDo(hc.makeURL(args.Str(0)), args.Str(1), args.Any(2), hc.makeHeaderArray(args.Map(4))...) + buf := make([]byte, 1024) + for { + n, err := r.Response.Body.Read(buf) + if err != nil { + break + } + _, _ = cb(argsIn.This, vm.ToValue(u.String(buf[0:n]))) + } + } else { + r = hc.client.Do(hc.makeURL(args.Str(0)), args.Str(1), args.Any(2), hc.makeHeaderArray(args.Map(4))...) + } + return makeResult(r, vm) +} + +func (hc *Http) Upload(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := gojs.MakeArgs(&argsIn, vm).Check(2) + postData := map[string]string{} + postFiles := map[string]any{} + u.Convert(args.Any(1), &postData) + if len(argsIn.Arguments) > 2 { + u.Convert(args.Any(2), &postFiles) + } + r, _ := hc.client.MPost(hc.makeURL(args.Str(0)), postData, postFiles, hc.makeHeaderArray(args.Map(3))...) + return makeResult(r, vm) +} + +func (hc *Http) Download(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := gojs.MakeArgs(&argsIn, vm).Check(2) + var r *httpclient.Result + var callback goja.Callable + if len(argsIn.Arguments) > 2 { + if cb, ok := goja.AssertFunction(argsIn.Arguments[2]); ok { + callback = cb + } + } + if callback != nil { + r, _ = hc.client.Download(hc.makeURL(args.Str(0)), args.Str(1), func(start, end int64, ok bool, finished, total int64) { + _, _ = callback(argsIn.This, vm.ToValue(finished), vm.ToValue(total)) + }, hc.makeHeaderArray(args.Map(3))...) + } else { + r, _ = hc.client.Download(hc.makeURL(args.Str(0)), args.Str(1), nil, hc.makeHeaderArray(args.Map(3))...) + } + return makeResult(r, vm) +} + +func (hc *Http) Connect(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := gojs.MakeArgs(&argsIn, vm).Check(1) + url := hc.makeURL(args.Str(0)) + if strings.HasPrefix(url, "http") { + url = strings.Replace(url, "http", "ws", 1) + } + + ws := &WS{url: url, this: args.This, running: false, pingStopChan: make(chan bool, 1), closeChan: make(chan bool, 1), logger: args.Logger, headers: make(map[string]string)} + + for k, v := range hc.globalHeaders { + ws.headers[k] = v + } + if opt := args.Obj(1); opt != nil { + if headers := opt.Map("headers"); headers != nil { + for k, v := range headers { + ws.headers[k] = u.String(v) + } + } + ws.compress = opt.Bool("compress") + ws.onOpen = opt.Func("onOpen") + ws.onClose = opt.Func("onClose") + ws.onError = opt.Func("onError") + // ws.onPing = opt.Func("onPing") + // ws.onPong = opt.Func("onPong") + ws.onMessage = opt.Func("onMessage") + ws.onJSONMessage = opt.Func("onJSONMessage") + ws.pingInterval = opt.Int64("pingInterval") + ws.reconnectInterval = opt.Int64("reconnectInterval") + if ws.reconnectInterval == 0 && (ws.onMessage != nil || ws.onJSONMessage != nil) { + ws.reconnectInterval = 1000 + } + } + // fmt.Println(u.BMagenta("WS"), "start") + if err := ws.connect(vm); err == nil { + if ws.pingInterval > 0 { + // fmt.Println(u.BMagenta("WS"), "start ping") + go func() { + // ws.sleep(time.Duration(ws.pingInterval) * time.Millisecond) + for { + if ws.conn != nil { + // fmt.Println(u.BMagenta("WS"), "ping") + ws.writeLock.Lock() + ws.conn.WriteMessage(websocket.PingMessage, []byte{'P'}) + ws.writeLock.Unlock() + } + if !ws.running { + break + } + ws.sleep(time.Duration(ws.pingInterval) * time.Millisecond) + if !ws.running { + break + } + } + // fmt.Println(u.BMagenta("WS"), "stop ping") + ws.pingStopChan <- true + }() + } else { + ws.pingStopChan <- true + } + + if ws.onMessage != nil || ws.onJSONMessage != nil { + // fmt.Println(u.BMagenta("WS"), "start onMessage") + go func() { + for { + for { + if ws.conn != nil { + if ws.onJSONMessage != nil { + var obj interface{} + err := ws.conn.ReadJSON(&obj) + if err != nil { + break + } + _, _ = ws.onJSONMessage(ws.this, vm.ToValue(obj)) + } else { + typ, buf, err := ws.conn.ReadMessage() + if err != nil { + break + } + if typ == websocket.TextMessage { + _, _ = ws.onMessage(ws.this, vm.ToValue(string(buf))) + } else { + _, _ = ws.onMessage(ws.this, vm.ToValue(buf)) + } + } + } + } + // fmt.Println(u.BMagenta("WS"), "stop onMessage") + // 未结束的连接自动重连 + if !ws.running { + break + } + ws.sleep(time.Duration(ws.reconnectInterval) * time.Millisecond) + if !ws.running { + break + } + // fmt.Println(u.BMagenta("WS"), "reconnect") + ws.connect(vm) + } + // fmt.Println(u.BMagenta("WS"), "stop onMessage2") + ws.closeChan <- true + }() + } else { + ws.closeChan <- true + } + return vm.ToValue(gojs.MakeMap(ws)) + } else { + panic(vm.NewGoError(err)) + } +} + +type WS struct { + conn *websocket.Conn + running bool + closed bool + closeChan chan bool + pingStopChan chan bool + logger *log.Logger + this goja.Value + writeLock sync.Mutex + url string + headers map[string]string + compress bool + onOpen goja.Callable + onClose goja.Callable + onError goja.Callable + // onPing goja.Callable + // onPong goja.Callable + onMessage goja.Callable + onJSONMessage goja.Callable + pingInterval int64 + reconnectInterval int64 +} + +func (ws *WS) sleep(interval time.Duration) { + if interval < time.Second { + time.Sleep(interval) + } else { + for { + time.Sleep(time.Second) + interval -= time.Second + if !ws.running || interval <= 0 { + break + } + } + } +} + +func (ws *WS) error(err goja.Value) { + if ws.onError != nil { + ws.onError(ws.this, err) + } +} + +func (ws *WS) connect(vm *goja.Runtime) error { + reqHeader := http.Header{} + for k, v := range ws.headers { + reqHeader.Set(k, v) + } + + conn, _, err := websocket.DefaultDialer.Dial(ws.url, reqHeader) + if err != nil { + return err + } + ws.conn = conn + if ws.reconnectInterval > 0 || ws.pingInterval > 0 { + ws.running = true + } + ws.closed = false + if ws.compress { + conn.EnableWriteCompression(true) + } + // if ws.onPing != nil { + // conn.SetPingHandler(func(appData string) error { + // fmt.Println(u.BMagenta("WS"), "onPing") + // _, err := ws.onPing(ws.this, vm.ToValue(appData)) + // return err + // }) + // return err + // } + // if ws.onPong != nil { + // conn.SetPongHandler(func(appData string) error { + // fmt.Println(u.BMagenta("WS"), "onPong") + // _, err := ws.onPong(ws.this, vm.ToValue(appData)) + // return err + // }) + // return err + // } + conn.SetCloseHandler(func(code int, text string) error { + // fmt.Println(u.BMagenta("WS"), "onClose") + if ws.onClose != nil { + _, err := ws.onClose(ws.this, vm.ToValue(code), vm.ToValue(text)) + return err + } + + // 关闭旧连接和接收器 + if !ws.closed { + ws.conn.Close() + ws.closed = true + } + + // 未结束的连接自动重连 + if ws.running { + time.Sleep(time.Duration(ws.reconnectInterval) * time.Millisecond) + // fmt.Println(u.BMagenta("WS"), "reconnect") + return ws.connect(vm) + } + return nil + }) + + return nil +} + +func (ws *WS) Read(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + typ, buf, err := ws.conn.ReadMessage() + if err != nil { + panic(vm.NewGoError(err)) + } + if typ == websocket.TextMessage { + return vm.ToValue(string(buf)) + } else { + return vm.ToValue(buf) + } +} + +func (ws *WS) ReadJSON(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + var obj interface{} + err := ws.conn.ReadJSON(&obj) + if err != nil { + panic(vm.NewGoError(err)) + } + return vm.ToValue(obj) +} + +func (ws *WS) Write(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := gojs.MakeArgs(&argsIn, vm).Check(1) + var err error + ws.writeLock.Lock() + if args.Arguments[0].ExportType().Kind() == reflect.String { + err = ws.conn.WriteMessage(websocket.TextMessage, args.Bytes(0)) + } else { + err = ws.conn.WriteMessage(websocket.BinaryMessage, args.Bytes(0)) + } + ws.writeLock.Unlock() + if err != nil { + panic(vm.NewGoError(err)) + } + return nil +} + +func (ws *WS) WriteJSON(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := gojs.MakeArgs(&argsIn, vm).Check(1) + ws.writeLock.Lock() + err := ws.conn.WriteJSON(args.Any(0)) + ws.writeLock.Unlock() + if err != nil { + panic(vm.NewGoError(err)) + } + return nil +} + +func (ws *WS) Close(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + // fmt.Println(u.BMagenta("WS"), "stop") + ws.running = false + if !ws.closed { + ws.closed = true + err := ws.conn.Close() + if err != nil { + panic(vm.NewGoError(err)) + } + <-ws.pingStopChan + <-ws.closeChan + } + return nil +} diff --git a/http.ts b/http.ts new file mode 100644 index 0000000..48c07ae --- /dev/null +++ b/http.ts @@ -0,0 +1,74 @@ +// just for develop + +export default { + new: new_, + newH2C, + get, + head, + post, + put, + delete: delete_, + do: do_, + upload, + download, + connect +} + +function new_(config?: Config): Client { return null as any } +function newH2C(config?: Config): Client { return null as any } +function get(url: string, headers?: Object): Result { return null as any } +function head(url: string, headers?: Object): Result { return null as any } +function post(url: string, data: any, headers?: Object): Result { return null as any } +function put(url: string, data: any, headers?: Object): Result { return null as any } +function delete_(url: string, data: any, headers?: Object): Result { return null as any } +function do_(method: string, url: string, data: any, callback?: (data: string) => void, headers?: Object): Result { return null as any } +function upload(url: string, form: Object, files: Object, headers?: Object): Result { return null as any } +function download(filename: string, url: string, callback?: (finished: number, total: number) => void, headers?: Object): Result { return null as any } +function connect(url: string, config?: WSConfig): WS { return null as any } + +interface Client { + 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 + upload(url: string, form: Object, files: Object, headers?: Object): Result + download(filename: string, url: string, callback?: (finished: number, total: number) => void, headers?: Object): Result +} + +interface Result { + status: string + statusCode: number + headers: Object + bytes(): Uint8Array + string(): string + object(): Object +} + +interface Config { + timeout: number + baseURL: string + globalHeaders: Map + downloadPartSize: number + redirect: boolean +} + +interface WSConfig { + headers: Object + compress: boolean + onOpen: () => void + onClose: (code: number, data: string) => void + onMessage: (data: any) => void + onJSONMessage: (data: any) => void + pingInterval: number + reconnectInterval: number +} + +interface WS { + read(): any + readJSON(): any + write(data: any): void + writeJSON(data: any): void + close(): void +} \ No newline at end of file diff --git a/http_test.go b/http_test.go new file mode 100644 index 0000000..839aacd --- /dev/null +++ b/http_test.go @@ -0,0 +1,116 @@ +package http_test + +import ( + "strings" + "testing" + "time" + + "apigo.cc/gojs" + _ "apigo.cc/gojs/console" + _ "apigo.cc/gojs/http" + "github.com/gorilla/websocket" + "github.com/ssgo/s" + "github.com/ssgo/u" +) + +var as *s.AsyncServer + +func TestStart(t *testing.T) { + s.Config.Listen = "18001,http|18002,h2c" + s.Register(0, "/echo", func(in struct{ Name string }) string { + return in.Name + }, "") + s.RegisterWebsocket(0, "/ws", nil, func(conn *websocket.Conn) { + typ, data, _ := conn.ReadMessage() + conn.WriteMessage(typ, data) + typ, data, _ = conn.ReadMessage() + conn.WriteMessage(typ, data) + typ, data, _ = conn.ReadMessage() + conn.WriteMessage(typ, data) + conn.Close() + }, nil, nil, nil, "") + as = s.AsyncStart() + gojs.ExportForDev() +} + +func TestHttp(t *testing.T) { + vm := gojs.New() + vm.RunCode("import http from 'apigo.cc/gojs/http'") + testIsOk(t, vm, `http.get('http://127.0.0.1:18001/echo?name=Tom').string()`, "Tom") + testIsOk(t, vm, `http.post('http://127.0.0.1:18001/echo',{name:'Tom'}).string()`, "Tom") + vm.RunCode("let h2 = http.newH2C()") + testIsOk(t, vm, `h2.get('http://127.0.0.1:18002/echo?name=Tom').string()`, "Tom") + testIsOk(t, vm, `h2.post('http://127.0.0.1:18002/echo',{name:'Tom'}).string()`, "Tom") +} + +func TestWS(t *testing.T) { + vm := gojs.New() + var err error + var r any + _, err = vm.RunFile("ws_test.js") + if err != nil { + t.Fatal("ws error", err) + } + + r, err = vm.RunCode("testSync()") + if err != nil { + t.Fatal("ws error", err) + } + if r != true { + t.Fatal("ws check failed", r) + } + + // 异步测试 + _, err = vm.RunCode("testAsync()") + if err != nil { + t.Fatal("ws error", err) + } + time.Sleep(time.Millisecond * 100) + + // 检查结果 + r, err = vm.RunCode("asyncResult") + if err != nil { + t.Fatal("ws error", err) + } + testHas(t, u.String(r), "Tom1", "Tom2", "Tom3") + + // 重连后再次测试 + r, err = vm.RunCode("testAsync2()") + if err != nil { + t.Fatal("ws error", err) + } + time.Sleep(time.Millisecond * 100) + + r, err = vm.RunCode("asyncResult") + if err != nil { + t.Fatal("ws error", err) + } + testHas(t, u.String(r), "Tom1", "Tom2", "Tom3", "Tom4", "Tom5", "Tom6") + + // 主动关闭连接 + _, err = vm.RunCode("closeAsync()") + if err != nil { + t.Fatal("ws error", err) + } +} + +func TestStop(t *testing.T) { + as.Stop() +} + +func testHas(t *testing.T, r string, checks ...string) { + for _, check := range checks { + if !strings.Contains(r, check) { + t.Fatal("not exists", r, check) + } + } +} + +func testIsOk(t *testing.T, vm *gojs.Runtime, code string, check string) { + r, err := vm.RunCode(code) + if err != nil { + t.Fatal(code, err) + } else if r != check { + t.Fatal(code, r, check) + } +} diff --git a/ws_test.js b/ws_test.js new file mode 100644 index 0000000..b713def --- /dev/null +++ b/ws_test.js @@ -0,0 +1,65 @@ +import http from 'apigo.cc/gojs/http' +import console from 'apigo.cc/gojs/console' + +function main() { } + +function testSync() { + let conn = http.connect("http://127.0.0.1:18001/ws") + conn.write('111') + if (conn.read() !== '111') { + return false + } + + conn.write(new Uint8Array([0x01, 0x02, 0x03])) + let r2 = conn.read() + if (r2[0] != 1 || r2[1] != 2 || r2[2] != 3) { + return false + } + + conn.writeJSON({ name: 'Tom' }) + let r3 = conn.readJSON() + if (r3.name != 'Tom') { + return false + } + conn.close() + return true +} + +let asyncResult = '' +let asyncConn +function testAsync() { + let conn = http.connect("ws://127.0.0.1:18001/ws", { + pingInterval: 10, + reconnectInterval: 10, + compress: true, + onError: console.error, + onClose: function (code, text) { + console.info("onClose", code, text) + }, + onPing: function (data) { + console.info("onPing", data) + }, + onPong: function (data) { + console.info("onPong", data) + }, + onJSONMessage: function (data) { + // console.info("onJSONMessage", data) + asyncResult += data.name + }, + }) + asyncConn = conn + + conn.writeJSON({ name: 'Tom1' }) + conn.writeJSON({ name: 'Tom2' }) + conn.writeJSON({ name: 'Tom3' }) +} + +function testAsync2() { + asyncConn.writeJSON({ name: 'Tom4' }) + asyncConn.writeJSON({ name: 'Tom5' }) + asyncConn.writeJSON({ name: 'Tom6' }) +} + +function closeAsync() { + asyncConn.close() +}