Skip to content

Requests

The first argument to every handler is the request.

@app.get("/me")
async def me(request: Request):
    token = request.header("authorization")        # case-insensitive, None if absent
    theme = request.cookies.get("theme", "light")
    return {"token": token, "theme": theme}
attribute what it is
method the HTTP method, uppercase
path the request path
query the raw query string
body the raw body as bytes
headers every header, lowercased, as a dict
cookies parsed cookies as a dict
header(name, default=None) one header by name, case-insensitively

Headers are lazy

Headers stay in hyper's own map until Python asks for them. request.header(name) looks one up without building anything; request.headers builds the whole dict and should be avoided on a hot path. A handler that never reads a header pays nothing for the ones that arrived.

A header that is not valid UTF-8 reads as absent rather than raising, which keeps a malformed request from becoming a 500.

The request is immutable

Request is a frozen class. Nothing on the Python side can mutate it, which is why it needs no locking even when several worker loops are running in the same process on a free-threaded build. Pass values between middleware and handlers through a ContextVar or a dependency, not by attaching attributes to the request.

Repeated headers

Repeated headers are joined with ", ", as HTTP itself defines. Cookie is parsed for you into request.cookies.