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.
Protocol Handler Interface
Pounce's protocol layer is modular. HTTP/1.1, HTTP/2, and WebSocket handlers use small parser/serializer interfaces; HTTP/3 is integrated through the zoomies QUIC datagram path and has separate lifecycle and parity caveats.
| Handler | Protocol | Module |
|---|---|---|
H1Protocol |
HTTP/1.1 (h11) | protocols/h1.py |
H2Protocol |
HTTP/2 (h2) | protocols/h2.py |
| HTTP/3 integration | HTTP/3 (bengal-zoomies) | protocols/h3.py, _h3_handler.py, asgi/h3_bridge.py |
WSProtocol |
WebSocket (wsproto) | protocols/ws.py |
For the H1/H2/WebSocket parser paths, handlers feed raw bytes in, yield parsed events, serialize responses back to bytes, and track connection state. The worker selects the path based on connection type, TLS+ALPN, WebSocket upgrade, and HTTP/3 configuration.
Note
The protocol handler interface is internal and may change between releases. Pin your pounce version if building custom handlers.
See Also
- Architecture — Full pipeline overview
- API Reference — ASGI type definitions
- Security — Security features
- Observability — Health checks, request IDs, metrics