Middleware
Catzilla provides a powerful, zero-allocation middleware system with both global and per-route middleware support. Build custom middleware chains with optimal performance and minimal memory overhead.
Overview
Catzilla’s middleware system provides:
Global Middleware - Applied to all routes with
@app.middleware()
RouterGroup Middleware - Applied to all routes within a RouterGroup
Per-Route Middleware - Specific middleware for individual routes
Zero-Allocation Design - C-accelerated middleware execution
Middleware Ordering - Control execution order with priority system
Request/Response Processing - Full access to request and response objects
Short-Circuiting - Stop middleware chain and return early responses
Quick Start
Basic Global Middleware
Create middleware that applies to all routes:
from catzilla import Catzilla, Request, Response, JSONResponse
from typing import Optional
import time
app = Catzilla()
@app.middleware(priority=10, pre_route=True, name="timing_middleware")
def timing_middleware(request: Request) -> Optional[Response]:
"""Measure request processing time"""
start_time = time.time()
# Store start time in request context for response processing
if not hasattr(request, 'context'):
request.context = {}
request.context['start_time'] = start_time
print(f"⏱️ Request started at {start_time}")
return None # Continue to next middleware
@app.get("/")
def home(request: Request) -> Response:
return JSONResponse({"message": "Hello with timing middleware!"})
if __name__ == "__main__":
print("🚀 Starting Catzilla middleware example...")
print("Try: curl http://localhost:8000/")
app.listen(port=8000)
Per-Route Middleware
Apply middleware to specific routes only:
from catzilla import Catzilla, Request, Response, JSONResponse
from typing import Optional
app = Catzilla()
def auth_middleware(request: Request) -> Optional[Response]:
"""Check authentication"""
auth_header = request.headers.get("Authorization") or request.headers.get("authorization")
if not auth_header or not auth_header.startswith("Bearer "):
return JSONResponse({
"error": "Missing or invalid authentication"
}, status_code=401)
# Authentication passed, add user info to context
if not hasattr(request, 'context'):
request.context = {}
request.context['user'] = {
"id": "user123",
"token": auth_header[7:] # Remove "Bearer "
}
return None # Continue to route handler
# Apply middleware only to protected routes
@app.get("/protected", middleware=[auth_middleware])
def protected_endpoint(request: Request) -> Response:
user = getattr(request, 'context', {}).get('user', {})
return JSONResponse({
"message": "You are authenticated!",
"user": user
})
@app.get("/public")
def public_endpoint(request: Request) -> Response:
return JSONResponse({"message": "No authentication required"})
if __name__ == "__main__":
print("🚀 Starting per-route middleware example...")
print("Try: curl http://localhost:8000/public")
print("Try: curl -H 'Authorization: Bearer token' http://localhost:8000/protected")
app.listen(port=8000)
RouterGroup Middleware
Apply middleware to all routes within a RouterGroup using group-level middleware:
from catzilla import Catzilla, Request, Response, JSONResponse
from catzilla.router import RouterGroup
from typing import Optional
import time
app = Catzilla()
def auth_middleware(request: Request) -> Optional[Response]:
"""Authentication middleware for protected routes"""
auth_header = request.headers.get("Authorization") or request.headers.get("authorization")
if not auth_header or not auth_header.startswith("Bearer "):
return JSONResponse({
"error": "Authentication required"
}, status_code=401)
# Add user info to request context
if not hasattr(request, 'context'):
request.context = {}
request.context['user'] = {
"id": "user123",
"token": auth_header[7:] # Remove "Bearer "
}
return None # Continue to route handler
def api_middleware(request: Request) -> Optional[Response]:
"""API-specific middleware"""
if not hasattr(request, 'context'):
request.context = {}
request.context['api'] = {
"version": "v1",
"timestamp": time.time()
}
return None
# Create RouterGroups with group-level middleware
protected_group = RouterGroup(prefix="/protected", middleware=[auth_middleware])
api_group = RouterGroup(prefix="/api", middleware=[api_middleware])
# All routes in protected_group will automatically run auth_middleware
@protected_group.get("/profile")
def protected_profile(request: Request) -> Response:
user = getattr(request, 'context', {}).get('user', {})
return JSONResponse({
"message": "Protected profile accessed",
"user": user
})
@protected_group.get("/settings")
def protected_settings(request: Request) -> Response:
user = getattr(request, 'context', {}).get('user', {})
return JSONResponse({
"message": "Protected settings accessed",
"user": user
})
# All routes in api_group will automatically run api_middleware
@api_group.get("/status")
def api_status(request: Request) -> Response:
api_context = getattr(request, 'context', {}).get('api', {})
return JSONResponse({
"message": "API status",
"api_context": api_context
})
# Combine group middleware with per-route middleware
@api_group.get("/data", middleware=[auth_middleware])
def api_data(request: Request) -> Response:
"""Group middleware + per-route middleware"""
api_context = getattr(request, 'context', {}).get('api', {})
user = getattr(request, 'context', {}).get('user', {})
return JSONResponse({
"message": "API data with combined middleware",
"api_context": api_context,
"user": user,
"middleware_chain": [
"1. Global middleware",
"2. Group: API middleware",
"3. Per-route: Auth middleware"
]
})
# Register router groups with the app
app.include_routes(protected_group)
app.include_routes(api_group)
if __name__ == "__main__":
print("🚀 Starting RouterGroup middleware example...")
print("Try: curl -H 'Authorization: Bearer token' http://localhost:8000/protected/profile")
print("Try: curl http://localhost:8000/api/status")
print("Try: curl -H 'Authorization: Bearer token' http://localhost:8000/api/data")
app.listen(port=8000)
Multiple RouterGroup Middleware
Apply multiple middleware functions to a RouterGroup:
def rate_limit_middleware(request: Request) -> Optional[Response]:
"""Rate limiting middleware"""
client_ip = request.headers.get("x-forwarded-for", "127.0.0.1")
if not hasattr(request, 'context'):
request.context = {}
request.context['rate_limit'] = {
'ip': client_ip,
'remaining': 100
}
return None
def admin_middleware(request: Request) -> Optional[Response]:
"""Admin access middleware"""
user = getattr(request, 'context', {}).get('user')
if not user:
return JSONResponse({
"error": "Authentication required"
}, status_code=401)
# Check admin privileges
if user.get('token') != 'admin-token':
return JSONResponse({
"error": "Admin access required"
}, status_code=403)
return None
# RouterGroup with multiple middleware (executes in order)
admin_group = RouterGroup(
prefix="/admin",
middleware=[auth_middleware, rate_limit_middleware, admin_middleware]
)
@admin_group.get("/dashboard")
def admin_dashboard(request: Request) -> Response:
"""Admin dashboard with triple middleware protection"""
user = getattr(request, 'context', {}).get('user', {})
rate_limit = getattr(request, 'context', {}).get('rate_limit', {})
return JSONResponse({
"message": "Admin dashboard accessed",
"user": user,
"rate_limit": rate_limit,
"middleware_chain": [
"1. Global middleware",
"2. Group: Auth middleware",
"3. Group: Rate limit middleware",
"4. Group: Admin middleware"
]
})
app.include_routes(admin_group)
if __name__ == "__main__":
app.listen(port=8000)
Basic Middleware Patterns
Request Logging
Log all incoming requests:
from catzilla import Catzilla, Request, Response, JSONResponse
from typing import Optional
app = Catzilla()
@app.middleware(priority=10, pre_route=True, name="request_logger")
def request_logging_middleware(request: Request) -> Optional[Response]:
"""Log all requests"""
print(f"📥 {request.method} {request.path}")
print(f" Headers: {dict(request.headers)}")
# Add request info to context for response logging
if not hasattr(request, 'context'):
request.context = {}
request.context['logged'] = True
return None # Continue to next middleware
@app.middleware(priority=10, pre_route=False, post_route=True, name="response_logger")
def response_logger_middleware(request: Request) -> Optional[Response]:
"""Log responses"""
if getattr(request, 'context', {}).get('logged'):
print(f"📤 Response processed for {request.method} {request.path}")
return None
@app.get("/")
def home(request: Request) -> Response:
return JSONResponse({"message": "Hello with request logging!"})
if __name__ == "__main__":
print("🚀 Starting request logging example...")
print("Try: curl http://localhost:8000/")
app.listen(port=8000)
CORS Middleware
Handle Cross-Origin Resource Sharing:
from catzilla import Catzilla, Request, Response, JSONResponse
from typing import Optional
app = Catzilla()
@app.middleware(priority=50, pre_route=True, name="cors_handler")
def cors_middleware(request: Request) -> Optional[Response]:
"""Add CORS headers"""
print("🌍 CORS Middleware: Processing request")
# Handle preflight requests
if request.method == "OPTIONS":
return Response("", headers={
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
})
# Add CORS info to context for response processing
if not hasattr(request, 'context'):
request.context = {}
request.context['cors_enabled'] = True
return None # Continue to next middleware
@app.get("/")
def home(request: Request) -> Response:
return JSONResponse({"message": "CORS-enabled endpoint"})
if __name__ == "__main__":
print("🚀 Starting CORS middleware example...")
print("Try: curl -X OPTIONS http://localhost:8000/")
print("Try: curl http://localhost:8000/")
app.listen(port=8000)
Error Handling Middleware
Catch and handle errors gracefully:
from catzilla import Catzilla, Request, Response, JSONResponse
from typing import Optional
app = Catzilla()
@app.middleware(priority=100, pre_route=True, name="error_handler")
def error_handling_middleware(request: Request) -> Optional[Response]:
"""Global error handling preparation"""
# Add error handling context
if not hasattr(request, 'context'):
request.context = {}
request.context['error_handling_enabled'] = True
return None
@app.get("/error")
def error_endpoint(request):
"""Endpoint that triggers an error"""
raise ValueError("This is a test error")
@app.get("/")
def home(request):
return JSONResponse({"message": "Error handling middleware enabled"})
if __name__ == "__main__":
print("🚀 Starting error handling example...")
print("Try: curl http://localhost:8000/")
print("Try: curl http://localhost:8000/error")
app.listen(port=8000)
Advanced Middleware
Middleware with Priority
Control middleware execution order:
from catzilla import Catzilla, Request, Response, JSONResponse
from typing import Optional
app = Catzilla()
@app.middleware(priority=10, pre_route=True, name="security_headers") # Executes first
def security_middleware(request: Request) -> Optional[Response]:
"""Security headers - highest priority"""
print("🔒 Security Middleware: Adding security context")
if not hasattr(request, 'context'):
request.context = {}
request.context['security'] = {
'x_frame_options': 'DENY',
'x_content_type_options': 'nosniff'
}
return None
@app.middleware(priority=50, pre_route=True, name="logging") # Executes second
def logging_middleware(request: Request) -> Optional[Response]:
"""Request logging"""
print(f"📝 Logging Middleware: Processing {request.method} {request.path}")
return None
@app.middleware(priority=100, pre_route=True, name="analytics") # Executes last
def analytics_middleware(request: Request) -> Optional[Response]:
"""Analytics tracking"""
print("📊 Analytics Middleware: Tracking request")
return None
@app.get("/")
def home(request):
return JSONResponse({
"message": "Priority-ordered middleware example",
"security": getattr(request, 'context', {}).get('security', {})
})
if __name__ == "__main__":
print("🚀 Starting priority middleware example...")
print("Execution order: security (10) → logging (50) → analytics (100)")
print("Try: curl http://localhost:8000/")
app.listen(port=8000)
Async Middleware
Middleware that works with async operations:
import asyncio
from catzilla import Catzilla, Request, Response, JSONResponse
from typing import Optional
app = Catzilla()
@app.middleware(priority=50, pre_route=True, name="async_processor")
def async_middleware(request: Request) -> Optional[Response]:
"""Sync middleware calling async operations"""
async def async_processing():
# Async preprocessing
await asyncio.sleep(0.001) # Simulate async operation
print("🔄 Async Middleware: Async processing completed")
# Run async function in sync middleware
asyncio.run(async_processing())
# Add async processing info to context
if not hasattr(request, 'context'):
request.context = {}
request.context['async_processed'] = True
return None # Continue to next middleware
# Works with both async and sync handlers
@app.get("/async-handler")
async def async_handler(request):
await asyncio.sleep(0.01)
return JSONResponse({
"message": "Async handler with async middleware",
"async_processed": getattr(request, 'context', {}).get('async_processed', False)
})
@app.get("/sync-handler")
def sync_handler(request):
return JSONResponse({
"message": "Sync handler with async middleware",
"async_processed": getattr(request, 'context', {}).get('async_processed', False)
})
if __name__ == "__main__":
print("🚀 Starting async middleware example...")
print("Try: curl http://localhost:8000/async-handler")
print("Try: curl http://localhost:8000/sync-handler")
app.listen(port=8000)
Conditional Middleware
Middleware that applies based on conditions:
from catzilla import Catzilla, Request, Response, JSONResponse
from typing import Optional
app = Catzilla()
def rate_limit_middleware(request: Request) -> Optional[Response]:
"""Rate limiting for API endpoints"""
print(f"⏱️ Rate Limit: Checking path {request.path}")
# Only apply rate limiting to API routes
if not request.path.startswith("/api/"):
print("⏭️ Rate Limit: Skipping non-API route")
return None
# Check rate limit (simplified example)
client_ip = request.headers.get("X-Real-IP", "127.0.0.1")
# In real implementation, check rate limit store (Redis, etc.)
# For demo, allow all requests
print(f"✅ Rate Limit: IP {client_ip} - OK")
# Add rate limit info to context
if not hasattr(request, 'context'):
request.context = {}
request.context['rate_limit'] = {
'ip': client_ip,
'remaining': 100
}
return None # Continue to handler
@app.get("/api/data", middleware=[rate_limit_middleware])
def api_data(request):
rate_limit = getattr(request, 'context', {}).get('rate_limit', {})
return JSONResponse({
"data": "API response with rate limiting",
"rate_limit": rate_limit
})
@app.get("/regular")
def regular_endpoint(request):
return JSONResponse({"data": "Regular response without rate limiting"})
if __name__ == "__main__":
print("🚀 Starting conditional middleware example...")
print("Try: curl http://localhost:8000/api/data")
print("Try: curl http://localhost:8000/regular")
app.listen(port=8000)
Middleware Composition
Combining Multiple Middleware
Chain multiple middleware for complex processing:
import uuid
from catzilla import Catzilla, Request, Response, JSONResponse
from typing import Optional
app = Catzilla()
def request_id_middleware(request: Request) -> Optional[Response]:
"""Add unique request ID"""
request_id = str(uuid.uuid4())
if not hasattr(request, 'context'):
request.context = {}
request.context['request_id'] = request_id
print(f"🆔 Request ID: {request_id}")
return None
def user_context_middleware(request: Request) -> Optional[Response]:
"""Extract user context from JWT"""
auth_header = request.headers.get("Authorization", "")
if not hasattr(request, 'context'):
request.context = {}
if auth_header.startswith("Bearer "):
# In real app, decode JWT
request.context['user'] = {
'id': 'user123',
'role': 'admin'
}
else:
request.context['user'] = {
'id': None,
'role': 'anonymous'
}
print(f"👤 User Context: {request.context['user']['role']}")
return None
def audit_middleware(request: Request) -> Optional[Response]:
"""Audit logging with user context"""
context = getattr(request, 'context', {})
request_id = context.get('request_id', 'unknown')
user = context.get('user', {})
# Log audit trail
print(f"📋 AUDIT: {request_id} - "
f"User: {user.get('id')} - "
f"{request.method} {request.path}")
return None
# Apply middleware chain to specific routes
middleware_chain = [request_id_middleware, user_context_middleware, audit_middleware]
@app.get("/admin/users", middleware=middleware_chain)
def admin_users(request):
context = getattr(request, 'context', {})
return JSONResponse({
"users": ["user1", "user2"],
"request_id": context.get('request_id'),
"user_role": context.get('user', {}).get('role')
})
if __name__ == "__main__":
print("🚀 Starting middleware composition example...")
print("Try: curl -H 'Authorization: Bearer token' http://localhost:8000/admin/users")
app.listen(port=8000)
RouterGroup Middleware Composition
Organize complex middleware chains using RouterGroups:
from catzilla import Catzilla, Request, Response, JSONResponse
from catzilla.router import RouterGroup
from typing import Optional
import time
import uuid
app = Catzilla()
# Define reusable middleware functions
def request_id_middleware(request: Request) -> Optional[Response]:
"""Add unique request ID"""
request_id = str(uuid.uuid4())
if not hasattr(request, 'context'):
request.context = {}
request.context['request_id'] = request_id
return None
def timing_middleware(request: Request) -> Optional[Response]:
"""Track request timing"""
if not hasattr(request, 'context'):
request.context = {}
request.context['start_time'] = time.time()
return None
def auth_middleware(request: Request) -> Optional[Response]:
"""Authentication middleware"""
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
return JSONResponse({"error": "Authentication required"}, status_code=401)
if not hasattr(request, 'context'):
request.context = {}
request.context['user'] = {"id": "user123", "token": auth_header[7:]}
return None
def audit_middleware(request: Request) -> Optional[Response]:
"""Audit logging with context"""
context = getattr(request, 'context', {})
request_id = context.get('request_id', 'unknown')
user_id = context.get('user', {}).get('id', 'anonymous')
print(f"AUDIT: {request_id} - User: {user_id} - {request.method} {request.path}")
return None
# Create API v1 group with common middleware
api_v1 = RouterGroup(
prefix="/api/v1",
middleware=[request_id_middleware, timing_middleware]
)
# Create protected API group with authentication
protected_api = RouterGroup(
prefix="/protected",
middleware=[request_id_middleware, timing_middleware, auth_middleware, audit_middleware]
)
# API v1 routes (with request ID and timing)
@api_v1.get("/status")
def api_status(request):
context = getattr(request, 'context', {})
return JSONResponse({
"status": "OK",
"request_id": context.get('request_id'),
"start_time": context.get('start_time')
})
# Protected routes (with full middleware chain)
@protected_api.get("/user-data")
def protected_user_data(request):
context = getattr(request, 'context', {})
return JSONResponse({
"message": "Protected user data",
"request_id": context.get('request_id'),
"user": context.get('user'),
"processing_time": time.time() - context.get('start_time', 0)
})
# Nested RouterGroups for complex organization
admin_api = RouterGroup(prefix="/admin")
# Admin users subgroup with additional middleware
admin_users = RouterGroup(
prefix="/users",
middleware=[auth_middleware, audit_middleware]
)
@admin_users.get("/")
def admin_list_users(request):
return JSONResponse({"users": ["user1", "user2"]})
# Include the users group in admin group, then in main app
admin_api.include_group(admin_users)
app.include_routes(api_v1)
app.include_routes(protected_api)
app.include_routes(admin_api)
if __name__ == "__main__":
app.listen(port=8000)
Custom Middleware Classes
Create reusable middleware classes:
from catzilla import Catzilla, Request, Response, JSONResponse
from typing import Optional
import time
app = Catzilla()
class SecurityMiddleware:
def __init__(self, enabled_headers=None):
self.enabled_headers = enabled_headers or [
"X-Frame-Options",
"X-Content-Type-Options",
"X-XSS-Protection"
]
def __call__(self, request: Request) -> Optional[Response]:
# Store headers to be added in response middleware
if not hasattr(request, 'context'):
request.context = {}
request.context['security_headers'] = {}
if "X-Frame-Options" in self.enabled_headers:
request.context['security_headers']["X-Frame-Options"] = "DENY"
if "X-Content-Type-Options" in self.enabled_headers:
request.context['security_headers']["X-Content-Type-Options"] = "nosniff"
if "X-XSS-Protection" in self.enabled_headers:
request.context['security_headers']["X-XSS-Protection"] = "1; mode=block"
return None
class MetricsMiddleware:
def __init__(self):
self.request_count = 0
self.total_time = 0.0
def __call__(self, request: Request) -> Optional[Response]:
# Store start time for response middleware to calculate duration
if not hasattr(request, 'context'):
request.context = {}
request.context['metrics_start'] = time.time()
self.request_count += 1
request.context['request_count'] = self.request_count
return None
# Response middleware to add headers (would typically be a separate decorator)
def add_security_headers(request: Request) -> Optional[Response]:
# This would be implemented as a post-route middleware
# For now, demonstrating the pattern
return None
# Create middleware class instances
security_middleware = SecurityMiddleware()
metrics_middleware = MetricsMiddleware()
# Register middleware instances with the app
# Note: Use function call syntax for class instances, not decorator syntax
app.middleware(priority=100, name="security")(security_middleware)
app.middleware(priority=110, name="metrics")(metrics_middleware)
# For comparison - this is how you'd register a function:
# @app.middleware(priority=120, name="function_middleware")
# def some_function_middleware(request: Request) -> Optional[Response]:
# return None
@app.get("/metrics-demo")
def metrics_demo(request: Request) -> Response:
# Access metrics data from context
context = getattr(request, 'context', {})
request_count = context.get('request_count', 0)
return JSONResponse({
"message": "Response with metrics tracking",
"request_count": request_count
})
if __name__ == "__main__":
app.listen(port=8000)
Production Patterns
Request/Response Validation
Validate requests and sanitize responses:
from catzilla import Catzilla, Request, Response, JSONResponse
from typing import Optional
app = Catzilla()
@app.middleware(priority=100, name="request_validation")
def request_validation_middleware(request: Request) -> Optional[Response]:
"""Validate request format"""
# Check content type for POST/PUT requests
if request.method in ["POST", "PUT"]:
content_type = request.headers.get("Content-Type", "")
if not content_type.startswith("application/json"):
return JSONResponse(
{"error": "Content-Type must be application/json"},
status_code=400
)
# Check request size
content_length = request.headers.get("Content-Length", "0")
try:
if int(content_length) > 1024 * 1024: # 1MB limit
return JSONResponse(
{"error": "Request too large"},
status_code=413
)
except ValueError:
pass
return None
@app.middleware(priority=200, name="security_headers")
def security_headers_middleware(request: Request) -> Optional[Response]:
"""Add security headers via context"""
if not hasattr(request, 'context'):
request.context = {}
# Store security headers to be added in post-processing
request.context['security_headers'] = {
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"X-XSS-Protection": "1; mode=block"
}
# Mark sensitive headers for removal
request.context['remove_headers'] = ["X-Powered-By", "Server"]
return None
@app.get("/api/upload")
def upload_endpoint(request: Request) -> Response:
"""Example endpoint that benefits from validation middleware"""
return JSONResponse({
"message": "Upload endpoint with validation",
"security_headers": getattr(request, 'context', {}).get('security_headers', {})
})
@app.post("/api/data")
def create_data(request: Request) -> Response:
"""Example POST endpoint"""
return JSONResponse({"message": "Data created successfully"})
if __name__ == "__main__":
print("🚀 Starting request/response validation example...")
print("Try: curl -H 'Content-Type: application/json' -d '{}' http://localhost:8000/api/data")
print("Try: curl -H 'Content-Type: text/plain' -d 'test' http://localhost:8000/api/data")
app.listen(port=8000)
Performance Monitoring
Monitor application performance:
from catzilla import Catzilla, Request, Response, JSONResponse
from typing import Optional
import time
import psutil
import os
app = Catzilla()
class PerformanceMonitor:
def __init__(self):
self.slow_requests = []
self.request_times = []
def get_memory_usage(self):
"""Get current memory usage in MB"""
process = psutil.Process(os.getpid())
return process.memory_info().rss / 1024 / 1024
def __call__(self, request: Request) -> Optional[Response]:
start_time = time.time()
start_memory = self.get_memory_usage()
if not hasattr(request, 'context'):
request.context = {}
# Store performance tracking data
request.context['perf_start_time'] = start_time
request.context['perf_start_memory'] = start_memory
request.context['perf_monitor'] = self
return None
# Response middleware to calculate and log performance metrics
def performance_response_middleware(request: Request) -> Optional[Response]:
"""Log performance metrics after request processing"""
context = getattr(request, 'context', {})
if 'perf_start_time' in context:
start_time = context['perf_start_time']
start_memory = context['perf_start_memory']
perf_monitor = context.get('perf_monitor')
if perf_monitor:
end_time = time.time()
end_memory = perf_monitor.get_memory_usage()
processing_time = end_time - start_time
memory_used = end_memory - start_memory
# Track performance metrics
perf_monitor.request_times.append(processing_time)
# Log slow requests
if processing_time > 1.0: # > 1 second
perf_monitor.slow_requests.append({
"path": request.path,
"method": request.method,
"time": processing_time,
"memory": memory_used
})
print(f"Request {request.method} {request.path} took {processing_time:.3f}s")
return None
# Apply performance monitoring
perf_monitor = PerformanceMonitor()
app.middleware(priority=50, name="perf_start")(perf_monitor)
@app.middleware(priority=900, name="perf_end", pre_route=False, post_route=True)
def performance_response_middleware(request: Request) -> Optional[Response]:
"""Log performance metrics after request processing"""
context = getattr(request, 'context', {})
if 'perf_start_time' in context:
start_time = context['perf_start_time']
start_memory = context['perf_start_memory']
perf_monitor = context.get('perf_monitor')
if perf_monitor:
end_time = time.time()
end_memory = perf_monitor.get_memory_usage()
processing_time = end_time - start_time
memory_used = end_memory - start_memory
# Track performance metrics
perf_monitor.request_times.append(processing_time)
# Log slow requests
if processing_time > 1.0: # > 1 second
perf_monitor.slow_requests.append({
"path": request.path,
"method": request.method,
"time": processing_time,
"memory": memory_used
})
print(f"Request {request.method} {request.path} took {processing_time:.3f}s")
return None
@app.get("/performance-stats")
def performance_stats(request: Request):
if perf_monitor.request_times:
avg_time = sum(perf_monitor.request_times) / len(perf_monitor.request_times)
else:
avg_time = 0.0
return JSONResponse({
"total_requests": len(perf_monitor.request_times),
"average_response_time": f"{avg_time:.4f}s",
"slow_requests_count": len(perf_monitor.slow_requests),
"slow_requests": perf_monitor.slow_requests[-5:] # Last 5
})
@app.get("/test-slow")
def slow_endpoint(request: Request) -> Response:
"""Test endpoint that's intentionally slow"""
import time
time.sleep(1.5) # Simulate slow processing
return JSONResponse({"message": "Slow endpoint completed"})
if __name__ == "__main__":
print("🚀 Starting performance monitoring example...")
print("Try: curl http://localhost:8000/performance-stats")
print("Try: curl http://localhost:8000/test-slow")
app.listen(port=8000)
Best Practices
RouterGroup Middleware Best Practices
Guidelines for effective RouterGroup middleware usage:
# ✅ Good: Logical grouping with shared middleware
auth_required_group = RouterGroup(
prefix="/protected",
middleware=[auth_middleware]
)
api_group = RouterGroup(
prefix="/api",
middleware=[rate_limit_middleware, api_versioning_middleware]
)
# ✅ Good: Combine group and per-route middleware strategically
@api_group.get("/upload", middleware=[file_validation_middleware])
def upload_file(request):
# Runs: rate_limit -> api_versioning -> file_validation -> handler
pass
# ✅ Good: Keep middleware functions pure and reusable
def cors_middleware(request: Request):
"""Reusable CORS middleware"""
if not hasattr(request, 'context'):
request.context = {}
request.context['cors_enabled'] = True
return None
# ❌ Avoid: Too many middleware in one group (performance impact)
heavy_group = RouterGroup(
prefix="/heavy",
middleware=[
auth_middleware, rate_limit_middleware, audit_middleware,
validation_middleware, logging_middleware, metrics_middleware,
security_middleware, caching_middleware # Too many!
]
)
# ✅ Better: Split into logical layers
base_group = RouterGroup(
prefix="/api",
middleware=[rate_limit_middleware, auth_middleware]
)
@base_group.get("/data", middleware=[validation_middleware])
def get_data(request):
# Clear middleware chain: rate_limit -> auth -> validation -> handler
pass
# ✅ Good: Document middleware execution order
"""
Middleware Execution Order for /protected/admin/users:
1. Global: request_logger_middleware (priority 10)
2. Global: cors_middleware (priority 50)
3. Global: security_middleware (priority 100)
4. Group: auth_middleware (from protected_group)
5. Group: admin_middleware (from protected_group)
6. Per-route: audit_middleware (from route decorator)
7. Route Handler: admin_users()
8. Response flows back through middleware in reverse order
"""
if __name__ == "__main__":
app.listen(port=8000)
Middleware Order
Understand middleware execution order:
Request Flow:
1. Global Middleware (by priority)
- Security Middleware (priority=100) ↓
- CORS Middleware (priority=50) ↓
- Auth Middleware (priority=30) ↓
- Logging Middleware (priority=10) ↓
2. RouterGroup Middleware (in order)
- Group Middleware 1 ↓
- Group Middleware 2 ↓
- Group Middleware N ↓
3. Per-Route Middleware (in order)
- Route Middleware 1 ↓
- Route Middleware 2 ↓
- Route Middleware N ↓
4. Route Handler ↓
5. Response Flow (reverse order)
- Route Middleware N ↑
- Route Middleware 2 ↑
- Route Middleware 1 ↑
- Group Middleware N ↑
- Group Middleware 2 ↑
- Group Middleware 1 ↑
- Logging Middleware ↑
- Auth Middleware ↑
- CORS Middleware ↑
- Security Middleware ↑
Example with RouterGroup:
protected_group = RouterGroup(
prefix="/protected",
middleware=[auth_middleware, rate_limit_middleware]
)
@protected_group.get("/data", middleware=[validation_middleware])
def get_data(request):
return JSONResponse({"data": "response"})
Execution order for GET /protected/data:
1. Global middlewares (by priority)
2. auth_middleware (from RouterGroup)
3. rate_limit_middleware (from RouterGroup)
4. validation_middleware (from route)
5. get_data() handler
6. Response flows back through all middleware in reverse
Error Handling
Best practices for middleware error handling:
from catzilla import Catzilla, Request, Response, JSONResponse
from typing import Optional
import time
app = Catzilla()
@app.middleware(priority=100, name="robust")
def robust_middleware(request: Request) -> Optional[Response]:
"""Middleware with proper error handling"""
try:
# Pre-processing
if not hasattr(request, 'context'):
request.context = {}
request.context['middleware_start'] = time.time()
# Store success marker for post-processing
request.context['middleware_success'] = True
return None
except Exception as e:
# Log the error
print(f"Middleware error: {e}")
# Return error response
return JSONResponse(
{"error": "Middleware processing failed"},
status_code=500
)
# Post-processing middleware to add timing headers
@app.middleware(priority=900, name="timing", pre_route=False, post_route=True)
def timing_response_middleware(request: Request) -> Optional[Response]:
"""Add timing headers after request processing"""
context = getattr(request, 'context', {})
if 'middleware_start' in context and context.get('middleware_success'):
processing_time = time.time() - context['middleware_start']
# In a real implementation, this would modify the response headers
print(f"Processing time: {processing_time:.4f}s")
return None
@app.get("/")
def home(request: Request) -> Response:
"""Test route with error handling middleware"""
return JSONResponse({
"message": "Error handling middleware enabled",
"middleware_success": getattr(request, 'context', {}).get('middleware_success', False)
})
@app.get("/error-test")
def error_test(request: Request) -> Response:
"""Route that might cause middleware errors"""
# Simulate potential error condition
import random
if random.random() < 0.1: # 10% chance of error
raise Exception("Simulated error")
return JSONResponse({"message": "No error occurred"})
if __name__ == "__main__":
print("🚀 Starting error handling middleware example...")
print("Try: curl http://localhost:8000/")
print("Try: curl http://localhost:8000/error-test")
app.listen(port=8000)
Performance Tips
Optimize middleware for production:
from catzilla import Catzilla, Request, Response, JSONResponse
from typing import Optional
app = Catzilla()
# ✅ Good: Minimal processing in middleware
@app.middleware(priority=100, name="fast")
def fast_middleware(request: Request) -> Optional[Response]:
# Quick check
if request.method == "OPTIONS":
return Response("", status_code=200)
return None
# ❌ Avoid: Heavy processing in middleware
def slow_middleware(request: Request) -> Optional[Response]:
# Heavy processing in middleware slows down ALL requests
# This should be avoided for global middleware
print("Performing heavy computation for all requests (BAD)")
return None
# ✅ Good: Use per-route middleware for expensive operations
def expensive_middleware(request: Request) -> Optional[Response]:
# Only applied to specific routes that need it
print("Performing expensive operation for specific route")
if not hasattr(request, 'context'):
request.context = {}
request.context['expensive_operation_done'] = True
return None
@app.get("/expensive-route", middleware=[expensive_middleware])
def expensive_route(request: Request) -> Response:
context = getattr(request, 'context', {})
operation_done = context.get('expensive_operation_done', False)
return JSONResponse({
"message": "Expensive operation complete",
"operation_performed": operation_done
})
@app.get("/fast")
def fast_route(request: Request) -> Response:
"""Fast route that benefits from minimal middleware"""
return JSONResponse({
"message": "Fast route with minimal middleware overhead"
})
if __name__ == "__main__":
print("🚀 Starting performance tips example...")
print("Try: curl http://localhost:8000/fast")
print("Try: curl http://localhost:8000/expensive-route")
app.listen(port=8000)
This middleware system provides the flexibility and performance you need to build robust, production-ready applications with Catzilla.