github.com/goplusjs/gopherjs@v1.2.6-0.20211206034512-f187917453b8/compiler/natives/src/net/http/http.go (about) 1 // +build js 2 3 package http 4 5 import ( 6 "bufio" 7 "bytes" 8 "errors" 9 "io/ioutil" 10 "net/textproto" 11 "strconv" 12 13 "github.com/gopherjs/gopherjs/js" 14 ) 15 16 var DefaultTransport = func() RoundTripper { 17 switch { 18 case js.Global.Get("fetch") != js.Undefined && js.Global.Get("ReadableStream") != js.Undefined: // ReadableStream is used as a check for support of streaming response bodies, see https://fetch.spec.whatwg.org/#streams. 19 return &fetchTransport{} 20 case js.Global.Get("XMLHttpRequest") != js.Undefined: 21 return &XHRTransport{} 22 default: 23 return noTransport{} 24 } 25 }() 26 27 // noTransport is used when neither Fetch API nor XMLHttpRequest API are available. It always fails. 28 type noTransport struct{} 29 30 func (noTransport) RoundTrip(req *Request) (*Response, error) { 31 return nil, errors.New("net/http: neither of Fetch nor XMLHttpRequest APIs is available") 32 } 33 34 type XHRTransport struct { 35 inflight map[*Request]*js.Object 36 } 37 38 func (t *XHRTransport) RoundTrip(req *Request) (*Response, error) { 39 xhr := js.Global.Get("XMLHttpRequest").New() 40 41 if t.inflight == nil { 42 t.inflight = map[*Request]*js.Object{} 43 } 44 t.inflight[req] = xhr 45 defer delete(t.inflight, req) 46 47 respCh := make(chan *Response) 48 errCh := make(chan error) 49 50 xhr.Set("onload", func() { 51 header, _ := textproto.NewReader(bufio.NewReader(bytes.NewReader([]byte(xhr.Call("getAllResponseHeaders").String() + "\n")))).ReadMIMEHeader() 52 body := js.Global.Get("Uint8Array").New(xhr.Get("response")).Interface().([]byte) 53 54 contentLength := int64(-1) 55 switch req.Method { 56 case "HEAD": 57 if l, err := strconv.ParseInt(header.Get("Content-Length"), 10, 64); err == nil { 58 contentLength = l 59 } 60 default: 61 contentLength = int64(len(body)) 62 } 63 64 respCh <- &Response{ 65 Status: xhr.Get("status").String() + " " + xhr.Get("statusText").String(), 66 StatusCode: xhr.Get("status").Int(), 67 Header: Header(header), 68 ContentLength: contentLength, 69 Body: ioutil.NopCloser(bytes.NewReader(body)), 70 Request: req, 71 } 72 }) 73 74 xhr.Set("onerror", func(e *js.Object) { 75 errCh <- errors.New("net/http: XMLHttpRequest failed") 76 }) 77 78 xhr.Set("onabort", func(e *js.Object) { 79 errCh <- errors.New("net/http: request canceled") 80 }) 81 82 xhr.Call("open", req.Method, req.URL.String()) 83 xhr.Set("responseType", "arraybuffer") // has to be after "open" until https://bugzilla.mozilla.org/show_bug.cgi?id=1110761 is resolved 84 for key, values := range req.Header { 85 for _, value := range values { 86 xhr.Call("setRequestHeader", key, value) 87 } 88 } 89 if req.Body == nil { 90 xhr.Call("send") 91 } else { 92 body, err := ioutil.ReadAll(req.Body) 93 if err != nil { 94 req.Body.Close() // RoundTrip must always close the body, including on errors. 95 return nil, err 96 } 97 req.Body.Close() 98 xhr.Call("send", body) 99 } 100 101 select { 102 case resp := <-respCh: 103 return resp, nil 104 case err := <-errCh: 105 return nil, err 106 } 107 } 108 109 func (t *XHRTransport) CancelRequest(req *Request) { 110 if xhr, ok := t.inflight[req]; ok { 111 xhr.Call("abort") 112 } 113 }