Module spin_sdk.http
Module with helpers for wasi http
Functions
async def send(request: Request) ‑> Response-
Expand source code
async def send(request: Request) -> Response: """Send an HTTP request and return a response or raise an error""" match request.method: case "GET": method: Method = Method_Get() case "HEAD": method = Method_Head() case "POST": method = Method_Post() case "PUT": method = Method_Put() case "DELETE": method = Method_Delete() case "CONNECT": method = Method_Connect() case "OPTIONS": method = Method_Options() case "TRACE": method = Method_Trace() case "PATCH": method = Method_Patch() case _: method = Method_Other(request.method) url_parsed = parse.urlparse(request.uri) match url_parsed.scheme: case "http": scheme: Scheme = Scheme_Http() case "https": scheme = Scheme_Https() case "": scheme = Scheme_Http() case _: scheme = Scheme_Other(url_parsed.scheme) headers_dict = request.headers # Add a `content-length` header if the caller didn't include one, but did # specify a body: if headers_dict.get('content-length') is None: content_length = len(request.body) if request.body is not None else 0 # Make a copy rather than mutate in place, since the caller might not # expect us to mutate it: headers_dict = dict(headers_dict) headers_dict['content-length'] = str(content_length) headers = list(map( lambda pair: (pair[0], bytes(pair[1], "utf-8")), headers_dict.items() )) tx, rx = wit.byte_stream() componentize_py_async_support.spawn(_copy(request.body, tx)) outgoing_request = WasiRequest.new(Fields.from_list(headers), rx, _trailers_future(), None)[0] outgoing_request.set_method(method) outgoing_request.set_scheme(scheme) if url_parsed.netloc == '': if isinstance(scheme, Scheme_Http): authority = ":80" else: authority = ":443" else: authority = url_parsed.netloc outgoing_request.set_authority(authority) path_and_query = url_parsed.path if url_parsed.query: path_and_query += '?' + url_parsed.query outgoing_request.set_path_with_query(path_and_query) incoming_response = await client.send(outgoing_request) status = incoming_response.get_status_code() headers = incoming_response.get_headers().copy_all() rx, trailers = WasiResponse.consume_body(incoming_response, _unit_future()) body = bytearray() with rx: while not rx.writer_dropped: body += await rx.read(16 * 1024) return Response( status, dict(map( lambda pair: (pair[0], str(pair[1], "utf-8")), headers )), bytes(body) )Send an HTTP request and return a response or raise an error
def strip_forbidden_headers(headers: MutableMapping[str, str]) ‑> MutableMapping[str, str]-
Expand source code
def strip_forbidden_headers(headers:MutableMapping[str, str]) -> MutableMapping[str, str]: """ Strips forbidden headers for requests and responses originating from guest apps, per wasmtime/Spin """ # See https://github.com/bytecodealliance/wasmtime/blob/e9e1665c5ef150d618bd8c21fb355c063596d6f7/crates/wasi-http/src/lib.rs#L42-L52 for header in [ "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "proxy-connection", "transfer-encoding", "upgrade", "host", "http2-settings" ]: try: del headers[header] except KeyError: pass return headersStrips forbidden headers for requests and responses originating from guest apps, per wasmtime/Spin
Classes
class Handler (*args, **kwargs)-
Expand source code
class Handler(Base): """Simplified handler for incoming HTTP requests using blocking, buffered I/O.""" async def handle_request(self, request: Request) -> Response: """Handle an incoming HTTP request and return a response or raise an error""" raise NotImplementedError async def handle(self, request: WasiRequest) -> WasiResponse: method = request.get_method() if isinstance(method, Method_Get): method_str = "GET" elif isinstance(method, Method_Head): method_str = "HEAD" elif isinstance(method, Method_Post): method_str = "POST" elif isinstance(method, Method_Put): method_str = "PUT" elif isinstance(method, Method_Delete): method_str = "DELETE" elif isinstance(method, Method_Connect): method_str = "CONNECT" elif isinstance(method, Method_Options): method_str = "OPTIONS" elif isinstance(method, Method_Trace): method_str = "TRACE" elif isinstance(method, Method_Patch): method_str = "PATCH" elif isinstance(method, Method_Other): method_str = method.value else: raise AssertionError headers = request.get_headers().copy_all() request_uri = request.get_path_with_query() rx, trailers = WasiRequest.consume_body(request, _unit_future()) body = bytearray() with rx: while not rx.writer_dropped: body += await rx.read(16 * 1024) if request_uri is None: uri = "/" else: uri = request_uri try: simple_response = await self.handle_request(Request( method_str, uri, dict(map(lambda pair: (pair[0], str(pair[1], "utf-8")), headers)), bytes(body) )) except: traceback.print_exc() response = WasiResponse.new(Fields(), None, _trailers_future())[0] response.set_status_code(500) return response if simple_response.headers.get('content-length') is None: content_length = len(simple_response.body) if simple_response.body is not None else 0 simple_response.headers['content-length'] = str(content_length) tx, rx = wit.byte_stream() componentize_py_async_support.spawn(_copy(simple_response.body, tx)) response = WasiResponse.new(Fields.from_list(list(map( lambda pair: (pair[0], bytes(pair[1], "utf-8")), simple_response.headers.items() ))), rx, _trailers_future())[0] response.set_status_code(simple_response.status) return responseSimplified handler for incoming HTTP requests using blocking, buffered I/O.
Ancestors
- HttpHandler
- typing.Protocol
- typing.Generic
Methods
async def handle_request(self,
request: Request) ‑> Response-
Expand source code
async def handle_request(self, request: Request) -> Response: """Handle an incoming HTTP request and return a response or raise an error""" raise NotImplementedErrorHandle an incoming HTTP request and return a response or raise an error
Inherited members
class Request (method: str, uri: str, headers: MutableMapping[str, str], body: bytes | None)-
Expand source code
@dataclass class Request: """An HTTP request""" method: str uri: str headers: MutableMapping[str, str] body: Optional[bytes]An HTTP request
Instance variables
var body : bytes | Nonevar headers : MutableMapping[str, str]var method : strvar uri : str
class Response (status: int, headers: MutableMapping[str, str], body: bytes | None)-
Expand source code
@dataclass class Response: """An HTTP response""" status: int headers: MutableMapping[str, str] body: Optional[bytes]An HTTP response
Instance variables
var body : bytes | Nonevar headers : MutableMapping[str, str]var status : int