Overview
The ASGI bridge is the layer between Pounce's protocol parsers and your ASGI application. It constructs thescope dict, creates receive and sendcallables, and manages the per-request lifecycle.
HTTP Bridge
For HTTP requests, the bridge:
- Builds scope — Extracts method, path, headers, query string from the parsed request
- Validates proxy headers — Applies
X-Forwarded-*from trusted peers, strips from untrusted - Generates request ID — UUID4 hex or honoured from trusted proxy's
X-Request-ID - Creates receive — Returns request body chunks as
http.requestevents - Creates send — Accepts
http.response.startandhttp.response.bodyevents, sanitises response headers (CRLF stripping), injectsX-Request-ID - Tracks state — Monitors response status, headers sent, body complete
# Simplified flow
scope = build_scope(request, config) # + proxy header validation
request_id = extract_or_generate(headers) # UUID4 or from trusted proxy
receive = create_receive(request_body)
send = create_send(connection, config, request_id=request_id)
await app(scope, receive, send)
Scope Construction
The ASGI scope follows the ASGI HTTP Connection Scope specification:
{
"type": "http",
"asgi": {"version": "3.0", "spec_version": "2.4"},
"http_version": "1.1", # or "2"
"method": "GET",
"path": "/",
"root_path": "",
"scheme": "https",
"query_string": b"",
"headers": [(b"host", b"example.com"), ...],
"server": ("127.0.0.1", 8000),
"client": ("192.168.1.1", 54321),
}
Streaming Send
Thesendcallable writes response chunks directly to the socket:
# Your ASGI app sends:
await send({"type": "http.response.start", "status": 200, "headers": [...]})
await send({"type": "http.response.body", "body": b"chunk1", "more_body": True})
await send({"type": "http.response.body", "body": b"chunk2", "more_body": True})
await send({"type": "http.response.body", "body": b"", "more_body": False})
# Pounce writes each chunk to the socket immediately — no buffering
Disconnect Detection
Pounce monitors the client connection concurrently. If the client disconnects mid-request, your app receives ahttp.disconnect event from receive():
event = await receive()
if event["type"] == "http.disconnect":
# Client disconnected — clean up and return
return
Lifespan Bridge
The lifespan bridge handles application startup and shutdown:
- Send
lifespan.startupevent - Wait for
lifespan.startup.completeorlifespan.startup.failed - (server runs)
- Send
lifespan.shutdownevent - Wait for
lifespan.shutdown.complete
Security in the Bridge
Thesend callable applies several security measures during http.response.start:
- CRLF sanitisation — Strips
\rand\nfrom all response header names and values - Bodyless guard — Disables compression for 204/304 responses (no body allowed per RFC 9110)
- HEAD guard — Disables compression for HEAD responses to preserve
Content-Length - Request ID injection — Appends
X-Request-IDresponse header
These protections are active on both HTTP/1.1 and HTTP/2 bridges.
See Also
- Architecture — Full pipeline overview
- API Reference — ASGI type definitions
- Security — Full security feature reference
- Observability — Health checks, request IDs, metrics