Files
wazero/internal/gojs/http.go
Crypt Keeper f8a33cef8d logging: avoids logging activity to stdio file descriptors (#1007)
This avoids logging activity on stdio file descriptors, in order to help
make troubleshooting easier. Usually, there isn't an issue in these, yet
wasm panics are harder to read if there is also logging of the ..
logging.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2023-01-04 16:04:40 +08:00

166 lines
4.5 KiB
Go

package gojs
import (
"context"
"fmt"
"io"
"net/http"
"net/textproto"
"sort"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/gojs/goos"
)
// headersConstructor = Get("Headers").New() // http.Roundtrip && "fetch"
var headersConstructor = newJsVal(goos.RefHttpHeadersConstructor, "Headers")
type RoundTripperKey struct{}
func getRoundTripper(ctx context.Context) http.RoundTripper {
if rt, ok := ctx.Value(RoundTripperKey{}).(http.RoundTripper); ok {
return rt
}
return nil
}
// httpFetch implements jsFn for http.RoundTripper
//
// Reference in roundtrip_js.go init
//
// jsFetchMissing = js.Global().Get("fetch").IsUndefined()
//
// In http.Transport RoundTrip, this returns a promise
//
// fetchPromise := js.Global().Call("fetch", req.URL.String(), opt)
type httpFetch struct{}
func (httpFetch) invoke(ctx context.Context, _ api.Module, args ...interface{}) (interface{}, error) {
rt := getRoundTripper(ctx)
if rt == nil {
panic("unexpected to reach here without roundtripper as property is nil checked")
}
url := args[0].(string)
properties := args[1].(*object).properties
req, err := http.NewRequestWithContext(ctx, properties["method"].(string), url, nil)
if err != nil {
return nil, err
}
// TODO: headers properties[headers]
v := &fetchPromise{rt: rt, req: req}
return v, nil
}
type fetchPromise struct {
rt http.RoundTripper
req *http.Request
}
// call implements jsCall.call
func (p *fetchPromise) call(ctx context.Context, mod api.Module, this goos.Ref, method string, args ...interface{}) (interface{}, error) {
if method == "then" {
if res, err := p.rt.RoundTrip(p.req); err != nil {
failure := args[1].(funcWrapper)
// HTTP is at the GOOS=js abstraction, so we can return any error.
return failure.invoke(ctx, mod, this, err)
} else {
success := args[0].(funcWrapper)
return success.invoke(ctx, mod, this, &fetchResult{res: res})
}
}
panic(fmt.Sprintf("TODO: fetchPromise.%s", method))
}
type fetchResult struct {
res *http.Response
}
// get implements jsGet.get
func (s *fetchResult) get(_ context.Context, propertyKey string) interface{} {
switch propertyKey {
case "headers":
names := make([]string, 0, len(s.res.Header))
for k := range s.res.Header {
names = append(names, k)
}
// Sort names for consistent iteration
sort.Strings(names)
h := &headers{names: names, headers: s.res.Header}
return h
case "body":
// return undefined as arrayPromise is more complicated than an array.
return goos.Undefined
case "status":
return uint32(s.res.StatusCode)
}
panic(fmt.Sprintf("TODO: get fetchResult.%s", propertyKey))
}
// call implements jsCall.call
func (s *fetchResult) call(_ context.Context, _ api.Module, _ goos.Ref, method string, _ ...interface{}) (interface{}, error) {
switch method {
case "arrayBuffer":
v := &arrayPromise{reader: s.res.Body}
return v, nil
}
panic(fmt.Sprintf("TODO: call fetchResult.%s", method))
}
type headers struct {
headers http.Header
names []string
i int
}
// get implements jsGet.get
func (h *headers) get(_ context.Context, propertyKey string) interface{} {
switch propertyKey {
case "done":
return h.i == len(h.names)
case "value":
name := h.names[h.i]
value := h.headers.Get(name)
h.i++
return &objectArray{[]interface{}{name, value}}
}
panic(fmt.Sprintf("TODO: get headers.%s", propertyKey))
}
// call implements jsCall.call
func (h *headers) call(_ context.Context, _ api.Module, _ goos.Ref, method string, args ...interface{}) (interface{}, error) {
switch method {
case "entries":
// Sort names for consistent iteration
sort.Strings(h.names)
return h, nil
case "next":
return h, nil
case "append":
name := textproto.CanonicalMIMEHeaderKey(args[0].(string))
value := args[1].(string)
h.names = append(h.names, name)
h.headers.Add(name, value)
return nil, nil
}
panic(fmt.Sprintf("TODO: call headers.%s", method))
}
type arrayPromise struct {
reader io.ReadCloser
}
// call implements jsCall.call
func (p *arrayPromise) call(ctx context.Context, mod api.Module, this goos.Ref, method string, args ...interface{}) (interface{}, error) {
switch method {
case "then":
defer p.reader.Close()
if b, err := io.ReadAll(p.reader); err != nil {
// HTTP is at the GOOS=js abstraction, so we can return any error.
return args[1].(funcWrapper).invoke(ctx, mod, this, err)
} else {
return args[0].(funcWrapper).invoke(ctx, mod, this, &byteArray{b})
}
}
panic(fmt.Sprintf("TODO: call arrayPromise.%s", method))
}