Chapter 12: Middleware and Signals
12.1 Middleware System
Middleware Concept
Middleware is Django's hook framework for request/response processing. It is a lightweight, low-level "plugin" system for globally changing Django's input or output.
Middleware workflow:
- When request reaches Django application, it passes through each middleware's
process_requestmethod in order - Then through
process_viewmethod - After view function processing completes, response passes through each middleware's
process_responsemethod in order
Built-in Middleware
Django provides many built-in middleware:
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]Common built-in middleware explanation:
SecurityMiddleware: Provides security-related HTTP headersSessionMiddleware: Enables session supportCommonMiddleware: Handles some common functionsCsrfViewMiddleware: Provides CSRF protectionAuthenticationMiddleware: Associates users with requestsMessageMiddleware: Enables cookie and session-based message supportXFrameOptionsMiddleware: Prevents clickjacking
Custom Middleware
Create custom middleware:
# middleware.py
import time
from django.utils.deprecation import MiddlewareMixin
from django.http import HttpResponse
class TimingMiddleware(MiddlewareMixin):
"""Middleware to record request processing time"""
def process_request(self, request):
"""Called before Django determines which view to use"""
request.start_time = time.time()
print(f"Request started: {request.path}")
return None # Continue processing request
def process_view(self, request, view_func, view_args, view_kwargs):
"""Called before Django calls view"""
print(f"About to call view: {view_func.__name__}")
return None # Continue processing view
def process_response(self, request, response):
"""Called before all responses return to browser"""
if hasattr(request, 'start_time'):
duration = time.time() - request.start_time
print(f"Request completed: {request.path}, time taken: {duration:.2f} seconds")
return response
class MaintenanceMiddleware(MiddlewareMixin):
"""Maintenance mode middleware"""
def process_request(self, request):
# Check if in maintenance mode
from django.conf import settings
if getattr(settings, 'MAINTENANCE_MODE', False):
# Check if admin
if not request.user.is_staff:
return HttpResponse(
'<h1>Website under maintenance</h1><p>Please visit again later</p>',
status=503
)
return None
class LoggingMiddleware(MiddlewareMixin):
"""Request logging middleware"""
def process_request(self, request):
# Record request information
import logging
logger = logging.getLogger('request_logger')
logger.info(
f"IP: {self.get_client_ip(request)}, "
f"Method: {request.method}, "
f"Path: {request.path}, "
f"User: {getattr(request.user, 'username', 'Anonymous')}"
)
return None
def get_client_ip(self, request):
"""Get client IP address"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
# More complex middleware example
class ContentSecurityPolicyMiddleware(MiddlewareMixin):
"""Content Security Policy middleware"""
def process_response(self, request, response):
# Add CSP header
csp_policy = (
"default-src 'self'; "
"script-src 'self' 'unsafe-inline' https://cdn.example.com; "
"style-src 'self' 'unsafe-inline' https://cdn.example.com; "
"img-src 'self' data: https:; "
"font-src 'self' https://fonts.googleapis.com https://fonts.gstatic.com;"
)
response['Content-Security-Policy'] = csp_policy
return responseRegister middleware in settings.py:
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# Custom middleware
'myapp.middleware.TimingMiddleware',
'myapp.middleware.LoggingMiddleware',
'myapp.middleware.MaintenanceMiddleware',
]Request/Response Processing
Middleware can handle different stages of request and response:
# advanced_middleware.py
from django.utils.deprecation import MiddlewareMixin
from django.http import JsonResponse
from django.core.exceptions import PermissionDenied
class AdvancedMiddleware(MiddlewareMixin):
"""Advanced middleware example"""
def process_request(self, request):
"""
Called on each request, before Django determines which view to use
Return None to continue, return HttpResponse object to short-circuit
"""
# Check request rate limit
if self.is_rate_limited(request):
return JsonResponse({'error': 'Too many requests'}, status=429)
# Add custom attributes to request object
request.custom_data = {
'processed_time': time.time(),
'middleware_version': '1.0'
}
return None
def process_view(self, request, view_func, view_args, view_kwargs):
"""
Called before Django calls view
Can be used to modify parameters passed to view
"""
# Record view call
print(f"Calling view function: {view_func.__name__}")
# Check if view requires special permissions
if getattr(view_func, 'require_api_key', False):
api_key = request.META.get('HTTP_X_API_KEY')
if not api_key or not self.validate_api_key(api_key):
raise PermissionDenied("Invalid API key")
return None
def process_exception(self, request, exception):
"""
Called when view throws exception
Can be used to handle specific exception types
"""
if isinstance(exception, PermissionDenied):
return JsonResponse({'error': 'Insufficient permissions'}, status=403)
# Log exception
import logging
logger = logging.getLogger('exception_logger')
logger.error(f"View exception: {exception}", exc_info=True)
return None
def process_response(self, request, response):
"""
Called before all responses return to browser
Can be used to modify response
"""
# Add custom response headers
response['X-Processed-By'] = 'Django-Advanced-Middleware'
# Record response time
if hasattr(request, 'custom_data'):
process_time = time.time() - request.custom_data['processed_time']
response['X-Process-Time'] = f"{process_time:.3f}s"
return response
def is_rate_limited(self, request):
"""Check request rate limit"""
# Simple implementation, can use Redis etc. in real applications
client_ip = self.get_client_ip(request)
# Should implement actual rate limiting logic here
return False
def validate_api_key(self, api_key):
"""Validate API key"""
# In real applications should query database or config file
valid_keys = ['valid-key-1', 'valid-key-2']
return api_key in valid_keys
def get_client_ip(self, request):
"""Get client IP"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
return x_forwarded_for.split(',')[0]
return request.META.get('REMOTE_ADDR')Add middleware functionality to views using decorators:
# decorators.py
from functools import wraps
def require_api_key(view_func):
"""Add API key requirement to view"""
@wraps(view_func)
def wrapper(*args, **kwargs):
return view_func(*args, **kwargs)
wrapper.require_api_key = True
return wrapper
# views.py
from .decorators import require_api_key
@require_api_key
def api_view(request):
return JsonResponse({'message': 'API access successful'})Middleware is a very powerful feature in Django framework, can be used to implement global request processing, security control, performance monitoring, etc.
Summary
Django middleware system provides powerful request/response processing capabilities:
- ✅ Understand middleware position and function in request/response processing chain
- ✅ Master built-in middleware functions and configuration methods
- ✅ Able to create custom middleware for specific business needs
- ✅ Understand middleware processing stages execution order
- ✅ Add middleware functionality to views using decorators
Proper use of middleware can build more flexible and secure web applications.
Next Article
We will learn Django signal system.