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