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