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