Files
responder/docs/source/tutorial-middleware.rst
kennethreitz 3d5f3c7e93 Improve documentation across the board (#602)
## Summary

- **Deployment guide**: health check endpoint, Docker Compose example,
Caddy reverse proxy config, Procfile pattern, production checklist
- **API reference**: quick usage examples for every class (API, Request,
Response, RouteGroup, BackgroundQueue, RateLimiter, status helpers)
- **Feature tour**: new Pydantic validation and content negotiation
sections, expanded MessagePack docs
- **Testing guide**: rate limiting and mounted WSGI app test examples,
Werkzeug 3.1.7 tip
- **Middleware tutorial**: pure ASGI middleware example (no
BaseHTTPMiddleware dependency)
- **CLI guide**: environment variables section (PORT, SECRET_KEY)
- **Homepage**: updated feature list with SSE, rate limiting, Pydantic,
content negotiation, route groups
- **Backlog**: removed already-implemented items, added current ideas

+362 lines of docs, no code changes.

## Test plan
- [x] `make html` builds cleanly with no warnings
- [x] All 199 tests pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Kenneth Reitz <me@kennethreitz.org>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Andreas Motl <andreas.motl@panodata.org>
2026-03-24 15:51:55 -04:00

171 lines
5.7 KiB
ReStructuredText

Writing Middleware
==================
Middleware sits between the server and your route handlers, processing
every request and response that flows through your application. It's the
right tool for cross-cutting concerns — things that apply to *all*
requests, not just specific routes.
Common middleware use cases:
- Request logging and timing
- Authentication and authorization
- Adding security headers
- Request ID generation
- Rate limiting
- Response compression (built-in)
Hooks vs. Middleware
--------------------
Responder gives you two levels of request processing:
**Hooks** (``before_request`` / ``after_request``) run inside Responder's
routing layer. They receive Responder's ``req`` and ``resp`` objects and
are the simplest way to add behavior::
@api.route(before_request=True)
def add_header(req, resp):
resp.headers["X-Powered-By"] = "Responder"
@api.after_request()
def log_request(req, resp):
print(f"{req.method} {req.url.path} -> {resp.status_code}")
**Middleware** runs at the ASGI level, wrapping the entire application.
It's more powerful but more complex — you work with raw ASGI scopes
instead of Responder objects. Use middleware when you need to process
requests *before* they reach Responder's routing, or when you need to
integrate with Starlette middleware.
Using Starlette Middleware
--------------------------
Responder is built on Starlette, so any Starlette middleware works
out of the box::
from starlette.middleware.base import BaseHTTPMiddleware
class TimingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
import time
start = time.time()
response = await call_next(request)
duration = time.time() - start
response.headers["X-Response-Time"] = f"{duration:.3f}s"
return response
api.add_middleware(TimingMiddleware)
The ``dispatch`` method receives a Starlette ``Request`` and a
``call_next`` function. Call ``call_next(request)`` to pass the request
to the next middleware (or to your route handler). The return value is
a Starlette ``Response`` that you can modify before it's sent.
Built-in Middleware
-------------------
Responder configures several middleware components automatically:
- **GZipMiddleware** — compresses responses larger than 500 bytes
- **TrustedHostMiddleware** — validates the ``Host`` header
- **ServerErrorMiddleware** — catches unhandled exceptions
- **ExceptionMiddleware** — routes exceptions to your handlers
- **SessionMiddleware** — manages signed cookie sessions
Optional middleware you can enable:
- **CORSMiddleware**``api = responder.API(cors=True)``
- **HTTPSRedirectMiddleware**``api = responder.API(enable_hsts=True)``
Adding Third-Party Middleware
-----------------------------
Any ASGI middleware can be added with ``api.add_middleware()``::
from some_package import SomeMiddleware
api.add_middleware(SomeMiddleware, option1="value", option2=True)
Keyword arguments are passed to the middleware's constructor.
Middleware Order
----------------
Middleware wraps your application like layers of an onion. The *last*
middleware added is the *outermost* layer — it sees the request first
and the response last.
Responder's built-in middleware stack (from outermost to innermost):
1. SessionMiddleware
2. ServerErrorMiddleware
3. CORSMiddleware (if enabled)
4. TrustedHostMiddleware
5. HTTPSRedirectMiddleware (if enabled)
6. GZipMiddleware
7. ExceptionMiddleware
8. Your routes
When you call ``api.add_middleware()``, your middleware is added *outside*
the existing stack. Keep this in mind for ordering dependencies — if
middleware A depends on middleware B having run first, add B before A.
Writing Pure ASGI Middleware
----------------------------
For maximum performance and control, you can write middleware as a plain
ASGI application. This bypasses Starlette's ``BaseHTTPMiddleware``
abstraction — it's faster and gives you direct access to the ASGI
protocol::
class SecurityHeadersMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
await self.app(scope, receive, send)
return
async def send_with_headers(message):
if message["type"] == "http.response.start":
extra = [
(b"x-content-type-options", b"nosniff"),
(b"x-frame-options", b"DENY"),
(b"referrer-policy", b"strict-origin-when-cross-origin"),
]
message["headers"] = list(message["headers"]) + extra
await send(message)
await self.app(scope, receive, send_with_headers)
api.add_middleware(SecurityHeadersMiddleware)
This is the same pattern used internally by Starlette and uvicorn. The
middleware receives the ASGI ``scope``, ``receive``, and ``send`` callables,
and wraps ``send`` to inject headers into the response.
For most cases, ``BaseHTTPMiddleware`` is simpler and perfectly fine.
Use the pure ASGI approach when you need to handle WebSocket connections,
streaming responses, or want to avoid the overhead of request/response
object creation.
When to Use What
-----------------
- **Simple header additions, logging, auth checks** → use hooks
- **Response transformation, timing, third-party integrations** → use middleware
- **Rate limiting** → use the built-in ``RateLimiter`` (it uses hooks internally)
- **Request ID** → use ``api = responder.API(request_id=True)``
Start with hooks. They're simpler and cover most cases. Graduate to
middleware when hooks aren't enough.