Difficulty: Advanced
Middleware is a framework of hooks into Django's request/response processing. Each middleware component is a callable that processes every request before it reaches the view and every response before it returns to the client. Middleware forms a layered architecture - like an onion, requests pass through middleware layers inward to the view, and responses pass back outward.
Django's MIDDLEWARE setting is a list of middleware classes. The order matters significantly: during request processing, middleware is executed top to bottom. During response processing, it is executed bottom to top. This means the first middleware in the list is the outermost layer - it processes the request first and the response last.
Django ships with several essential middleware. SecurityMiddleware handles HTTPS redirects and security headers. SessionMiddleware enables session support. CommonMiddleware handles URL trailing slashes and static file serving. CsrfViewMiddleware provides CSRF protection. AuthenticationMiddleware attaches request.user. MessageMiddleware enables the messages framework.
Each middleware component can perform actions at several points in the request/response cycle. process_request (called before the view) can modify the request or return an early response. process_view (called just before the view function) receives the view function and its arguments. process_response (called after the view) can modify the response. process_exception (called when the view raises an exception) can handle errors.
Middleware ordering is critical. AuthenticationMiddleware must come after SessionMiddleware because it depends on session data. CsrfViewMiddleware should come before any middleware that modifies the response body. SecurityMiddleware should be first to apply security headers to all responses. Misarranging middleware can cause subtle bugs or security vulnerabilities.
Middleware is ideal for cross-cutting concerns - functionality that applies to many or all requests regardless of the specific view. Examples include logging, timing, authentication, CORS headers, rate limiting, request ID tracking, and content compression. If something needs to happen on every request, middleware is the right tool.
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', # Security headers
'django.contrib.sessions.middleware.SessionMiddleware', # Session support
'django.middleware.common.CommonMiddleware', # URL normalization
'django.middleware.csrf.CsrfViewMiddleware', # CSRF protection
'django.contrib.auth.middleware.AuthenticationMiddleware', # request.user
'django.contrib.messages.middleware.MessageMiddleware', # Messages
'django.middleware.clickjacking.XFrameOptionsMiddleware', # Clickjacking
]
# Processing order:
# Request: Security -> Session -> Common -> CSRF -> Auth -> Messages -> XFrame -> View
# Response: View -> XFrame -> Messages -> Auth -> CSRF -> Common -> Session -> Security
Middleware processes requests top-to-bottom and responses bottom-to-top. Security is first/last because it needs to add headers to every response and can redirect HTTP to HTTPS before anything else runs.
# How middleware wraps around the view:
#
# Request arrives
# |
# v
# SecurityMiddleware.process_request()
# |
# v
# SessionMiddleware.process_request()
# |
# v
# AuthenticationMiddleware.process_request()
# |
# v
# CsrfViewMiddleware.process_view()
# |
# v
# ==== VIEW FUNCTION ====
# |
# v
# CsrfViewMiddleware.process_response()
# |
# v
# AuthenticationMiddleware.process_response()
# |
# v
# SessionMiddleware.process_response()
# |
# v
# SecurityMiddleware.process_response()
# |
# v
# Response sent to client
The middleware stack forms an onion pattern. Each layer can short-circuit by returning a response early, skipping inner layers. This is how authentication middleware redirects unauthenticated users before the view runs.
import time
import logging
logger = logging.getLogger(__name__)
class RequestTimingMiddleware:
"""Log the time taken to process each request."""
def __init__(self, get_response):
self.get_response = get_response
# One-time setup code goes here
def __call__(self, request):
# Code before the view (and later middleware)
start_time = time.time()
# Get the response from the next middleware or view
response = self.get_response(request)
# Code after the view (and earlier middleware)
duration = time.time() - start_time
logger.info(
f'{request.method} {request.path} - {response.status_code} '
f'({duration:.3f}s)'
)
return response
New-style middleware uses __init__ and __call__. __init__ receives get_response (the next middleware or view). __call__ runs on every request - code before get_response runs before the view, code after runs after the view.
from django.http import HttpResponseForbidden
import logging
logger = logging.getLogger(__name__)
class AdvancedMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
def process_view(self, request, view_func, view_args, view_kwargs):
"""Called just before the view."""
logger.info(f'Calling view: {view_func.__name__}')
# Return None to continue, or HttpResponse to short-circuit
return None
def process_exception(self, request, exception):
"""Called when the view raises an exception."""
logger.error(f'Exception in {request.path}: {exception}')
# Return None to use default exception handling
# Return HttpResponse to handle the exception
return None
def process_template_response(self, request, response):
"""Called for TemplateResponse objects."""
response.context_data['middleware_message'] = 'Added by middleware'
return response
Middleware can define hook methods for specific points in the cycle. process_view runs before the view with access to the view function. process_exception handles view exceptions. process_template_response modifies template context.
Middleware, Request Processing, Response Processing, MIDDLEWARE Setting, Order