feat(goja): implement fetch API and fix websocket concurrency
fix(goja): handle ignored error returns in fetch API docs: add fetch API type definitions to global-types
This commit is contained in:
@@ -84,6 +84,9 @@ func FilterInstGoJa(gl core.Params) (core.FilterInstance, error) {
|
||||
if err = EventInject(ctx, vm, jsLoop); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = FetchInject(vm, jsLoop); err != nil {
|
||||
return err
|
||||
}
|
||||
if global.EnableWebsocket {
|
||||
var closer io.Closer
|
||||
closer, err = WebsocketInject(ctx, vm, debug, request, jsLoop)
|
||||
|
||||
99
pkg/filters/goja/var_fetch.go
Normal file
99
pkg/filters/goja/var_fetch.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/dop251/goja_nodejs/eventloop"
|
||||
)
|
||||
|
||||
func FetchInject(jsCtx *goja.Runtime, loop *eventloop.EventLoop) error {
|
||||
return jsCtx.GlobalObject().Set("fetch", func(url string, options ...map[string]interface{}) *goja.Promise {
|
||||
promise, resolve, reject := jsCtx.NewPromise()
|
||||
|
||||
go func() {
|
||||
method := "GET"
|
||||
var body io.Reader
|
||||
headers := make(http.Header)
|
||||
|
||||
if len(options) > 0 {
|
||||
opts := options[0]
|
||||
if m, ok := opts["method"].(string); ok {
|
||||
method = strings.ToUpper(m)
|
||||
}
|
||||
if h, ok := opts["headers"].(map[string]interface{}); ok {
|
||||
for k, v := range h {
|
||||
if strVal, ok := v.(string); ok {
|
||||
headers.Set(k, strVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
if b, ok := opts["body"].(string); ok {
|
||||
body = strings.NewReader(b)
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
loop.RunOnLoop(func(*goja.Runtime) {
|
||||
_ = reject(err)
|
||||
})
|
||||
return
|
||||
}
|
||||
req.Header = headers
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
loop.RunOnLoop(func(*goja.Runtime) {
|
||||
_ = reject(err)
|
||||
})
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
loop.RunOnLoop(func(*goja.Runtime) {
|
||||
_ = reject(err)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
headersMap := make(map[string]interface{})
|
||||
for k, v := range resp.Header {
|
||||
headersMap[k] = v
|
||||
}
|
||||
|
||||
loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||
responseObj := map[string]interface{}{
|
||||
"ok": resp.StatusCode >= 200 && resp.StatusCode < 300,
|
||||
"status": resp.StatusCode,
|
||||
"statusText": resp.Status,
|
||||
"headers": headersMap,
|
||||
"text": func() *goja.Promise {
|
||||
p, res, _ := vm.NewPromise()
|
||||
_ = res(string(respBody))
|
||||
return p
|
||||
},
|
||||
"json": func() *goja.Promise {
|
||||
p, res, rej := vm.NewPromise()
|
||||
var data interface{}
|
||||
if err := json.Unmarshal(respBody, &data); err != nil {
|
||||
_ = rej(err)
|
||||
} else {
|
||||
_ = res(data)
|
||||
}
|
||||
return p
|
||||
},
|
||||
}
|
||||
_ = resolve(responseObj)
|
||||
})
|
||||
}()
|
||||
|
||||
return promise
|
||||
})
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package goja
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
@@ -21,6 +22,9 @@ func WebsocketInject(ctx core.FilterContext, jsCtx *goja.Runtime, w http.Respons
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var readMu sync.Mutex
|
||||
var writeMu sync.Mutex
|
||||
|
||||
zap.L().Debug("websocket upgrader created")
|
||||
closers.AddCloser(conn.Close)
|
||||
go func() {
|
||||
@@ -61,7 +65,9 @@ func WebsocketInject(ctx core.FilterContext, jsCtx *goja.Runtime, w http.Respons
|
||||
})
|
||||
}
|
||||
}()
|
||||
readMu.Lock()
|
||||
_, p, err := conn.ReadMessage()
|
||||
readMu.Unlock()
|
||||
loop.RunOnLoop(func(runtime *goja.Runtime) {
|
||||
if err != nil {
|
||||
_ = reject(runtime.ToValue(err))
|
||||
@@ -91,7 +97,9 @@ func WebsocketInject(ctx core.FilterContext, jsCtx *goja.Runtime, w http.Respons
|
||||
})
|
||||
}
|
||||
}()
|
||||
readMu.Lock()
|
||||
messageType, p, err := conn.ReadMessage()
|
||||
readMu.Unlock()
|
||||
loop.RunOnLoop(func(runtime *goja.Runtime) {
|
||||
if err != nil {
|
||||
_ = reject(runtime.ToValue(err))
|
||||
@@ -124,7 +132,9 @@ func WebsocketInject(ctx core.FilterContext, jsCtx *goja.Runtime, w http.Respons
|
||||
})
|
||||
}
|
||||
}()
|
||||
writeMu.Lock()
|
||||
err := conn.WriteMessage(websocket.TextMessage, []byte(data))
|
||||
writeMu.Unlock()
|
||||
loop.RunOnLoop(func(runtime *goja.Runtime) {
|
||||
if err != nil {
|
||||
_ = reject(runtime.ToValue(err))
|
||||
@@ -171,7 +181,9 @@ func WebsocketInject(ctx core.FilterContext, jsCtx *goja.Runtime, w http.Respons
|
||||
return
|
||||
}
|
||||
|
||||
writeMu.Lock()
|
||||
err := conn.WriteMessage(mType, dataRaw)
|
||||
writeMu.Unlock()
|
||||
loop.RunOnLoop(func(runtime *goja.Runtime) {
|
||||
if err != nil {
|
||||
_ = reject(runtime.ToValue(err))
|
||||
|
||||
Reference in New Issue
Block a user