1 // Copyright 2018 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
20 // jsFetchMode is a Request.Header map key that, if present,
21 // signals that the map entry is actually an option to the Fetch API mode setting.
22 // Valid values are: "cors", "no-cors", "same-origin", "navigate"
23 // The default is "same-origin".
25 // Reference: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters
26 const jsFetchMode
= "js.fetch:mode"
28 // jsFetchCreds is a Request.Header map key that, if present,
29 // signals that the map entry is actually an option to the Fetch API credentials setting.
30 // Valid values are: "omit", "same-origin", "include"
31 // The default is "same-origin".
33 // Reference: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters
34 const jsFetchCreds
= "js.fetch:credentials"
36 // RoundTrip implements the RoundTripper interface using the WHATWG Fetch API.
37 func (t
*Transport
) RoundTrip(req
*Request
) (*Response
, error
) {
39 return t
.roundTrip(req
)
42 ac
:= js
.Global().Get("AbortController")
43 if ac
!= js
.Undefined() {
44 // Some browsers that support WASM don't necessarily support
45 // the AbortController. See
46 // https://developer.mozilla.org/en-US/docs/Web/API/AbortController#Browser_compatibility.
50 opt
:= js
.Global().Get("Object").New()
51 // See https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
52 // for options available.
53 opt
.Set("method", req
.Method
)
54 opt
.Set("credentials", "same-origin")
55 if h
:= req
.Header
.Get(jsFetchCreds
); h
!= "" {
56 opt
.Set("credentials", h
)
57 req
.Header
.Del(jsFetchCreds
)
59 if h
:= req
.Header
.Get(jsFetchMode
); h
!= "" {
61 req
.Header
.Del(jsFetchMode
)
63 if ac
!= js
.Undefined() {
64 opt
.Set("signal", ac
.Get("signal"))
66 headers
:= js
.Global().Get("Headers").New()
67 for key
, values
:= range req
.Header
{
68 for _
, value
:= range values
{
69 headers
.Call("append", key
, value
)
72 opt
.Set("headers", headers
)
75 // TODO(johanbrandhorst): Stream request body when possible.
76 // See https://bugs.chromium.org/p/chromium/issues/detail?id=688906 for Blink issue.
77 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1387483 for Firefox issue.
78 // See https://github.com/web-platform-tests/wpt/issues/7693 for WHATWG tests issue.
79 // See https://developer.mozilla.org/en-US/docs/Web/API/Streams_API for more details on the Streams API
80 // and browser support.
81 body
, err
:= ioutil
.ReadAll(req
.Body
)
83 req
.Body
.Close() // RoundTrip must always close the body, including on errors.
87 a
:= js
.TypedArrayOf(body
)
91 respPromise
:= js
.Global().Call("fetch", req
.URL
.String(), opt
)
93 respCh
= make(chan *Response
, 1)
94 errCh
= make(chan error
, 1)
96 success
:= js
.NewCallback(func(args
[]js
.Value
) {
99 // https://developer.mozilla.org/en-US/docs/Web/API/Headers/entries
100 headersIt
:= result
.Get("headers").Call("entries")
102 n
:= headersIt
.Call("next")
103 if n
.Get("done").Bool() {
106 pair
:= n
.Get("value")
107 key
, value
:= pair
.Index(0).String(), pair
.Index(1).String()
108 ck
:= CanonicalHeaderKey(key
)
109 header
[ck
] = append(header
[ck
], value
)
112 contentLength
:= int64(0)
113 if cl
, err
:= strconv
.ParseInt(header
.Get("Content-Length"), 10, 64); err
== nil {
117 b
:= result
.Get("body")
118 var body io
.ReadCloser
119 if b
!= js
.Undefined() {
120 body
= &streamReader
{stream
: b
.Call("getReader")}
122 // Fall back to using ArrayBuffer
123 // https://developer.mozilla.org/en-US/docs/Web/API/Body/arrayBuffer
124 body
= &arrayReader
{arrayPromise
: result
.Call("arrayBuffer")}
128 case respCh
<- &Response
{
129 Status
: result
.Get("status").String() + " " + StatusText(result
.Get("status").Int()),
130 StatusCode
: result
.Get("status").Int(),
132 ContentLength
: contentLength
,
136 case <-req
.Context().Done():
139 defer success
.Release()
140 failure
:= js
.NewCallback(func(args
[]js
.Value
) {
141 err
:= fmt
.Errorf("net/http: fetch() failed: %s", args
[0].String())
144 case <-req
.Context().Done():
147 defer failure
.Release()
148 respPromise
.Call("then", success
, failure
)
150 case <-req
.Context().Done():
151 if ac
!= js
.Undefined() {
152 // Abort the Fetch request
155 return nil, req
.Context().Err()
156 case resp
:= <-respCh
:
163 var errClosed
= errors
.New("net/http: reader is closed")
165 // useFakeNetwork is used to determine whether the request is made
166 // by a test and should be made to use the fake in-memory network.
167 func useFakeNetwork() bool {
168 return len(os
.Args
) > 0 && strings
.HasSuffix(os
.Args
[0], ".test")
171 // streamReader implements an io.ReadCloser wrapper for ReadableStream.
172 // See https://fetch.spec.whatwg.org/#readablestream for more information.
173 type streamReader
struct {
176 err error
// sticky read error
179 func (r
*streamReader
) Read(p
[]byte) (n
int, err error
) {
183 if len(r
.pending
) == 0 {
185 bCh
= make(chan []byte, 1)
186 errCh
= make(chan error
, 1)
188 success
:= js
.NewCallback(func(args
[]js
.Value
) {
190 if result
.Get("done").Bool() {
194 value
:= make([]byte, result
.Get("value").Get("byteLength").Int())
195 a
:= js
.TypedArrayOf(value
)
196 a
.Call("set", result
.Get("value"))
200 defer success
.Release()
201 failure
:= js
.NewCallback(func(args
[]js
.Value
) {
202 // Assumes it's a TypeError. See
203 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
204 // for more information on this type. See
205 // https://streams.spec.whatwg.org/#byob-reader-read for the spec on
207 errCh
<- errors
.New(args
[0].Get("message").String())
209 defer failure
.Release()
210 r
.stream
.Call("read").Call("then", success
, failure
)
219 n
= copy(p
, r
.pending
)
220 r
.pending
= r
.pending
[n
:]
224 func (r
*streamReader
) Close() error
{
225 // This ignores any error returned from cancel method. So far, I did not encounter any concrete
226 // situation where reporting the error is meaningful. Most users ignore error from resp.Body.Close().
227 // If there's a need to report error here, it can be implemented and tested when that need comes up.
228 r
.stream
.Call("cancel")
235 // arrayReader implements an io.ReadCloser wrapper for ArrayBuffer.
236 // https://developer.mozilla.org/en-US/docs/Web/API/Body/arrayBuffer.
237 type arrayReader
struct {
238 arrayPromise js
.Value
241 err error
// sticky read error
244 func (r
*arrayReader
) Read(p
[]byte) (n
int, err error
) {
251 bCh
= make(chan []byte, 1)
252 errCh
= make(chan error
, 1)
254 success
:= js
.NewCallback(func(args
[]js
.Value
) {
255 // Wrap the input ArrayBuffer with a Uint8Array
256 uint8arrayWrapper
:= js
.Global().Get("Uint8Array").New(args
[0])
257 value
:= make([]byte, uint8arrayWrapper
.Get("byteLength").Int())
258 a
:= js
.TypedArrayOf(value
)
259 a
.Call("set", uint8arrayWrapper
)
263 defer success
.Release()
264 failure
:= js
.NewCallback(func(args
[]js
.Value
) {
265 // Assumes it's a TypeError. See
266 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
267 // for more information on this type.
268 // See https://fetch.spec.whatwg.org/#concept-body-consume-body for reasons this might error.
269 errCh
<- errors
.New(args
[0].Get("message").String())
271 defer failure
.Release()
272 r
.arrayPromise
.Call("then", success
, failure
)
280 if len(r
.pending
) == 0 {
283 n
= copy(p
, r
.pending
)
284 r
.pending
= r
.pending
[n
:]
288 func (r
*arrayReader
) Close() error
{