Middleware
Status: Draft; implementation checked against the development source on 2026-09-08.
What it does
Wraps request handling in an ordered chain of cross-cutting stages — error handling, CORS, logging, sessions, auth, and well-known endpoints. Each stage is an object that always exists; you arm it through config.
When to use it
Whenever you need behaviour that applies across routes rather than inside a single
handler: turning exceptions into clean responses, adding CORS headers, logging
requests, and so on. Use the middleware kwarg to configure stages. On AsgiServer, errors,
sessions and authentication are active by default; the last two are armed by
the capability mixins.
The built-in chain
The registry ships these middleware with a priority. The table shows the
registry default; AsgiServer additionally arms session and auth via its mixins.
Lower priority number = more outer (runs first on the way in, last on the way
out):
Name |
Priority |
Default |
Notes |
|---|---|---|---|
|
100 |
on |
maps exceptions and unmatched paths to status codes |
|
150 |
off |
|
|
200 |
off |
request logging |
|
300 |
off |
CORS headers |
|
400 |
off |
armed automatically by |
|
450 |
off |
armed automatically by |
Only the http scope is processed by the chain.
SessionMixin and AuthMixin arm their stages simply by being composed into
AsgiServer, even without session_store or auth kwargs. Explicit
middleware={"session": False, "auth": False} disables them.
Setup — arming a stage
Pass the middleware kwarg. A value of True arms a stage with its defaults; a
dict arms it with options; False disarms it.
from genro_asgi import AsgiServer, RoutedApplication
from genro_routes import route
class App(RoutedApplication):
mount = ""
@route()
def index(self) -> dict:
return {"ok": True}
server = AsgiServer(
applications=[App()],
middleware={
"cors": True,
"logging": True,
},
)
server.serve(host="127.0.0.1", port=8000)
CORS options
cors accepts an options dict:
server = AsgiServer(
applications=[App()],
middleware={"cors": {
"allow_origins": ["https://example.com"],
"allow_credentials": True,
"max_age": 600,
}},
)
Custom middleware
Register your own middleware class in the registry, then arm it like any built-in stage:
from genro_asgi import BaseMiddleware
class StampMiddleware(BaseMiddleware):
...
server = AsgiServer(
applications=[App()],
middleware_registry={"stamp": StampMiddleware},
middleware={"stamp": {...}},
)
middleware_registry={"stamp": StampMiddleware}teaches the server the new name.middleware={"stamp": {...}}arms it (and passes its options).
The individual built-in middleware classes are importable from
genro_asgi.middleware (e.g. from genro_asgi.middleware import CORSMiddleware), and BaseMiddleware / MiddlewareMixin from genro_asgi.
How to verify it
With cors armed, a request carrying an Origin gets CORS headers back:
$ curl -i -H "Origin: https://example.com" http://127.0.0.1:8000/index
HTTP/1.1 200 OK
access-control-allow-origin: https://example.com
With logging armed, requests appear in the server’s log output.
Gotchas
The registry enables
errors; the shippedAsgiServeralso armssessionandauth. CORS, logging and wellknown require explicit configuration.A call the handler cannot take is answered by the dispatcher, never a
500, on two distinct codes: a call that does not fit the signature — an unknown keyword, a missing required argument, one positional too many — answers400, while values the signature accepts and the handler’s validation rejects answer422Unprocessable Content (RFC 9110 §15.5.21: the request is well formed, its semantics are not). What the handler BODY raises is mapped to neither and reacheserrorsas a500; a handler raisingHTTPBadRequestitself still answers400, never remapped.An unknown middleware name in
middleware={...}raisesValueError. If you are arming a custom stage, register it inmiddleware_registryfirst.Ordering is by priority, lower = more outer. A custom stage lands according to its own priority in the chain.
Session and auth are already armed on
AsgiServer; configure their middleware options when needed, or explicitly disable them.