github.com/emcfarlane/larking@v0.0.0-20220605172417-1704b45ee6c3/starlib/net/starlarkhttp/http.go (about)

     1  // Copyright 2021 Edward McFarlane. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package http provides HTTP client implementations.
     6  package starlarkhttp
     7  
     8  import (
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"net/url"
    13  	"sort"
    14  	"strings"
    15  
    16  	"github.com/emcfarlane/larking/starlib/starext"
    17  	"github.com/emcfarlane/larking/starlib/starlarkerrors"
    18  	"github.com/emcfarlane/larking/starlib/starlarkio"
    19  	"github.com/emcfarlane/larking/starlib/starlarkstruct"
    20  	"github.com/emcfarlane/larking/starlib/starlarkthread"
    21  	"go.starlark.net/starlark"
    22  )
    23  
    24  // NewModule loads the predeclared built-ins for the net/http module.
    25  func NewModule() *starlarkstruct.Module {
    26  	return &starlarkstruct.Module{
    27  		Name: "http",
    28  		Members: starlark.StringDict{
    29  			"default_client": defaultClient,
    30  			"get":            starext.MakeBuiltin("http.get", defaultClient.get),
    31  			"head":           starext.MakeBuiltin("http.head", defaultClient.head),
    32  			"post":           starext.MakeBuiltin("http.post", defaultClient.post),
    33  			"client":         starext.MakeBuiltin("http.new_client", MakeClient),
    34  			"request":        starext.MakeBuiltin("http.new_request", MakeRequest),
    35  
    36  			// net/http errors
    37  			"err_not_supported":         starlarkerrors.NewError(http.ErrNotSupported),
    38  			"err_missing_boundary":      starlarkerrors.NewError(http.ErrMissingBoundary),
    39  			"err_not_multipart":         starlarkerrors.NewError(http.ErrNotMultipart),
    40  			"err_body_not_allowed":      starlarkerrors.NewError(http.ErrBodyNotAllowed),
    41  			"err_hijacked":              starlarkerrors.NewError(http.ErrHijacked),
    42  			"err_content_length":        starlarkerrors.NewError(http.ErrContentLength),
    43  			"err_abort_handler":         starlarkerrors.NewError(http.ErrAbortHandler),
    44  			"err_body_read_after_close": starlarkerrors.NewError(http.ErrBodyReadAfterClose),
    45  			"err_handler_timeout":       starlarkerrors.NewError(http.ErrHandlerTimeout),
    46  			"err_line_too_long":         starlarkerrors.NewError(http.ErrLineTooLong),
    47  			"err_missing_file":          starlarkerrors.NewError(http.ErrMissingFile),
    48  			"err_no_cookie":             starlarkerrors.NewError(http.ErrNoCookie),
    49  			"err_no_location":           starlarkerrors.NewError(http.ErrNoLocation),
    50  			"err_server_closed":         starlarkerrors.NewError(http.ErrServerClosed),
    51  			"err_skip_alt_protocol":     starlarkerrors.NewError(http.ErrSkipAltProtocol),
    52  			"err_use_last_response":     starlarkerrors.NewError(http.ErrUseLastResponse),
    53  		},
    54  	}
    55  }
    56  
    57  type Client struct {
    58  	do     func(thread *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error)
    59  	frozen bool
    60  }
    61  
    62  func NewClient(client *http.Client) *Client {
    63  	do := func(
    64  		thread *starlark.Thread,
    65  		fnname string,
    66  		args starlark.Tuple,
    67  		kwargs []starlark.Tuple,
    68  	) (starlark.Value, error) {
    69  		var req *Request
    70  		if err := starlark.UnpackArgs(fnname, args, kwargs,
    71  			"req", &req,
    72  		); err != nil {
    73  			return nil, err
    74  		}
    75  
    76  		response, err := client.Do(req.Request)
    77  		if err != nil {
    78  			return nil, err
    79  		}
    80  
    81  		rsp := &Response{
    82  			Response: response,
    83  		}
    84  		if err := starlarkthread.AddResource(thread, rsp); err != nil {
    85  			return nil, err
    86  		}
    87  		return rsp, nil
    88  	}
    89  	return &Client{do: do}
    90  }
    91  
    92  func (v *Client) String() string        { return "<client>" }
    93  func (v *Client) Type() string          { return "http.client" }
    94  func (v *Client) Freeze()               { v.frozen = true }
    95  func (v *Client) Truth() starlark.Bool  { return true }
    96  func (v *Client) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable type: %s", v.Type()) }
    97  
    98  type clientAttr func(*Client) starlark.Value
    99  
   100  var clientAttrs = map[string]clientAttr{
   101  	"do":   func(v *Client) starlark.Value { return starext.MakeMethod(v, "do", v.do) },
   102  	"get":  func(v *Client) starlark.Value { return starext.MakeMethod(v, "get", v.get) },
   103  	"post": func(v *Client) starlark.Value { return starext.MakeMethod(v, "post", v.post) },
   104  	"head": func(v *Client) starlark.Value { return starext.MakeMethod(v, "head", v.head) },
   105  }
   106  
   107  func (v *Client) Attr(name string) (starlark.Value, error) {
   108  	if a := clientAttrs[name]; a != nil {
   109  		return a(v), nil
   110  	}
   111  	return nil, nil
   112  }
   113  func (v *Client) AttrNames() []string {
   114  	names := make([]string, 0, len(clientAttrs))
   115  	for name := range clientAttrs {
   116  		names = append(names, name)
   117  	}
   118  	sort.Strings(names)
   119  	return names
   120  }
   121  
   122  var defaultClient = NewClient(http.DefaultClient)
   123  
   124  func (v *Client) Do(thread *starlark.Thread, fnname string, req *Request) (*Response, error) {
   125  	x, err := v.do(thread, fnname, starlark.Tuple{req}, nil)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	w, ok := x.(*Response)
   130  	if !ok {
   131  		return nil, fmt.Errorf("invalid response type: %T", x)
   132  	}
   133  	return w, nil
   134  }
   135  
   136  func Do(thread *starlark.Thread, fnname string, req *Request) (*Response, error) {
   137  	return defaultClient.Do(thread, fnname, req)
   138  }
   139  
   140  func (v *Client) get(thread *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   141  	var urlstr string
   142  	if err := starlark.UnpackArgs(fnname, args, kwargs,
   143  		"url", &urlstr,
   144  	); err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	ctx := starlarkthread.GetContext(thread)
   149  	req, err := http.NewRequestWithContext(ctx, "GET", urlstr, nil)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	r := &Request{Request: req}
   154  	return v.Do(thread, fnname, r)
   155  }
   156  
   157  func (v *Client) head(thread *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   158  	var urlstr string
   159  	if err := starlark.UnpackArgs(fnname, args, kwargs,
   160  		"url", &urlstr,
   161  	); err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	ctx := starlarkthread.GetContext(thread)
   166  	req, err := http.NewRequestWithContext(ctx, "HEAD", urlstr, nil)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	r := &Request{Request: req}
   171  	return v.Do(thread, fnname, r)
   172  }
   173  
   174  func (v *Client) post(thread *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   175  	var (
   176  		urlstr      string
   177  		contentType string
   178  		body        starlark.Value
   179  	)
   180  	if err := starlark.UnpackArgs(fnname, args, kwargs,
   181  		"url", &urlstr, "content_type", &contentType, "body", &body,
   182  	); err != nil {
   183  		return nil, err
   184  	}
   185  	rdr, err := makeReader(body)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	ctx := starlarkthread.GetContext(thread)
   191  	req, err := http.NewRequestWithContext(ctx, "HEAD", urlstr, rdr)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  	req.Header.Set("Content-Type", contentType)
   196  	r := &Request{Request: req}
   197  	return v.Do(thread, fnname, r)
   198  }
   199  
   200  func MakeClient(_ *starlark.Thread, name string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   201  	var fn starlark.Callable
   202  	if err := starlark.UnpackPositionalArgs(name, args, kwargs, 1, &fn); err != nil {
   203  		return nil, err
   204  	}
   205  
   206  	return &Client{
   207  		do: func(
   208  			thread *starlark.Thread,
   209  			_ string,
   210  			args starlark.Tuple,
   211  			kwargs []starlark.Tuple,
   212  		) (starlark.Value, error) {
   213  			return starlark.Call(thread, fn, args, kwargs)
   214  		},
   215  	}, nil
   216  }
   217  
   218  type Request struct {
   219  	*http.Request
   220  	frozen bool
   221  }
   222  
   223  func makeReader(v starlark.Value) (io.Reader, error) {
   224  	switch x := v.(type) {
   225  	case starlark.String:
   226  		return strings.NewReader(string(x)), nil
   227  	case starlark.Bytes:
   228  		return strings.NewReader(string(x)), nil
   229  	case io.Reader:
   230  		return x, nil
   231  	case starlark.NoneType, nil:
   232  		return http.NoBody, nil // none
   233  	default:
   234  		return nil, fmt.Errorf("unsupport type: %T", v)
   235  	}
   236  }
   237  
   238  func MakeRequest(thread *starlark.Thread, name string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   239  	var (
   240  		method string
   241  		urlstr string
   242  		body   starlark.Value
   243  	)
   244  	if err := starlark.UnpackArgs(name, args, kwargs,
   245  		"method", &method, "url", &urlstr, "body?", &body,
   246  	); err != nil {
   247  		return nil, err
   248  	}
   249  
   250  	r, err := makeReader(body)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  
   255  	request, err := http.NewRequest(method, urlstr, r)
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  	ctx := starlarkthread.GetContext(thread)
   260  	request = request.WithContext(ctx)
   261  
   262  	return &Request{
   263  		Request: request,
   264  	}, nil
   265  
   266  }
   267  
   268  func (v *Request) String() string {
   269  	return fmt.Sprintf("<request %s %s>", v.Request.Method, v.Request.URL.String())
   270  }
   271  func (v *Request) Type() string          { return "nethttp.request" }
   272  func (v *Request) Freeze()               { v.frozen = true }
   273  func (v *Request) Truth() starlark.Bool  { return v.Request != nil }
   274  func (v *Request) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable type: %s", v.Type()) }
   275  
   276  type requestAttr struct {
   277  	get func(*Request) (starlark.Value, error)
   278  	set func(*Request, starlark.Value) error
   279  }
   280  
   281  var requestAttrs = map[string]requestAttr{
   282  	"method": {
   283  		get: func(v *Request) (starlark.Value, error) {
   284  			return starlark.String(v.Method), nil
   285  		},
   286  		set: func(v *Request, val starlark.Value) error {
   287  			var x int
   288  			if err := starlark.AsInt(val, &x); err != nil {
   289  				return err
   290  			}
   291  			v.ProtoMajor = x
   292  			return nil
   293  		},
   294  	},
   295  	"url": {
   296  		get: func(v *Request) (starlark.Value, error) {
   297  			return starlark.String(v.URL.String()), nil
   298  		},
   299  		set: func(v *Request, val starlark.Value) error {
   300  			switch t := val.(type) {
   301  			case starlark.String:
   302  				u, err := url.Parse(string(t))
   303  				if err != nil {
   304  					return err
   305  				}
   306  				v.URL = u
   307  				return nil
   308  			default:
   309  				return fmt.Errorf("expected string")
   310  			}
   311  		},
   312  	},
   313  	"proto": {
   314  		get: func(v *Request) (starlark.Value, error) {
   315  			return starlark.String(v.Proto), nil
   316  		},
   317  		set: func(v *Request, val starlark.Value) error {
   318  			s, ok := starlark.AsString(val)
   319  			if !ok {
   320  				return fmt.Errorf("expected string")
   321  			}
   322  			v.Proto = s
   323  			return nil
   324  		},
   325  	},
   326  	"proto_major": {
   327  		get: func(v *Request) (starlark.Value, error) {
   328  			return starlark.MakeInt(v.ProtoMajor), nil
   329  		},
   330  		set: func(v *Request, val starlark.Value) error {
   331  			var x int
   332  			if err := starlark.AsInt(val, &x); err != nil {
   333  				return err
   334  			}
   335  			v.ProtoMajor = x
   336  			return nil
   337  		},
   338  	},
   339  	"proto_minor": {
   340  		get: func(v *Request) (starlark.Value, error) {
   341  			return starlark.MakeInt(v.ProtoMinor), nil
   342  		},
   343  		set: func(v *Request, val starlark.Value) error {
   344  			var x int
   345  			if err := starlark.AsInt(val, &x); err != nil {
   346  				return err
   347  			}
   348  			v.ProtoMinor = x
   349  			return nil
   350  		},
   351  	},
   352  	"header": {
   353  		get: func(v *Request) (starlark.Value, error) {
   354  			return &Header{
   355  				header: v.Header,
   356  				frozen: &v.frozen,
   357  			}, nil
   358  		},
   359  		set: func(v *Request, val starlark.Value) error {
   360  			switch t := val.(type) {
   361  			case *Header:
   362  				v.Header = t.header
   363  				return nil
   364  			default:
   365  				return fmt.Errorf("expected http.header")
   366  			}
   367  		},
   368  	},
   369  	"body": {
   370  		get: func(v *Request) (starlark.Value, error) {
   371  			return &starlarkio.Reader{Reader: v.Body}, nil
   372  		},
   373  		set: func(v *Request, val starlark.Value) error {
   374  			switch t := val.(type) {
   375  			case io.ReadCloser:
   376  				v.Body = t
   377  				return nil
   378  			default:
   379  				return fmt.Errorf("expected io.read_closer")
   380  			}
   381  		},
   382  	},
   383  	"content_length": {
   384  		get: func(v *Request) (starlark.Value, error) {
   385  			return starlark.MakeInt64(v.ContentLength), nil
   386  		},
   387  		set: func(v *Request, val starlark.Value) error {
   388  			var x int64
   389  			if err := starlark.AsInt(val, &x); err != nil {
   390  				return err
   391  			}
   392  			v.ContentLength = x
   393  			return nil
   394  		},
   395  	},
   396  	"basic_auth": {
   397  		get: func(v *Request) (starlark.Value, error) {
   398  			if username, password, ok := v.BasicAuth(); ok {
   399  				return starlark.Tuple{
   400  					starlark.String(username),
   401  					starlark.String(password),
   402  				}, nil
   403  			}
   404  			return starlark.None, nil
   405  		},
   406  		set: func(v *Request, val starlark.Value) error {
   407  			tpl, ok := val.(starlark.Tuple)
   408  			if !ok || len(tpl) != 2 {
   409  				return fmt.Errorf("expected length 2 tuple")
   410  			}
   411  			username, ok := starlark.AsString(tpl[0])
   412  			if !ok {
   413  				return fmt.Errorf("invalid type for username")
   414  			}
   415  			password, ok := starlark.AsString(tpl[1])
   416  			if !ok {
   417  				return fmt.Errorf("invalid type for password")
   418  			}
   419  			v.SetBasicAuth(username, password)
   420  			return nil
   421  		},
   422  	},
   423  }
   424  
   425  func (v *Request) Attr(name string) (starlark.Value, error) {
   426  	if fn, ok := requestAttrs[name]; ok {
   427  		return fn.get(v)
   428  	}
   429  	return nil, nil
   430  }
   431  func (v *Request) AttrNames() []string {
   432  	names := make([]string, 0, len(requestAttrs))
   433  	for name := range requestAttrs {
   434  		names = append(names, name)
   435  	}
   436  	sort.Strings(names)
   437  	return names
   438  }
   439  func (v *Request) SetField(name string, val starlark.Value) error {
   440  	if fn, ok := requestAttrs[name]; ok {
   441  		return fn.set(v, val)
   442  	}
   443  	return fmt.Errorf("unknown attribute %s", name)
   444  }
   445  
   446  type Response struct {
   447  	*http.Response
   448  	frozen bool
   449  }
   450  
   451  func (v *Response) Close() error          { return v.Body.Close() }
   452  func (v *Response) String() string        { return fmt.Sprintf("<response %s>", v.Status) }
   453  func (v *Response) Type() string          { return "nethttp.response" }
   454  func (v *Response) Freeze()               { v.frozen = true }
   455  func (v *Response) Truth() starlark.Bool  { return v.Response != nil }
   456  func (v *Response) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable type: %s", v.Type()) }
   457  
   458  // TODO: optional methods io.Closer, etc.
   459  var responseAttrs = map[string]func(v *Response) (starlark.Value, error){
   460  	"body": func(v *Response) (starlark.Value, error) {
   461  		return &starlarkio.Reader{Reader: v.Body}, nil
   462  	},
   463  }
   464  
   465  func (v *Response) Attr(name string) (starlark.Value, error) {
   466  	if fn, ok := responseAttrs[name]; ok {
   467  		return fn(v)
   468  	}
   469  	return nil, nil
   470  }
   471  func (v *Response) AttrNames() []string {
   472  	names := make([]string, 0, len(responseAttrs))
   473  	for name := range responseAttrs {
   474  		names = append(names, name)
   475  	}
   476  	sort.Strings(names)
   477  	return names
   478  }
   479  
   480  type Header struct {
   481  	header http.Header
   482  	frozen *bool
   483  }
   484  
   485  func (h *Header) String() string        { return fmt.Sprintf("%+v", h.header) }
   486  func (h *Header) Type() string          { return "http.header" }
   487  func (h *Header) Freeze()               { *h.frozen = true }
   488  func (h *Header) Truth() starlark.Bool  { return h.header != nil }
   489  func (h *Header) Hash() (uint32, error) { return 0, nil }
   490  
   491  type headerAttr func(*Header) (starlark.Value, error)
   492  
   493  var headerAttrs = map[string]headerAttr{
   494  	"add": func(h *Header) (starlark.Value, error) {
   495  		return starext.MakeMethod(h, "add", h.add), nil
   496  	},
   497  	"get": func(h *Header) (starlark.Value, error) {
   498  		return starext.MakeMethod(h, "get", h.get), nil
   499  	},
   500  	"del": func(h *Header) (starlark.Value, error) {
   501  		return starext.MakeMethod(h, "del", h.del), nil
   502  	},
   503  	"set": func(h *Header) (starlark.Value, error) {
   504  		return starext.MakeMethod(h, "set", h.set), nil
   505  	},
   506  	"values": func(h *Header) (starlark.Value, error) {
   507  		return starext.MakeMethod(h, "values", h.values), nil
   508  	},
   509  }
   510  
   511  func (v *Header) Attr(name string) (starlark.Value, error) {
   512  	if fn, ok := headerAttrs[name]; ok {
   513  		return fn(v)
   514  	}
   515  	return nil, nil
   516  }
   517  func (v *Header) AttrNames() []string {
   518  	names := make([]string, 0, len(headerAttrs))
   519  	for name := range responseAttrs {
   520  		names = append(names, name)
   521  	}
   522  	sort.Strings(names)
   523  	return names
   524  }
   525  func (v *Header) add(thread *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   526  	var key, value string
   527  	if err := starlark.UnpackPositionalArgs(fnname, args, kwargs, 2, &key, &value); err != nil {
   528  		return nil, err
   529  	}
   530  	v.header.Add(key, value)
   531  	return starlark.None, nil
   532  }
   533  func (v *Header) get(thread *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   534  	var key string
   535  	if err := starlark.UnpackPositionalArgs(fnname, args, kwargs, 1, &key); err != nil {
   536  		return nil, err
   537  	}
   538  	value := v.header.Get(key)
   539  	return starlark.String(value), nil
   540  }
   541  func (v *Header) del(thread *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   542  	var key string
   543  	if err := starlark.UnpackPositionalArgs(fnname, args, kwargs, 1, &key); err != nil {
   544  		return nil, err
   545  	}
   546  	v.header.Del(key)
   547  	return starlark.None, nil
   548  }
   549  func (v *Header) set(thread *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   550  	var key, value string
   551  	if err := starlark.UnpackPositionalArgs(fnname, args, kwargs, 2, &key, &value); err != nil {
   552  		return nil, err
   553  	}
   554  	v.header.Set(key, value)
   555  	return starlark.None, nil
   556  }
   557  func (v *Header) values(thread *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   558  	var key string
   559  	if err := starlark.UnpackPositionalArgs(fnname, args, kwargs, 1, &key); err != nil {
   560  		return nil, err
   561  	}
   562  	values := v.header.Values(key)
   563  	elems := make([]starlark.Value, len(values))
   564  	for i, v := range values {
   565  		elems[i] = starlark.String(v)
   566  	}
   567  	return starlark.NewList(elems), nil
   568  }