github.com/tetratelabs/wazero@v1.2.1/internal/gojs/http.go (about)

     1  package gojs
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"net/textproto"
     9  	"sort"
    10  
    11  	"github.com/tetratelabs/wazero/api"
    12  	"github.com/tetratelabs/wazero/internal/gojs/goos"
    13  )
    14  
    15  // headersConstructor = Get("Headers").New() // http.Roundtrip && "fetch"
    16  var headersConstructor = newJsVal(goos.RefHttpHeadersConstructor, "Headers")
    17  
    18  // httpFetch implements jsFn for http.RoundTripper
    19  //
    20  // Reference in roundtrip_js.go init
    21  //
    22  //	jsFetchMissing = js.Global().Get("fetch").IsUndefined()
    23  //
    24  // In http.Transport RoundTrip, this returns a promise
    25  //
    26  //	fetchPromise := js.Global().Call("fetch", req.URL.String(), opt)
    27  type httpFetch struct{ rt http.RoundTripper }
    28  
    29  func (h *httpFetch) invoke(ctx context.Context, _ api.Module, args ...interface{}) (interface{}, error) {
    30  	rt := h.rt
    31  	if rt == nil {
    32  		panic("unexpected to reach here without roundtripper as property is nil checked")
    33  	}
    34  	url := args[0].(string)
    35  	properties := args[1].(*object).properties
    36  	req, err := http.NewRequestWithContext(ctx, properties["method"].(string), url, nil)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	// TODO: headers properties[headers]
    41  	v := &fetchPromise{rt: rt, req: req}
    42  	return v, nil
    43  }
    44  
    45  type fetchPromise struct {
    46  	rt  http.RoundTripper
    47  	req *http.Request
    48  }
    49  
    50  // call implements jsCall.call
    51  func (p *fetchPromise) call(ctx context.Context, mod api.Module, this goos.Ref, method string, args ...interface{}) (interface{}, error) {
    52  	if method == "then" {
    53  		if res, err := p.rt.RoundTrip(p.req); err != nil {
    54  			failure := args[1].(funcWrapper)
    55  			// HTTP is at the GOOS=js abstraction, so we can return any error.
    56  			return failure.invoke(ctx, mod, this, err)
    57  		} else {
    58  			success := args[0].(funcWrapper)
    59  			return success.invoke(ctx, mod, this, &fetchResult{res: res})
    60  		}
    61  	}
    62  	panic(fmt.Sprintf("TODO: fetchPromise.%s", method))
    63  }
    64  
    65  type fetchResult struct {
    66  	res *http.Response
    67  }
    68  
    69  // Get implements the same method as documented on goos.GetFunction
    70  func (s *fetchResult) Get(propertyKey string) interface{} {
    71  	switch propertyKey {
    72  	case "headers":
    73  		names := make([]string, 0, len(s.res.Header))
    74  		for k := range s.res.Header {
    75  			names = append(names, k)
    76  		}
    77  		// Sort names for consistent iteration
    78  		sort.Strings(names)
    79  		h := &headers{names: names, headers: s.res.Header}
    80  		return h
    81  	case "body":
    82  		// return undefined as arrayPromise is more complicated than an array.
    83  		return goos.Undefined
    84  	case "status":
    85  		return uint32(s.res.StatusCode)
    86  	}
    87  	panic(fmt.Sprintf("TODO: get fetchResult.%s", propertyKey))
    88  }
    89  
    90  // call implements jsCall.call
    91  func (s *fetchResult) call(_ context.Context, _ api.Module, _ goos.Ref, method string, _ ...interface{}) (interface{}, error) {
    92  	switch method {
    93  	case "arrayBuffer":
    94  		v := &arrayPromise{reader: s.res.Body}
    95  		return v, nil
    96  	}
    97  	panic(fmt.Sprintf("TODO: call fetchResult.%s", method))
    98  }
    99  
   100  type headers struct {
   101  	headers http.Header
   102  	names   []string
   103  	i       int
   104  }
   105  
   106  // Get implements the same method as documented on goos.GetFunction
   107  func (h *headers) Get(propertyKey string) interface{} {
   108  	switch propertyKey {
   109  	case "done":
   110  		return h.i == len(h.names)
   111  	case "value":
   112  		name := h.names[h.i]
   113  		value := h.headers.Get(name)
   114  		h.i++
   115  		return &objectArray{[]interface{}{name, value}}
   116  	}
   117  	panic(fmt.Sprintf("TODO: get headers.%s", propertyKey))
   118  }
   119  
   120  // call implements jsCall.call
   121  func (h *headers) call(_ context.Context, _ api.Module, _ goos.Ref, method string, args ...interface{}) (interface{}, error) {
   122  	switch method {
   123  	case "entries":
   124  		// Sort names for consistent iteration
   125  		sort.Strings(h.names)
   126  		return h, nil
   127  	case "next":
   128  		return h, nil
   129  	case "append":
   130  		name := textproto.CanonicalMIMEHeaderKey(args[0].(string))
   131  		value := args[1].(string)
   132  		h.names = append(h.names, name)
   133  		h.headers.Add(name, value)
   134  		return nil, nil
   135  	}
   136  	panic(fmt.Sprintf("TODO: call headers.%s", method))
   137  }
   138  
   139  type arrayPromise struct {
   140  	reader io.ReadCloser
   141  }
   142  
   143  // call implements jsCall.call
   144  func (p *arrayPromise) call(ctx context.Context, mod api.Module, this goos.Ref, method string, args ...interface{}) (interface{}, error) {
   145  	switch method {
   146  	case "then":
   147  		defer p.reader.Close()
   148  		if b, err := io.ReadAll(p.reader); err != nil {
   149  			// HTTP is at the GOOS=js abstraction, so we can return any error.
   150  			return args[1].(funcWrapper).invoke(ctx, mod, this, err)
   151  		} else {
   152  			return args[0].(funcWrapper).invoke(ctx, mod, this, goos.WrapByteArray(b))
   153  		}
   154  	}
   155  	panic(fmt.Sprintf("TODO: call arrayPromise.%s", method))
   156  }