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 }