ASGI Bridge

How Pounce translates protocol events to the ASGI interface

3 min read 614 words

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:

  1. Builds scope — Extracts method, path, headers, query string from the parsed request
  2. Validates proxy headers — AppliesX-Forwarded-*from trusted peers, strips from untrusted
  3. Generates request ID — UUID4 hex or honoured from trusted proxy'sX-Request-ID
  4. Creates receive — Returns request body chunks ashttp.requestevents
  5. Creates send — Acceptshttp.response.start and http.response.body events, sanitises response headers (CRLF stripping), injects X-Request-ID
  6. 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:

sequenceDiagram participant P as Pounce participant A as ASGI App P->>A: lifespan.startup alt success A->>P: lifespan.startup.complete Note over P,A: Server runs — handling requests P->>A: lifespan.shutdown A->>P: lifespan.shutdown.complete else failure A->>P: lifespan.startup.failed Note over P: Server exits (non-zero) end
  1. Sendlifespan.startupevent
  2. Wait forlifespan.startup.complete or lifespan.startup.failed
  3. (server runs)
  4. Sendlifespan.shutdownevent
  5. Wait forlifespan.shutdown.complete

Security in the Bridge

Thesend callable applies several security measures during http.response.start:

  • CRLF sanitisation — Strips\r and \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 preserveContent-Length
  • Request ID injection — AppendsX-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