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.
