Django middleware is a framework of hooks into Django's request/response processing. It's a light, low-level "plugin" system for globally altering Django's input or output. This chapter provides a comprehensive understanding of how middleware works and its role in Django applications.
# Conceptual flow of a Django request
"""
1. Web Server receives HTTP request
2. Django creates HttpRequest object
3. Request flows through middleware (top to bottom)
4. URL resolver determines view function
5. View function processes request
6. View returns HttpResponse object
7. Response flows through middleware (bottom to top)
8. Web Server sends HTTP response to client
"""
# Visual representation:
"""
HTTP Request
↓
┌─────────────────┐
│ Middleware 1 │ ← process_request()
└─────────────────┘
↓
┌─────────────────┐
│ Middleware 2 │ ← process_request()
└─────────────────┘
↓
┌─────────────────┐
│ URL Resolver │
└─────────────────┘
↓
┌─────────────────┐
│ View Function │
└─────────────────┘
↓
┌─────────────────┐
│ Middleware 2 │ ← process_response()
└─────────────────┘
↓
┌─────────────────┐
│ Middleware 1 │ ← process_response()
└─────────────────┘
↓
HTTP Response
"""
Middleware is configured in Django settings:
# 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.CustomMiddleware',
]
class SimpleMiddleware:
"""Modern middleware using __call__ method"""
def __init__(self, get_response):
"""
One-time configuration and initialization.
get_response is the next middleware or view in the chain.
"""
self.get_response = get_response
# One-time initialization code here
def __call__(self, request):
"""
Called for each request before the view (and later middleware)
are called.
"""
# Code to be executed for each request before
# the view (and later middleware) are called.
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
class LegacyStyleMiddleware:
"""Legacy middleware with separate methods"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Process request
response = self.process_request(request)
if response:
return response
# Get response from next middleware/view
response = self.get_response(request)
# Process response
return self.process_response(request, response)
def process_request(self, request):
"""
Called on each request, before Django decides which view to execute.
Should return None or HttpResponse object.
"""
return None
def process_view(self, request, view_func, view_args, view_kwargs):
"""
Called just before Django calls the view.
Should return None or HttpResponse object.
"""
return None
def process_exception(self, request, exception):
"""
Called when a view raises an exception.
Should return None or HttpResponse object.
"""
return None
def process_template_response(self, request, response):
"""
Called just after the view has finished executing.
Only called if response has render() method.
"""
return response
def process_response(self, request, response):
"""
Called on all responses before they're returned to the browser.
Must return HttpResponse object.
"""
return response
# Example middleware stack
MIDDLEWARE = [
'middleware.SecurityMiddleware', # 1st to process request
'middleware.SessionMiddleware', # 2nd to process request
'middleware.AuthMiddleware', # 3rd to process request
'middleware.CustomMiddleware', # 4th to process request
]
# Request flow:
# Request → Security → Session → Auth → Custom → View
# Response ← Security ← Session ← Auth ← Custom ← View
class RequestResponseLogger:
"""Middleware to log request/response flow"""
def __init__(self, get_response):
self.get_response = get_response
print("RequestResponseLogger: Initialized")
def __call__(self, request):
print(f"RequestResponseLogger: Processing request to {request.path}")
# Call the next middleware or view
response = self.get_response(request)
print(f"RequestResponseLogger: Processing response with status {response.status_code}")
return response
class TimingMiddleware:
"""Middleware to measure request processing time"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
import time
start_time = time.time()
print(f"TimingMiddleware: Request started at {start_time}")
response = self.get_response(request)
end_time = time.time()
processing_time = end_time - start_time
print(f"TimingMiddleware: Request completed in {processing_time:.4f} seconds")
# Add timing header to response
response['X-Processing-Time'] = f"{processing_time:.4f}"
return response
# With both middleware enabled:
MIDDLEWARE = [
'myapp.middleware.RequestResponseLogger',
'myapp.middleware.TimingMiddleware',
]
# Output for a request:
"""
RequestResponseLogger: Processing request to /example/
TimingMiddleware: Request started at 1634567890.1234
TimingMiddleware: Request completed in 0.0156 seconds
RequestResponseLogger: Processing response with status 200
"""
class RequestProcessingMiddleware:
"""Demonstrate request processing"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# This runs before the view
self.process_request_data(request)
response = self.get_response(request)
return response
def process_request_data(self, request):
"""Process incoming request data"""
# Add custom attributes to request
request.custom_data = {
'timestamp': timezone.now(),
'user_agent': request.META.get('HTTP_USER_AGENT', ''),
'ip_address': self.get_client_ip(request)
}
# Log request information
print(f"Request from {request.custom_data['ip_address']} at {request.custom_data['timestamp']}")
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
class ViewProcessingMiddleware:
"""Middleware with process_view method"""
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 is executed"""
# Log view information
print(f"About to execute view: {view_func.__name__}")
print(f"View args: {view_args}")
print(f"View kwargs: {view_kwargs}")
# Add view information to request
request.view_info = {
'view_name': view_func.__name__,
'view_module': view_func.__module__,
'args': view_args,
'kwargs': view_kwargs
}
# Could return HttpResponse to short-circuit view execution
# return HttpResponse("View execution prevented")
# Return None to continue normal processing
return None
class ExceptionHandlingMiddleware:
"""Middleware for handling exceptions"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
def process_exception(self, request, exception):
"""Handle exceptions raised by views"""
# Log the exception
import logging
logger = logging.getLogger(__name__)
logger.error(
f"Exception in view: {exception.__class__.__name__}: {exception}",
extra={
'request_path': request.path,
'request_method': request.method,
'user': getattr(request, 'user', None),
'exception_type': exception.__class__.__name__
},
exc_info=True
)
# Could return custom error response
if isinstance(exception, ValueError):
from django.http import HttpResponseBadRequest
return HttpResponseBadRequest("Invalid request data")
# Return None to use default exception handling
return None
class TemplateResponseMiddleware:
"""Middleware for processing template responses"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
def process_template_response(self, request, response):
"""Process responses that have a render() method"""
# Only called for TemplateResponse objects
if hasattr(response, 'context_data'):
# Add global context variables
response.context_data['global_timestamp'] = timezone.now()
response.context_data['request_id'] = getattr(request, 'id', None)
# Modify template name based on conditions
if request.user.is_authenticated:
response.template_name = response.template_name.replace(
'.html', '_authenticated.html'
)
return response
class ConditionalMiddleware:
"""Middleware that runs conditionally"""
def __init__(self, get_response):
self.get_response = get_response
self.enabled_paths = ['/api/', '/admin/']
def __call__(self, request):
# Only process certain paths
should_process = any(
request.path.startswith(path)
for path in self.enabled_paths
)
if should_process:
# Add processing logic here
request.processed_by_conditional = True
response = self.get_response(request)
if should_process:
# Add response processing here
response['X-Processed-By'] = 'ConditionalMiddleware'
return response
class ConfigurableMiddleware:
"""Middleware with configuration options"""
def __init__(self, get_response):
self.get_response = get_response
# Load configuration from settings
from django.conf import settings
self.config = getattr(settings, 'CONFIGURABLE_MIDDLEWARE', {})
self.enabled = self.config.get('enabled', True)
self.debug_mode = self.config.get('debug', False)
self.excluded_paths = self.config.get('excluded_paths', [])
def __call__(self, request):
if not self.enabled:
return self.get_response(request)
# Skip excluded paths
if any(request.path.startswith(path) for path in self.excluded_paths):
return self.get_response(request)
if self.debug_mode:
print(f"ConfigurableMiddleware processing: {request.path}")
response = self.get_response(request)
return response
# settings.py
CONFIGURABLE_MIDDLEWARE = {
'enabled': True,
'debug': False,
'excluded_paths': ['/static/', '/media/']
}
class DataSharingMiddleware:
"""Middleware that shares data with other middleware"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Set data that other middleware can use
request.middleware_data = {
'processing_start': timezone.now(),
'middleware_chain': []
}
response = self.get_response(request)
# Log the complete middleware chain
if hasattr(request, 'middleware_data'):
chain = request.middleware_data.get('middleware_chain', [])
print(f"Middleware chain: {' → '.join(chain)}")
return response
class ChainTrackingMiddleware:
"""Middleware that tracks the processing chain"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Add self to the chain
if hasattr(request, 'middleware_data'):
request.middleware_data['middleware_chain'].append(
self.__class__.__name__
)
response = self.get_response(request)
return response
from django.test import TestCase, RequestFactory
from django.http import HttpResponse
from myapp.middleware import CustomMiddleware
class MiddlewareTestCase(TestCase):
"""Test cases for custom middleware"""
def setUp(self):
self.factory = RequestFactory()
# Create a simple get_response callable
def get_response(request):
return HttpResponse("Test response")
self.middleware = CustomMiddleware(get_response)
def test_middleware_processes_request(self):
"""Test that middleware processes requests correctly"""
request = self.factory.get('/test/')
response = self.middleware(request)
self.assertEqual(response.status_code, 200)
self.assertTrue(hasattr(request, 'processed_by_middleware'))
def test_middleware_adds_headers(self):
"""Test that middleware adds expected headers"""
request = self.factory.get('/test/')
response = self.middleware(request)
self.assertIn('X-Custom-Header', response)
def test_middleware_handles_exceptions(self):
"""Test middleware exception handling"""
def failing_get_response(request):
raise ValueError("Test exception")
middleware = CustomMiddleware(failing_get_response)
request = self.factory.get('/test/')
# Should handle exception gracefully
response = middleware(request)
self.assertEqual(response.status_code, 500)
class EfficientMiddleware:
"""Example of performance-conscious middleware"""
def __init__(self, get_response):
self.get_response = get_response
# Pre-compile expensive operations
import re
self.path_pattern = re.compile(r'^/api/v\d+/')
# Cache expensive lookups
self._config_cache = {}
def __call__(self, request):
# Fast path checks first
if not self.should_process(request):
return self.get_response(request)
# Minimize work in the request path
start_time = time.time()
response = self.get_response(request)
# Add minimal response processing
processing_time = time.time() - start_time
if processing_time > 0.1: # Only log slow requests
logger.warning(f"Slow request: {request.path} took {processing_time:.3f}s")
return response
def should_process(self, request):
"""Fast check to determine if processing is needed"""
return self.path_pattern.match(request.path) is not None
Now that you understand how middleware works, let's explore Django's built-in middleware components and learn how they implement common functionality.
Middleware
Django middleware is a powerful framework for processing requests and responses globally across your application. It provides hooks into Django's request/response processing, allowing you to modify requests before they reach views and responses before they're sent to clients.
Built-in Middleware
Django comes with several built-in middleware components that handle common web application concerns. Understanding these middleware components helps you leverage Django's capabilities and serves as examples for creating your own middleware.