This commit is contained in:
Star 2024-10-15 10:45:04 +08:00
commit c78548f50d
8 changed files with 885 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.*
!.gitignore
go.sum
node_modules
package.json

9
LICENSE Normal file
View File

@ -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.

51
README.md Normal file
View File

@ -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)

41
go.mod Normal file
View File

@ -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
)

524
http.go Normal file
View File

@ -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
}

74
http.ts Normal file
View File

@ -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<string, string>
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
}

116
http_test.go Normal file
View File

@ -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)
}
}

65
ws_test.js Normal file
View File

@ -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()
}