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  }