Write a little ASGI middleware
Sometimes you need a request ID, a timing header, or a place to attach context. This does not require founding another framework.
Three arguments, two directions
An ASGI app is called with (scope, receive, send). The scope describes the connection. receive() supplies request events; send() emits response events. Middleware can wrap either callable and then delegate to the inner app.
This example assigns a request ID, makes it available to handlers, and adds response headers when http.response.start is sent. It measures time until response headers, not the complete download time. Names are useful when they mean things.
Add request context and timing
Save as context_app.py, then run weft context_app:app.
from time import perf_counter
from uuid import uuid4
from wred import App
class RequestContext:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope['type'] != 'http':
return await self.app(scope, receive, send)
request_id = uuid4().hex
started = perf_counter()
scope = {**scope, 'state': {**scope.get('state', {}), 'request_id': request_id}}
async def send_with_context(message):
if message['type'] == 'http.response.start':
elapsed_ms = (perf_counter() - started) * 1000
message = {
**message,
'headers': [
*message.get('headers', []),
(b'x-request-id', request_id.encode('ascii')),
(b'server-timing', f'app;dur={elapsed_ms:.2f}'.encode('ascii')),
],
}
await send(message)
await self.app(scope, receive, send_with_context)
api = App()
@api.route('/api/hello')
async def hello(request):
return {'request_id': request.scope['state']['request_id']}
app = RequestContext(api)curl -i http://127.0.0.1:8000/api/helloThe body’s request_id matches X-Request-ID. Server-Timing reports the elapsed milliseconds up to response start. Each request keeps its own variables inside __call__; shared instance attributes would invite concurrent requests to exchange identities.
Keep the wrapper well behaved
- Pass through protocols you don’t handle. That lets a lifespan-aware outer app keep doing its job.
- Copy the scope when adding request-local data. Preserve existing
stateentries. - Preserve existing response headers, including repeated Set-Cookie fields.
- If you modify the response body, update or remove Content-Length as appropriate.
- Don’t consume the request body unless you also arrange to replay it to the inner app.
Our wrapper leaves the body alone. It also passes lifespan through rather than implementing it; wrapping a bare Wred app still leaves you with an HTTP-only app.
The pure ASGI middleware guide describes this pattern. Middleware can also come from other packages: the interoperability is in the ASGI call, not in matching logos.
Use Uvicorn’s proxy-header middleware with another server
You can reuse Uvicorn’s ProxyHeadersMiddleware while serving with Weft. Install uvicorn in addition to the notebook’s dependencies, save this as proxy_app.py at the repository root, and run weft proxy_app:app --host 127.0.0.1 --lifespan on.
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
from example_app.app import create_app
# Use only behind a trusted proxy connecting from IPv4 loopback.
app = ProxyHeadersMiddleware(create_app(), trusted_hosts=['127.0.0.1'])It accepts forwarded scheme and client information only from the listed proxy peers. For this example, the reverse proxy must connect over IPv4 loopback. Keep the backend private; a client that reaches it directly from a trusted address can supply those headers too.
This is an alternative to Weft’s --forwarded-allow-ips option used in the deployment chapters. Pick one owner for proxy-header processing. See the Uvicorn implementation.
Authentication middleware can use the same scope
Starlette’s authentication middleware places authentication information in scope["user"] and scope["auth"]. A Wred handler can read those keys directly. The middleware’s authentication backend and your authorization rules remain application choices.
The notebook uses its own @protected decorator instead. That decorator adds request.user itself. Don’t assume the property exists in unrelated Wred apps, or that wrapping with authentication middleware automatically protects every route.
