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