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 objectnext— 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: acmeorX-Tenant: acmeSubdomain:
acme.example.com->acmeQuery param:
?tenant=acmeCustom callable:
custom_extractor=lambda req: ...
Enforces
allowed_tenantswhitelisting and optionalstrict=Truevalidation.Automatically attaches
request.tenantand wraps execution intenant_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
traceparentheaders (00-{trace_id}-{span_id}-{flags}).Sets
request.trace_id,request.span_id, andrequest.tracer.Injects correlation headers in HTTP responses:
traceparent,X-Trace-ID, andX-Span-ID.