The order of middleware in Django's MIDDLEWARE setting is crucial for proper application behavior. This chapter explains how middleware ordering affects request/response processing and provides guidelines for optimal middleware arrangement.
# Example MIDDLEWARE configuration
MIDDLEWARE = [
'middleware.SecurityMiddleware', # 1st - Processes request first
'middleware.SessionMiddleware', # 2nd
'middleware.AuthMiddleware', # 3rd
'middleware.CustomMiddleware', # 4th - Processes request last
]
# Request Flow (Top to Bottom):
"""
HTTP Request
↓
SecurityMiddleware.process_request()
↓
SessionMiddleware.process_request()
↓
AuthMiddleware.process_request()
↓
CustomMiddleware.process_request()
↓
View Function
↓
CustomMiddleware.process_response()
↓
AuthMiddleware.process_response()
↓
SessionMiddleware.process_response()
↓
SecurityMiddleware.process_response()
↓
HTTP Response
"""
class OrderDemonstrationMiddleware:
"""Middleware to demonstrate processing order"""
def __init__(self, get_response):
self.get_response = get_response
self.name = self.__class__.__name__
print(f"{self.name}: Initialized")
def __call__(self, request):
print(f"{self.name}: Processing request to {request.path}")
response = self.get_response(request)
print(f"{self.name}: Processing response with status {response.status_code}")
return response
class FirstMiddleware(OrderDemonstrationMiddleware):
pass
class SecondMiddleware(OrderDemonstrationMiddleware):
pass
class ThirdMiddleware(OrderDemonstrationMiddleware):
pass
# Configuration:
MIDDLEWARE = [
'myapp.middleware.FirstMiddleware',
'myapp.middleware.SecondMiddleware',
'myapp.middleware.ThirdMiddleware',
]
# Output for a request to /example/:
"""
FirstMiddleware: Initialized
SecondMiddleware: Initialized
ThirdMiddleware: Initialized
FirstMiddleware: Processing request to /example/
SecondMiddleware: Processing request to /example/
ThirdMiddleware: Processing request to /example/
ThirdMiddleware: Processing response with status 200
SecondMiddleware: Processing response with status 200
FirstMiddleware: Processing response with status 200
"""
# CORRECT: Security middleware should be first
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', # HTTPS redirects, security headers
'django.contrib.sessions.middleware.SessionMiddleware',
# ... other middleware
]
# INCORRECT: Security middleware later in chain
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.security.SecurityMiddleware', # Too late for HTTPS redirect
# ... other middleware
]
# Why this matters:
class SecurityOrderExample:
"""Demonstrates why security middleware must be first"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# If HTTPS redirect happens here, session cookies might already be set over HTTP
if not request.is_secure():
# Session middleware already processed - cookies sent over HTTP!
return HttpResponsePermanentRedirect(
'https://' + request.get_host() + request.get_full_path()
)
response = self.get_response(request)
return response
# CORRECT: Sessions before authentication
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
# ... other middleware
]
# INCORRECT: Authentication before sessions
MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware', # Needs sessions!
'django.contrib.sessions.middleware.SessionMiddleware',
# ... other middleware
]
# Why this matters:
class AuthenticationDependencyExample:
"""Shows why authentication needs sessions"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Authentication middleware tries to access request.session
try:
user_id = request.session.get('_auth_user_id') # Requires SessionMiddleware
if user_id:
request.user = User.objects.get(pk=user_id)
else:
request.user = AnonymousUser()
except AttributeError:
# request.session doesn't exist - SessionMiddleware not run yet!
raise ImproperlyConfigured("SessionMiddleware must come before AuthenticationMiddleware")
response = self.get_response(request)
return response
# CORRECT: CSRF after sessions and auth
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
# ... other middleware
]
# Why this order matters:
class CSRFOrderingExample:
"""Demonstrates CSRF middleware dependencies"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# CSRF middleware needs to:
# 1. Access session to store/retrieve CSRF token
# 2. Know if user is authenticated (affects CSRF requirements)
if request.method in ('POST', 'PUT', 'PATCH', 'DELETE'):
# Check CSRF token from session (needs SessionMiddleware)
csrf_token = request.session.get('csrf_token')
# Different CSRF rules for authenticated users (needs AuthMiddleware)
if request.user.is_authenticated:
# Stricter CSRF validation for authenticated users
pass
response = self.get_response(request)
return response
# Cache middleware has special positioning requirements
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware', # MUST be first
'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',
'django.middleware.cache.FetchFromCacheMiddleware', # MUST be last
]
# Why this positioning is required:
class CacheMiddlewareExample:
"""Demonstrates cache middleware positioning requirements"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# UpdateCacheMiddleware (first):
# - Needs to see the final response after all modifications
# - Must cache the complete, processed response
# FetchFromCacheMiddleware (last):
# - Should return cached response before any processing
# - Bypasses all other middleware when cache hit occurs
response = self.get_response(request)
return response
class CustomCacheMiddleware:
"""Custom cache middleware demonstrating proper positioning"""
def __init__(self, get_response):
self.get_response = get_response
from django.core.cache import cache
self.cache = cache
def __call__(self, request):
# This should be positioned early to check cache first
cache_key = self.get_cache_key(request)
# Try to get cached response
cached_response = self.cache.get(cache_key)
if cached_response:
print(f"Cache HIT for {request.path}")
return cached_response
print(f"Cache MISS for {request.path}")
# Get response from rest of middleware chain
response = self.get_response(request)
# Cache the response (after all processing)
if response.status_code == 200:
self.cache.set(cache_key, response, 300) # 5 minutes
return response
def get_cache_key(self, request):
return f"page_cache:{request.path}:{request.GET.urlencode()}"
# Positioning considerations:
MIDDLEWARE = [
'myapp.middleware.CustomCacheMiddleware', # Early for cache hits
'django.middleware.security.SecurityMiddleware',
# ... other middleware that modify responses
]
# Recommended middleware order for typical Django app
MIDDLEWARE = [
# 1. Security (must be first)
'django.middleware.security.SecurityMiddleware',
# 2. Cache update (if using cache middleware)
'django.middleware.cache.UpdateCacheMiddleware',
# 3. Core functionality
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
# 4. Internationalization (after auth to access user preferences)
'django.middleware.locale.LocaleMiddleware',
# 5. Security (clickjacking)
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# 6. Custom middleware (business logic)
'myapp.middleware.UserActivityMiddleware',
'myapp.middleware.RequestLoggingMiddleware',
# 7. Cache fetch (must be last if using cache middleware)
'django.middleware.cache.FetchFromCacheMiddleware',
]
# Middleware order for API applications
MIDDLEWARE = [
# 1. Security
'django.middleware.security.SecurityMiddleware',
# 2. CORS (for API access)
'corsheaders.middleware.CorsMiddleware',
# 3. Core (minimal for APIs)
'django.middleware.common.CommonMiddleware',
# 4. Authentication (API-specific)
'rest_framework.middleware.AuthenticationMiddleware',
# 5. Rate limiting (important for APIs)
'myapp.middleware.RateLimitMiddleware',
# 6. Request/Response processing
'myapp.middleware.APILoggingMiddleware',
'myapp.middleware.JSONResponseMiddleware',
# 7. Error handling
'myapp.middleware.APIErrorHandlingMiddleware',
]
class DataSharingMiddleware:
"""First middleware - sets up shared data"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Set up data for other middleware
request.middleware_data = {
'start_time': time.time(),
'request_id': str(uuid.uuid4())[:8],
'processing_chain': []
}
response = self.get_response(request)
# Final processing
total_time = time.time() - request.middleware_data['start_time']
response['X-Total-Processing-Time'] = f"{total_time:.4f}"
response['X-Request-ID'] = request.middleware_data['request_id']
return response
class ProcessingMiddleware:
"""Second middleware - uses and modifies shared data"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Use data from previous middleware
if hasattr(request, 'middleware_data'):
request.middleware_data['processing_chain'].append('ProcessingMiddleware')
# Add processing step timing
step_start = time.time()
response = self.get_response(request)
# Add step timing
if hasattr(request, 'middleware_data'):
step_time = time.time() - step_start
response['X-Processing-Step-Time'] = f"{step_time:.4f}"
return response
class LoggingMiddleware:
"""Third middleware - logs the complete chain"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
# Log the complete processing chain
if hasattr(request, 'middleware_data'):
chain = request.middleware_data.get('processing_chain', [])
logger.info(f"Request {request.middleware_data['request_id']} "
f"processed by: {' → '.join(chain)}")
return response
# Order matters for data sharing:
MIDDLEWARE = [
'myapp.middleware.DataSharingMiddleware', # Sets up data
'myapp.middleware.ProcessingMiddleware', # Uses and modifies data
'myapp.middleware.LoggingMiddleware', # Logs final state
]
class TokenAuthMiddleware:
"""Extract token from request"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Extract token from header
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
if auth_header.startswith('Bearer '):
request.auth_token = auth_header[7:]
else:
request.auth_token = None
response = self.get_response(request)
return response
class UserAuthMiddleware:
"""Authenticate user based on token"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Use token from previous middleware
if hasattr(request, 'auth_token') and request.auth_token:
try:
# Validate token and get user
request.user = self.get_user_from_token(request.auth_token)
except Exception:
request.user = AnonymousUser()
else:
request.user = AnonymousUser()
response = self.get_response(request)
return response
def get_user_from_token(self, token):
# Token validation logic
pass
class PermissionMiddleware:
"""Check permissions based on authenticated user"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Check permissions using user from previous middleware
if hasattr(request, 'user') and request.path.startswith('/api/admin/'):
if not request.user.is_staff:
return HttpResponseForbidden('Admin access required')
response = self.get_response(request)
return response
# Correct order for auth chain:
MIDDLEWARE = [
'myapp.middleware.TokenAuthMiddleware', # Extract token
'myapp.middleware.UserAuthMiddleware', # Authenticate user
'myapp.middleware.PermissionMiddleware', # Check permissions
]
class MiddlewareOrderDebugger:
"""Debug middleware execution order"""
def __init__(self, get_response):
self.get_response = get_response
self.middleware_name = self.__class__.__name__
def __call__(self, request):
# Log request processing
print(f"[REQUEST] {self.middleware_name}: {request.path}")
# Add to processing chain
if not hasattr(request, '_middleware_chain'):
request._middleware_chain = []
request._middleware_chain.append(f"{self.middleware_name}:request")
response = self.get_response(request)
# Log response processing
print(f"[RESPONSE] {self.middleware_name}: {response.status_code}")
request._middleware_chain.append(f"{self.middleware_name}:response")
# Log complete chain on final middleware
if len(request._middleware_chain) >= 2: # At least one complete cycle
print(f"[CHAIN] {' → '.join(request._middleware_chain)}")
return response
# Use for debugging:
class DebugFirstMiddleware(MiddlewareOrderDebugger):
pass
class DebugSecondMiddleware(MiddlewareOrderDebugger):
pass
class DebugThirdMiddleware(MiddlewareOrderDebugger):
pass
# Problem 1: Authentication before sessions
# WRONG:
MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', # Too late!
]
# SOLUTION:
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
]
# Problem 2: Custom middleware modifying response after cache
# WRONG:
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
'myapp.middleware.ResponseModifierMiddleware', # Modifies after caching!
'django.middleware.cache.FetchFromCacheMiddleware',
]
# SOLUTION:
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
'myapp.middleware.ResponseModifierMiddleware', # Modify before caching
]
# Problem 3: Security headers after content modification
# WRONG:
MIDDLEWARE = [
'myapp.middleware.ContentModifierMiddleware',
'django.middleware.security.SecurityMiddleware', # Headers added too late!
]
# SOLUTION:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'myapp.middleware.ContentModifierMiddleware',
]
from django.test import TestCase, override_settings
from django.test.client import RequestFactory
class MiddlewareOrderTests(TestCase):
"""Test middleware order dependencies"""
def setUp(self):
self.factory = RequestFactory()
@override_settings(MIDDLEWARE=[
'myapp.middleware.FirstMiddleware',
'myapp.middleware.SecondMiddleware',
])
def test_correct_middleware_order(self):
"""Test that middleware executes in correct order"""
response = self.client.get('/test/')
# Check that headers are added in correct order
self.assertIn('X-First-Middleware', response)
self.assertIn('X-Second-Middleware', response)
@override_settings(MIDDLEWARE=[
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', # Wrong order
])
def test_incorrect_middleware_order_fails(self):
"""Test that incorrect order causes issues"""
with self.assertRaises(Exception):
response = self.client.get('/test/')
def test_middleware_data_flow(self):
"""Test data flow between middleware"""
request = self.factory.get('/test/')
# Simulate middleware chain
first_middleware = FirstMiddleware(lambda r: HttpResponse())
second_middleware = SecondMiddleware(first_middleware)
response = second_middleware(request)
# Verify data was passed correctly
self.assertTrue(hasattr(request, 'middleware_data'))
# Use this checklist for middleware ordering:
MIDDLEWARE_CHECKLIST = """
1. ✓ SecurityMiddleware first
2. ✓ UpdateCacheMiddleware early (if used)
3. ✓ SessionMiddleware before AuthenticationMiddleware
4. ✓ AuthenticationMiddleware before permission checks
5. ✓ CSRF after sessions and auth
6. ✓ Custom middleware after core Django middleware
7. ✓ FetchFromCacheMiddleware last (if used)
8. ✓ No circular dependencies
9. ✓ Performance-critical middleware positioned appropriately
10. ✓ Tested with actual request flow
"""
# Base middleware order
BASE_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',
]
# Development additions
DEVELOPMENT_MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
'myapp.middleware.DevelopmentLoggingMiddleware',
]
# Production additions
PRODUCTION_MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
] + BASE_MIDDLEWARE + [
'myapp.middleware.ProductionLoggingMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
]
# settings/development.py
MIDDLEWARE = BASE_MIDDLEWARE + DEVELOPMENT_MIDDLEWARE
# settings/production.py
MIDDLEWARE = PRODUCTION_MIDDLEWARE
Now that you understand middleware ordering, let's explore performance considerations and debugging techniques for middleware in production environments.
Creating Custom Middleware
Custom middleware allows you to implement application-specific functionality that runs for every request. This chapter covers designing, implementing, and testing custom middleware for various use cases.
Performance and Debugging
Middleware can significantly impact application performance and introduce complex debugging challenges. This chapter covers techniques for optimizing middleware performance and debugging middleware-related issues.