Request & Response¶
Every handler receives a Request — everything the client sent (URL, headers,
query params, body) — and returns a Response — everything you send back.
This page is the full reference for both.
Both classes are EndoCore's own thin wrappers over raw ASGI, no Starlette underneath.
Request¶
async def handler(request):
request.method # "POST"
request.path # "/v1/user/42"
request.path_params["id"] # "42"
request.query.get("page") # first value or None
request.query.getlist("tag") # all values
request.headers.get("content-type") # lower-cased keys
request.cookies.get("session")
data = await request.json() # parsed body (BadRequest on bad JSON)
raw = await request.body() # bytes
form = await request.form() # urlencoded or multipart
files = await request.files() # {field: UploadFile}
Query params¶
request.query is a multi-dict: []/.get() return the first value,
.getlist() returns all.
Forms & uploads¶
await request.form() parses both application/x-www-form-urlencoded and
multipart/form-data. Uploaded files are UploadFile objects:
form = await request.form()
name = form.get("name")
upload = form.files["avatar"] # UploadFile
content = upload.read() # bytes
upload.filename, upload.content_type, upload.size
Cookies (incl. signed)¶
request.cookies.get("session")
request.get_signed_cookie("session", secret="…", max_age=3600) # verifies HMAC
Streaming large bodies¶
The app enforces a body-size limit (max_body_size, default 16 MB) — oversize
bodies raise PayloadTooLarge (413).
request.stream() rarely streams from the network in practice
The always-on logging middleware (outermost layer on every request, not
something you can remove per-route) calls request.json() before
your handler runs at all — for every request, regardless of method or
content type — purely so it can log a masked payload even if the handler
later raises. That call fully drains the body into memory first (bounded
by max_body_size), and only then attempts to parse it as JSON,
discarding the result silently if it isn't. By the time your handler's
request.stream() runs, the body is already buffered — stream() just
replays those bytes as a single chunk (see its docstring: "if the body
was already buffered, yields it once"). request.stream() is real and
correctly implemented for the case where nothing upstream has touched
the body yet (e.g. calling Request directly outside the full
middleware pipeline, as the framework's own tests do), but inside a
normal running app, expect the full request body to already be resident
in memory (up to max_body_size) before any handler-level code —
streaming or not — gets to see it.
Response¶
from endocore import Response
Response.json({"ok": True}, status=201, headers={"X-Foo": "bar"})
Response.text("hello")
Response.redirect("/login", status=307)
Response.no_content() # 204
Response(b"...", media_type="image/png") # raw bytes
A handler can also just return a value and let the framework coerce it:
| Return | Becomes |
|---|---|
Response |
as-is |
dict / list |
JSON 200 |
str |
text 200 |
None |
204 |
(content, status) / (content, status, headers) |
JSON with that status |
Cookies¶
resp = Response.json({"ok": True})
resp.set_cookie("session", "abc", httponly=True, samesite="lax", max_age=3600)
resp.set_signed_cookie("session", "abc", secret="…") # HMAC-signed
resp.delete_cookie("session")
Background tasks¶
Run work after the response is sent: