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