Middleware

Keywords: request lifecycle, middleware hooks, adding middleware, custom middleware, lifecycle.py, post-request, pre-request

Middleware lets you run code before and after every request. Create a file in src/middlewares/ — it’s loaded automatically.

Create a middleware

# src/middlewares/logger.py

def handle(request, next):
    print(f"→ {request.method} {request.path}")
    response = next(request)
    print(f"← {request.status}")
    return response

That’s it. The file must export a handle(request, next) function.

  • request — the Request object

  • next — call this to continue to the next middleware (or the page handler)

  • Return the response from next(request)

Asynchronous Middleware

Middleware can also be declared using async def for non-blocking operations (e.g., calling external APIs or querying the database with async ORM methods). Asok automatically resolves mixed sync/async chains under both ASGI and WSGI servers. For details, see Async & Concurrency.

Execution order

Middleware files are loaded in alphabetical order. To control the order, prefix filenames with numbers:

src/middlewares/
├── 01_logger.py      # Runs first (outermost)
├── 02_ratelimit.py   # Runs second
└── 03_auth.py        # Runs third (closest to the page)

Built-in middleware

Request Logger

# src/middlewares/logger.py
from asok.logger import RequestLogger

log = RequestLogger()

def handle(request, next):
    return log(request, next)

Logs: GET /about 200 OK 3.2ms

Rate Limiting

# src/middlewares/ratelimit.py
from asok.ratelimit import RateLimit

limiter = RateLimit(max_requests=60, window=60)  # 60 requests per minute

def handle(request, next):
    return limiter(request, next)

Returns 429 Too Many Requests when the limit is exceeded. Rate limiting is per IP address.

Custom key function:

limiter = RateLimit(
    max_requests=100,
    window=3600,
    key_func=lambda req: req.user.id if req.is_authenticated else req.environ.get('REMOTE_ADDR')
)

Examples

Auth guard for /admin/*

# src/middlewares/admin.py

def handle(request, next):
    if request.path.startswith('/admin'):
        if not request.is_authenticated:
            request.redirect('/login')
        if not request.user.is_admin:
            request.redirect('/')
    return next(request)

Maintenance mode

# src/middlewares/maintenance.py
import os

def handle(request, next):
    if os.environ.get('MAINTENANCE') == 'true':
        request.status_code(503)
        return '<h1>We are under maintenance. Check back soon.</h1>'
    return next(request)

Response timing header

# src/middlewares/timing.py
import time

def handle(request, next):
    start = time.time()
    response = next(request)
    elapsed = (time.time() - start) * 1000
    # You could log this or add a header
    return response

Built-in Enterprise Middlewares

Asok provides high-performance, zero-dependency enterprise middlewares ready for production:

Multi-Tenancy SaaS Middleware (TenantMiddleware)

Resolves the active tenant for incoming requests and automatically switches database connections in the ORM:

from asok import Asok, TenantMiddleware

app = Asok()

# Automatic resolution from X-Tenant-ID header, subdomain, or query param
app.use(TenantMiddleware(extract_subdomain=True, base_domain="example.com"))
  • Supported extractors:

    • Header: X-Tenant-ID: acme or X-Tenant: acme

    • Subdomain: acme.example.com -> acme

    • Query param: ?tenant=acme

    • Custom callable: custom_extractor=lambda req: ...

  • Enforces allowed_tenants whitelisting and optional strict=True validation.

  • Automatically attaches request.tenant and wraps execution in tenant_context.

Distributed Tracing Middleware (TracingMiddleware)

Propagates W3C TraceContext standards (traceparent) and OpenTelemetry-compliant spans:

from asok import Asok, TracingMiddleware

app = Asok()
app.use(TracingMiddleware())
  • Automatically parses or initiates W3C traceparent headers (00-{trace_id}-{span_id}-{flags}).

  • Sets request.trace_id, request.span_id, and request.tracer.

  • Injects correlation headers in HTTP responses: traceparent, X-Trace-ID, and X-Span-ID.