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¶
- reverse proxy setup: Reverse Proxy (Nginx)
- TLS strategy: HTTPS and TLS
- process model: Workers
- reliability behavior: Server Behavior
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