github.com/tetratelabs/wazero@v1.2.1/internal/gojs/http.go (about) 1 package gojs 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "net/http" 8 "net/textproto" 9 "sort" 10 11 "github.com/tetratelabs/wazero/api" 12 "github.com/tetratelabs/wazero/internal/gojs/goos" 13 ) 14 15 // headersConstructor = Get("Headers").New() // http.Roundtrip && "fetch" 16 var headersConstructor = newJsVal(goos.RefHttpHeadersConstructor, "Headers") 17 18 // httpFetch implements jsFn for http.RoundTripper 19 // 20 // Reference in roundtrip_js.go init 21 // 22 // jsFetchMissing = js.Global().Get("fetch").IsUndefined() 23 // 24 // In http.Transport RoundTrip, this returns a promise 25 // 26 // fetchPromise := js.Global().Call("fetch", req.URL.String(), opt) 27 type httpFetch struct{ rt http.RoundTripper } 28 29 func (h *httpFetch) invoke(ctx context.Context, _ api.Module, args ...interface{}) (interface{}, error) { 30 rt := h.rt 31 if rt == nil { 32 panic("unexpected to reach here without roundtripper as property is nil checked") 33 } 34 url := args[0].(string) 35 properties := args[1].(*object).properties 36 req, err := http.NewRequestWithContext(ctx, properties["method"].(string), url, nil) 37 if err != nil { 38 return nil, err 39 } 40 // TODO: headers properties[headers] 41 v := &fetchPromise{rt: rt, req: req} 42 return v, nil 43 } 44 45 type fetchPromise struct { 46 rt http.RoundTripper 47 req *http.Request 48 } 49 50 // call implements jsCall.call 51 func (p *fetchPromise) call(ctx context.Context, mod api.Module, this goos.Ref, method string, args ...interface{}) (interface{}, error) { 52 if method == "then" { 53 if res, err := p.rt.RoundTrip(p.req); err != nil { 54 failure := args[1].(funcWrapper) 55 // HTTP is at the GOOS=js abstraction, so we can return any error. 56 return failure.invoke(ctx, mod, this, err) 57 } else { 58 success := args[0].(funcWrapper) 59 return success.invoke(ctx, mod, this, &fetchResult{res: res}) 60 } 61 } 62 panic(fmt.Sprintf("TODO: fetchPromise.%s", method)) 63 } 64 65 type fetchResult struct { 66 res *http.Response 67 } 68 69 // Get implements the same method as documented on goos.GetFunction 70 func (s *fetchResult) Get(propertyKey string) interface{} { 71 switch propertyKey { 72 case "headers": 73 names := make([]string, 0, len(s.res.Header)) 74 for k := range s.res.Header { 75 names = append(names, k) 76 } 77 // Sort names for consistent iteration 78 sort.Strings(names) 79 h := &headers{names: names, headers: s.res.Header} 80 return h 81 case "body": 82 // return undefined as arrayPromise is more complicated than an array. 83 return goos.Undefined 84 case "status": 85 return uint32(s.res.StatusCode) 86 } 87 panic(fmt.Sprintf("TODO: get fetchResult.%s", propertyKey)) 88 } 89 90 // call implements jsCall.call 91 func (s *fetchResult) call(_ context.Context, _ api.Module, _ goos.Ref, method string, _ ...interface{}) (interface{}, error) { 92 switch method { 93 case "arrayBuffer": 94 v := &arrayPromise{reader: s.res.Body} 95 return v, nil 96 } 97 panic(fmt.Sprintf("TODO: call fetchResult.%s", method)) 98 } 99 100 type headers struct { 101 headers http.Header 102 names []string 103 i int 104 } 105 106 // Get implements the same method as documented on goos.GetFunction 107 func (h *headers) Get(propertyKey string) interface{} { 108 switch propertyKey { 109 case "done": 110 return h.i == len(h.names) 111 case "value": 112 name := h.names[h.i] 113 value := h.headers.Get(name) 114 h.i++ 115 return &objectArray{[]interface{}{name, value}} 116 } 117 panic(fmt.Sprintf("TODO: get headers.%s", propertyKey)) 118 } 119 120 // call implements jsCall.call 121 func (h *headers) call(_ context.Context, _ api.Module, _ goos.Ref, method string, args ...interface{}) (interface{}, error) { 122 switch method { 123 case "entries": 124 // Sort names for consistent iteration 125 sort.Strings(h.names) 126 return h, nil 127 case "next": 128 return h, nil 129 case "append": 130 name := textproto.CanonicalMIMEHeaderKey(args[0].(string)) 131 value := args[1].(string) 132 h.names = append(h.names, name) 133 h.headers.Add(name, value) 134 return nil, nil 135 } 136 panic(fmt.Sprintf("TODO: call headers.%s", method)) 137 } 138 139 type arrayPromise struct { 140 reader io.ReadCloser 141 } 142 143 // call implements jsCall.call 144 func (p *arrayPromise) call(ctx context.Context, mod api.Module, this goos.Ref, method string, args ...interface{}) (interface{}, error) { 145 switch method { 146 case "then": 147 defer p.reader.Close() 148 if b, err := io.ReadAll(p.reader); err != nil { 149 // HTTP is at the GOOS=js abstraction, so we can return any error. 150 return args[1].(funcWrapper).invoke(ctx, mod, this, err) 151 } else { 152 return args[0].(funcWrapper).invoke(ctx, mod, this, goos.WrapByteArray(b)) 153 } 154 } 155 panic(fmt.Sprintf("TODO: call arrayPromise.%s", method)) 156 }