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  }