Skip to content

Quickstart

This quickstart moves from easy to advanced in one page.

Stage 1: Hello World (Easy)

from __future__ import annotations


async def app(scope, receive, send):
    """Return a plain-text greeting for HTTP requests."""
    if scope["type"] != "http":
        return

    body = b"Hello from Palfrey"
    await send(
        {
            "type": "http.response.start",
            "status": 200,
            "headers": [
                (b"content-type", b"text/plain; charset=utf-8"),
                (b"content-length", str(len(body)).encode("ascii")),
            ],
        }
    )
    await send({"type": "http.response.body", "body": body})

Run:

palfrey main:app --host 127.0.0.1 --port 8000

Check:

curl http://127.0.0.1:8000

Stage 2: JSON Response (Easy)

from __future__ import annotations

import json
from datetime import datetime, timezone


async def app(scope, receive, send):
    """Return a JSON payload for HTTP requests."""
    if scope["type"] != "http":
        return

    payload = {
        "service": "palfrey-demo",
        "path": scope.get("path", "/"),
        "timestamp": datetime.now(timezone.utc).isoformat(),
    }
    body = json.dumps(payload).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})

Start with reload in development:

palfrey main:app --reload --reload-dir .

Stage 3: Read Request Body (Intermediate)

from __future__ import annotations


async def read_body(receive):
    """Collect request body chunks into one byte string."""
    body = b""
    more_body = True
    while more_body:
        message = await receive()
        body += message.get("body", b"")
        more_body = message.get("more_body", False)
    return body


async def app(scope, receive, send):
    """Echo request size in plain text."""
    if scope["type"] != "http":
        return

    body = await read_body(receive)
    response = f"Received {len(body)} bytes".encode()
    await send(
        {
            "type": "http.response.start",
            "status": 200,
            "headers": [
                (b"content-type", b"text/plain; charset=utf-8"),
                (b"content-length", str(len(response)).encode("ascii")),
            ],
        }
    )
    await send({"type": "http.response.body", "body": response})

This pattern is useful for webhooks and JSON APIs that process request payloads.

Stage 4: WebSocket Echo (Intermediate)

from __future__ import annotations


async def app(scope, receive, send):
    """Accept websocket clients and echo text messages."""
    if scope["type"] != "websocket":
        return

    await send({"type": "websocket.accept"})
    while True:
        message = await receive()
        message_type = message["type"]

        if message_type == "websocket.disconnect":
            break

        if message_type == "websocket.receive" and "text" in message:
            await send({"type": "websocket.send", "text": message["text"]})

Run with explicit websocket mode:

palfrey main:app --ws websockets

Stage 5: App Factory (Intermediate)

from __future__ import annotations


async def _app(scope, receive, send):
    if scope["type"] != "http":
        return

    body = b"Factory app booted"
    await send(
        {
            "type": "http.response.start",
            "status": 200,
            "headers": [
                (b"content-type", b"text/plain"),
                (b"content-length", str(len(body)).encode("ascii")),
            ],
        }
    )
    await send({"type": "http.response.body", "body": body})


def create_app():
    """Return the ASGI app instance."""
    return _app

Run with --factory:

palfrey --factory main:create_app

Stage 6: Programmatic Startup (Advanced)

from __future__ import annotations

from palfrey import run


async def app(scope, receive, send):
    """Simple HTTP responder for programmatic startup demos."""
    if scope["type"] != "http":
        return

    body = b"programmatic run"
    await send(
        {
            "type": "http.response.start",
            "status": 200,
            "headers": [(b"content-type", b"text/plain"), (b"content-length", b"16")],
        }
    )
    await send({"type": "http.response.body", "body": body})


if __name__ == "__main__":
    run("docs_src.reference.programmatic_run:app", host="127.0.0.1", port=8000)

Use this when Palfrey startup is coordinated by another Python process.

Stage 7: Environment-Driven Configuration (Advanced)

from __future__ import annotations

import os

PREFILL = {
    "PALFREY_APP": "myservice.main:app",
    "PALFREY_HOST": "0.0.0.0",
    "PALFREY_PORT": "8000",
    "PALFREY_LOG_LEVEL": "info",
}

for key, value in PREFILL.items():
    os.environ.setdefault(key, value)

print("Configured environment variables:")
for key in sorted(PREFILL):
    print(f"{key}={os.environ[key]}")

Typical shell usage:

export PALFREY_HOST=0.0.0.0
export PALFREY_PORT=9000
palfrey main:app

Stage 8: Pick a Production Direction

Stage 9: Gunicorn + PalfreyWorker (Advanced)

Use this when you want Gunicorn process supervision with Palfrey protocol/runtime handling.

gunicorn main:app -k palfrey.workers.PalfreyWorker -w 4 -b 0.0.0.0:8000

Alternate worker class (h11-specific):

gunicorn main:app -k palfrey.workers.PalfreyH11Worker -w 4 -b 0.0.0.0:8000

Example Gunicorn config file:

"""Example Gunicorn config using Palfrey worker classes."""

from __future__ import annotations

bind = "0.0.0.0:8000"
workers = 4
worker_class = "palfrey.workers.PalfreyWorker"

# Optional Gunicorn settings that interact with Palfrey worker runtime.
keepalive = 5
timeout = 30
max_requests = 20000
max_requests_jitter = 2000

# Forwarded header trust can also be controlled in Gunicorn settings.
forwarded_allow_ips = "127.0.0.1"

Stage 10: HTTP/2 and HTTP/3 modes (Advanced)

HTTP/2 mode:

palfrey main:app --http h2 --host 127.0.0.1 --port 8000

HTTP/3 mode (TLS required):

palfrey main:app --http h3 --ws none --host 127.0.0.1 --port 8443 --ssl-certfile cert.pem --ssl-keyfile key.pem

Non-Technical Summary

You just learned three maturity levels:

  • basic app startup
  • practical development workflow
  • production-oriented runtime control