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 return nil, req.Context().Err()
241 case resp := <-respCh:
242 return resp, nil
243 case err := <-errCh:
244 return nil, err
245 }
246 }
247
248 var errClosed = errors.New("net/http: reader is closed")
249
250
251
252 type streamReader struct {
253 pending []byte
254 stream js.Value
255 err error
256 }
257
258 func (r *streamReader) Read(p []byte) (n int, err error) {
259 if r.err != nil {
260 return 0, r.err
261 }
262 if len(r.pending) == 0 {
263 var (
264 bCh = make(chan []byte, 1)
265 errCh = make(chan error, 1)
266 )
267 success := js.FuncOf(func(this js.Value, args []js.Value) any {
268 result := args[0]
269 if result.Get("done").Bool() {
270 errCh <- io.EOF
271 return nil
272 }
273 value := make([]byte, result.Get("value").Get("byteLength").Int())
274 js.CopyBytesToGo(value, result.Get("value"))
275 bCh <- value
276 return nil
277 })
278 defer success.Release()
279 failure := js.FuncOf(func(this js.Value, args []js.Value) any {
280
281
282
283
284
285 errCh <- errors.New(args[0].Get("message").String())
286 return nil
287 })
288 defer failure.Release()
289 r.stream.Call("read").Call("then", success, failure)
290 select {
291 case b := <-bCh:
292 r.pending = b
293 case err := <-errCh:
294 r.err = err
295 return 0, err
296 }
297 }
298 n = copy(p, r.pending)
299 r.pending = r.pending[n:]
300 return n, nil
301 }
302
303 func (r *streamReader) Close() error {
304
305
306
307 r.stream.Call("cancel")
308 if r.err == nil {
309 r.err = errClosed
310 }
311 return nil
312 }
313
314
315
316 type arrayReader struct {
317 arrayPromise js.Value
318 pending []byte
319 read bool
320 err error
321 }
322
323 func (r *arrayReader) Read(p []byte) (n int, err error) {
324 if r.err != nil {
325 return 0, r.err
326 }
327 if !r.read {
328 r.read = true
329 var (
330 bCh = make(chan []byte, 1)
331 errCh = make(chan error, 1)
332 )
333 success := js.FuncOf(func(this js.Value, args []js.Value) any {
334
335 uint8arrayWrapper := uint8Array.New(args[0])
336 value := make([]byte, uint8arrayWrapper.Get("byteLength").Int())
337 js.CopyBytesToGo(value, uint8arrayWrapper)
338 bCh <- value
339 return nil
340 })
341 defer success.Release()
342 failure := js.FuncOf(func(this js.Value, args []js.Value) any {
343
344
345
346
347 errCh <- errors.New(args[0].Get("message").String())
348 return nil
349 })
350 defer failure.Release()
351 r.arrayPromise.Call("then", success, failure)
352 select {
353 case b := <-bCh:
354 r.pending = b
355 case err := <-errCh:
356 return 0, err
357 }
358 }
359 if len(r.pending) == 0 {
360 return 0, io.EOF
361 }
362 n = copy(p, r.pending)
363 r.pending = r.pending[n:]
364 return n, nil
365 }
366
367 func (r *arrayReader) Close() error {
368 if r.err == nil {
369 r.err = errClosed
370 }
371 return nil
372 }
373
View as plain text