github.com/goplusjs/gopherjs@v1.2.6-0.20211206034512-f187917453b8/compiler/natives/src/net/http/fetch.go (about) 1 // +build js 2 3 package http 4 5 import ( 6 "errors" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "strconv" 11 12 "github.com/gopherjs/gopherjs/js" 13 ) 14 15 // streamReader implements an io.ReadCloser wrapper for ReadableStream of https://fetch.spec.whatwg.org/. 16 type streamReader struct { 17 pending []byte 18 stream *js.Object 19 } 20 21 func (r *streamReader) Read(p []byte) (n int, err error) { 22 if len(r.pending) == 0 { 23 var ( 24 bCh = make(chan []byte) 25 errCh = make(chan error) 26 ) 27 r.stream.Call("read").Call("then", 28 func(result *js.Object) { 29 if result.Get("done").Bool() { 30 errCh <- io.EOF 31 return 32 } 33 bCh <- result.Get("value").Interface().([]byte) 34 }, 35 func(reason *js.Object) { 36 // Assumes it's a DOMException. 37 errCh <- errors.New(reason.Get("message").String()) 38 }, 39 ) 40 select { 41 case b := <-bCh: 42 r.pending = b 43 case err := <-errCh: 44 return 0, err 45 } 46 } 47 n = copy(p, r.pending) 48 r.pending = r.pending[n:] 49 return n, nil 50 } 51 52 func (r *streamReader) Close() error { 53 // This ignores any error returned from cancel method. So far, I did not encounter any concrete 54 // situation where reporting the error is meaningful. Most users ignore error from resp.Body.Close(). 55 // If there's a need to report error here, it can be implemented and tested when that need comes up. 56 r.stream.Call("cancel") 57 return nil 58 } 59 60 // fetchTransport is a RoundTripper that is implemented using Fetch API. It supports streaming 61 // response bodies. 62 type fetchTransport struct{} 63 64 func (t *fetchTransport) RoundTrip(req *Request) (*Response, error) { 65 headers := js.Global.Get("Headers").New() 66 for key, values := range req.Header { 67 for _, value := range values { 68 headers.Call("append", key, value) 69 } 70 } 71 opt := map[string]interface{}{ 72 "method": req.Method, 73 "headers": headers, 74 "credentials": "same-origin", 75 } 76 if req.Body != nil { 77 // TODO: Find out if request body can be streamed into the fetch request rather than in advance here. 78 // See BufferSource at https://fetch.spec.whatwg.org/#body-mixin. 79 body, err := ioutil.ReadAll(req.Body) 80 if err != nil { 81 req.Body.Close() // RoundTrip must always close the body, including on errors. 82 return nil, err 83 } 84 req.Body.Close() 85 opt["body"] = body 86 } 87 respPromise := js.Global.Call("fetch", req.URL.String(), opt) 88 89 var ( 90 respCh = make(chan *Response) 91 errCh = make(chan error) 92 ) 93 respPromise.Call("then", 94 func(result *js.Object) { 95 header := Header{} 96 result.Get("headers").Call("forEach", func(value, key *js.Object) { 97 ck := CanonicalHeaderKey(key.String()) 98 header[ck] = append(header[ck], value.String()) 99 }) 100 101 contentLength := int64(-1) 102 if cl, err := strconv.ParseInt(header.Get("Content-Length"), 10, 64); err == nil { 103 contentLength = cl 104 } 105 106 select { 107 case respCh <- &Response{ 108 Status: result.Get("status").String() + " " + StatusText(result.Get("status").Int()), 109 StatusCode: result.Get("status").Int(), 110 Header: header, 111 ContentLength: contentLength, 112 Body: &streamReader{stream: result.Get("body").Call("getReader")}, 113 Request: req, 114 }: 115 case <-req.Context().Done(): 116 } 117 }, 118 func(reason *js.Object) { 119 select { 120 case errCh <- fmt.Errorf("net/http: fetch() failed: %s", reason.String()): 121 case <-req.Context().Done(): 122 } 123 }, 124 ) 125 select { 126 case <-req.Context().Done(): 127 // TODO: Abort request if possible using Fetch API. 128 return nil, errors.New("net/http: request canceled") 129 case resp := <-respCh: 130 return resp, nil 131 case err := <-errCh: 132 return nil, err 133 } 134 }