Source file
src/net/http/roundtrip_js.go
1
2
3
4
5
6
7 package http
8
9 import (
10 "errors"
11 "fmt"
12 "io"
13 "net/http/internal/ascii"
14 "net/url"
15 "strconv"
16 "strings"
17 "syscall/js"
18 )
19
20 var uint8Array = js.Global().Get("Uint8Array")
21
22
23
24
25
26
27
28 const jsFetchMode = "js.fetch:mode"
29
30
31
32
33
34
35
36 const jsFetchCreds = "js.fetch:credentials"
37
38
39
40
41
42
43
44 const jsFetchRedirect = "js.fetch:redirect"
45
46
47
48 var jsFetchMissing = js.Global().Get("fetch").IsUndefined()
49
50
51
52
53
54
55
56
57 var jsFetchDisabled = js.Global().Get("process").Type() == js.TypeObject &&
58 strings.HasPrefix(js.Global().Get("process").Get("argv0").String(), "node")
59
60
61 func (t *Transport) RoundTrip(req *Request) (*Response, error) {
62
63
64
65
66
67
68 if t.Dial != nil || t.DialContext != nil || t.DialTLS != nil || t.DialTLSContext != nil || jsFetchMissing || jsFetchDisabled {
69 return t.roundTrip(req)
70 }
71
72 ac := js.Global().Get("AbortController")
73 if !ac.IsUndefined() {
74
75
76
77 ac = ac.New()
78 }
79
80 opt := js.Global().Get("Object").New()
81
82
83 opt.Set("method", req.Method)
84 opt.Set("credentials", "same-origin")
85 if h := req.Header.Get(jsFetchCreds); h != "" {
86 opt.Set("credentials", h)
87 req.Header.Del(jsFetchCreds)
88 }
89 if h := req.Header.Get(jsFetchMode); h != "" {
90 opt.Set("mode", h)
91 req.Header.Del(jsFetchMode)
92 }
93 if h := req.Header.Get(jsFetchRedirect); h != "" {
94 opt.Set("redirect", h)
95 req.Header.Del(jsFetchRedirect)
96 }
97 if !ac.IsUndefined() {
98 opt.Set("signal", ac.Get("signal"))
99 }
100 headers := js.Global().Get("Headers").New()
101 for key, values := range req.Header {
102 for _, value := range values {
103 headers.Call("append", key, value)
104 }
105 }
106 opt.Set("headers", headers)
107
108 if req.Body != nil {
109
110
111
112
113
114
115
116
117 body, err := io.ReadAll(req.Body)
118 if err != nil {
119 req.Body.Close()
120 return nil, err
121 }
122 req.Body.Close()
123 if len(body) != 0 {
124 buf := uint8Array.New(len(body))
125 js.CopyBytesToJS(buf, body)
126 opt.Set("body", buf)
127 }
128 }
129
130 fetchPromise := js.Global().Call("fetch", req.URL.String(), opt)
131 var (
132 respCh = make(chan *Response, 1)
133 errCh = make(chan error, 1)
134 success, failure js.Func
135 )
136 success = js.FuncOf(func(this js.Value, args []js.Value) any {
137 success.Release()
138 failure.Release()
139
140 result := args[0]
141 header := Header{}
142
143 headersIt := result.Get("headers").Call("entries")
144 for {
145 n := headersIt.Call("next")
146 if n.Get("done").Bool() {
147 break
148 }
149 pair := n.Get("value")
150 key, value := pair.Index(0).String(), pair.Index(1).String()
151 ck := CanonicalHeaderKey(key)
152 header[ck] = append(header[ck], value)
153 }
154
155 contentLength := int64(0)
156 clHeader := header.Get("Content-Length")
157 switch {
158 case clHeader != "":
159 cl, err := strconv.ParseInt(clHeader, 10, 64)
160 if err != nil {
161 errCh <- fmt.Errorf("net/http: ill-formed Content-Length header: %v", err)
162 return nil
163 }
164 if cl < 0 {
165
166
167 errCh <- fmt.Errorf("net/http: invalid Content-Length header: %q", clHeader)
168 return nil
169 }
170 contentLength = cl
171 default:
172
173 contentLength = -1
174 }
175
176 b := result.Get("body")
177 var body io.ReadCloser
178
179
180 if !b.IsUndefined() && !b.IsNull() {
181 body = &streamReader{stream: b.Call("getReader")}
182 } else {
183
184
185 body = &arrayReader{arrayPromise: result.Call("arrayBuffer")}
186 }
187
188 code := result.Get("status").Int()
189
190 uncompressed := false
191 if ascii.EqualFold(header.Get("Content-Encoding"), "gzip") {
192
193 header.Del("Content-Encoding")
194 header.Del("Content-Length")
195 contentLength = -1
196 uncompressed = true
197 }
198
199 if result.Get("redirected").Bool() {
200 u, err := url.Parse(result.Get("url").String())
201 if err == nil {
202 req = req.Clone(req.ctx)
203 req.URL = u
204 }
205 }
206 respCh <- &Response{
207 Status: fmt.Sprintf("%d %s", code, StatusText(code)),
208 StatusCode: code,
209 Header: header,
210 ContentLength: contentLength,
211 Uncompressed: uncompressed,
212 Body: body,
213 Request: req,
214 }
215
216 return nil
217 })
218 failure = js.FuncOf(func(this js.Value, args []js.Value) any {
219 success.Release()
220 failure.Release()
221
222 err := args[0]
223
224
225
226 errMsg := err.Call("toString").String()
227
228 if cause := err.Get("cause"); !cause.IsUndefined() {
229
230
231 if !cause.Get("toString").IsUndefined() {
232 errMsg += ": " + cause.Call("toString").String()
233 } else if cause.Type() == js.TypeString {
234 errMsg += ": " + cause.String()
235 }
236 }
237 errCh <- fmt.Errorf("net/http: fetch() failed: %s", errMsg)
238 return nil
239 })
240
241 fetchPromise.Call("then", success, failure)
242 select {
243 case <-req.Context().Done():
244 if !ac.IsUndefined() {
245
246 ac.Call("abort")
247
248
249
250 select {
251 case resp := <-respCh:
252 resp.Body.Close()
253 case <-errCh:
254 }
255 }
256 return nil, req.Context().Err()
257 case resp := <-respCh:
258 return resp, nil
259 case err := <-errCh:
260 return nil, err
261 }
262 }
263
264 var errClosed = errors.New("net/http: reader is closed")
265
266
267
268 type streamReader struct {
269 pending []byte
270 stream js.Value
271 err error
272 }
273
274 func (r *streamReader) Read(p []byte) (n int, err error) {
275 if r.err != nil {
276 return 0, r.err
277 }
278 if len(r.pending) == 0 {
279 var (
280 bCh = make(chan []byte, 1)
281 errCh = make(chan error, 1)
282 )
283 success := js.FuncOf(func(this js.Value, args []js.Value) any {
284 result := args[0]
285 if result.Get("done").Bool() {
286 errCh <- io.EOF
287 return nil
288 }
289 value := make([]byte, result.Get("value").Get("byteLength").Int())
290 js.CopyBytesToGo(value, result.Get("value"))
291 bCh <- value
292 return nil
293 })
294 defer success.Release()
295 failure := js.FuncOf(func(this js.Value, args []js.Value) any {
296
297
298
299
300
301 errCh <- errors.New(args[0].Get("message").String())
302 return nil
303 })
304 defer failure.Release()
305 r.stream.Call("read").Call("then", success, failure)
306 select {
307 case b := <-bCh:
308 r.pending = b
309 case err := <-errCh:
310 r.err = err
311 return 0, err
312 }
313 }
314 n = copy(p, r.pending)
315 r.pending = r.pending[n:]
316 return n, nil
317 }
318
319 func (r *streamReader) Close() error {
320
321
322
323 r.stream.Call("cancel")
324 if r.err == nil {
325 r.err = errClosed
326 }
327 return nil
328 }
329
330
331
332 type arrayReader struct {
333 arrayPromise js.Value
334 pending []byte
335 read bool
336 err error
337 }
338
339 func (r *arrayReader) Read(p []byte) (n int, err error) {
340 if r.err != nil {
341 return 0, r.err
342 }
343 if !r.read {
344 r.read = true
345 var (
346 bCh = make(chan []byte, 1)
347 errCh = make(chan error, 1)
348 )
349 success := js.FuncOf(func(this js.Value, args []js.Value) any {
350
351 uint8arrayWrapper := uint8Array.New(args[0])
352 value := make([]byte, uint8arrayWrapper.Get("byteLength").Int())
353 js.CopyBytesToGo(value, uint8arrayWrapper)
354 bCh <- value
355 return nil
356 })
357 defer success.Release()
358 failure := js.FuncOf(func(this js.Value, args []js.Value) any {
359
360
361
362
363 errCh <- errors.New(args[0].Get("message").String())
364 return nil
365 })
366 defer failure.Release()
367 r.arrayPromise.Call("then", success, failure)
368 select {
369 case b := <-bCh:
370 r.pending = b
371 case err := <-errCh:
372 return 0, err
373 }
374 }
375 if len(r.pending) == 0 {
376 return 0, io.EOF
377 }
378 n = copy(p, r.pending)
379 r.pending = r.pending[n:]
380 return n, nil
381 }
382
383 func (r *arrayReader) Close() error {
384 if r.err == nil {
385 r.err = errClosed
386 }
387 return nil
388 }
389
View as plain text