Skip to content

ASGI Fundamentals

ASGI is the contract between Palfrey and your application.

The Three Inputs Every ASGI App Receives

  • scope: metadata about protocol and connection context
  • receive: async callable yielding inbound messages
  • send: async callable accepting outbound messages

Minimal app example:

from __future__ import annotations


async def app(scope, receive, send):
    """Serve HTTP and reject unknown protocols."""
    if scope["type"] != "http":
        raise RuntimeError(f"Unsupported scope type: {scope['type']}")

    await send(
        {
            "type": "http.response.start",
            "status": 200,
            "headers": [(b"content-type", b"text/plain; charset=utf-8")],
        }
    )
    await send({"type": "http.response.body", "body": b"ASGI app is running"})

Scope inspector example:

from __future__ import annotations

import json


async def app(scope, receive, send):
    """Return protocol metadata useful during debugging."""
    if scope["type"] != "http":
        return

    payload = {
        "type": scope["type"],
        "method": scope.get("method"),
        "path": scope.get("path"),
        "root_path": scope.get("root_path"),
        "client": scope.get("client"),
        "server": scope.get("server"),
        "scheme": scope.get("scheme"),
    }
    body = json.dumps(payload, indent=2, default=str).encode("utf-8")

    await send(
        {
            "type": "http.response.start",
            "status": 200,
            "headers": [
                (b"content-type", b"application/json"),
                (b"content-length", str(len(body)).encode("ascii")),
            ],
        }
    )
    await send({"type": "http.response.body", "body": body})

Scope by Protocol

HTTP scope

Typical fields:

  • type = "http"
  • method
  • path, raw_path, query_string
  • headers
  • client, server

WebSocket scope

Typical fields:

  • type = "websocket"
  • subprotocols
  • extensions
  • shared network/path/header fields

Lifespan scope

Typical fields:

  • type = "lifespan"
  • app startup/shutdown channel

Message Sequences

HTTP

  1. app receives one or more http.request
  2. app sends http.response.start
  3. app sends one or more http.response.body

WebSocket

  1. app receives websocket.connect
  2. app sends websocket.accept or websocket.close
  3. app and client exchange messages
  4. disconnect/close completes session

Lifespan

  1. app receives startup event
  2. app initializes shared resources
  3. app receives shutdown event
  4. app releases resources

Common ASGI Mistakes

  • not sending http.response.start before body
  • returning non-None from ASGI app
  • sending websocket messages before websocket.accept
  • assuming headers are strings instead of bytes

Why engineers care

ASGI correctness directly affects interoperability and reliability.

Why non-technical stakeholders should care

A standard contract reduces integration risk and speeds migration between frameworks and runtimes.