Django Custom Middleware
Introduction
Django's middleware framework provides a powerful way to process requests and responses globally across your application. While Django includes several built-in middleware components, creating custom middleware allows you to implement specific functionality that executes during the request-response cycle.
Custom middleware can be used for a variety of purposes such as:
- Authentication and authorization
- Request logging and monitoring
- Response modification
- Performance tracking
- Custom headers manipulation
- Cross-origin resource sharing (CORS) handling
In this tutorial, we'll explore how to create, implement, and use custom middleware in your Django applications.
Understanding Middleware Architecture
Before diving into custom middleware, it's important to understand how Django's middleware system works:
- When a request comes in, Django processes it through middleware in order (top to bottom as defined in your settings)
- The request passes through each middleware's
process_request
method - After the view is called, the response passes back through middleware in reverse order through their
process_response
methods
Since Django 1.10, middleware uses a new style based on a callable class, though the old function-based middleware is still supported.
Creating Your First Custom Middleware
Let's start by creating a simple middleware that logs information about incoming requests:
Function-Based Middleware (New Style)
First, let's create a new file called middleware.py
in your Django application:
import time
from django.utils.deprecation import MiddlewareMixin
class RequestLogMiddleware(MiddlewareMixin):
"""Middleware to log all requests and their processing time."""
def process_request(self, request):
"""Store the start time when the request comes in."""
request.start_time = time.time()
def process_response(self, request, response):
"""Calculate and log the request duration."""
# If process_request was never called, start_time won't exist
if hasattr(request, 'start_time'):
duration = time.time() - request.start_time
print(f"Request to {request.path} took {duration:.2f}s to process.")
return response
Activating Your Middleware
To use your custom middleware, you need to add it to the MIDDLEWARE
list in your project's settings.py
file:
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',
'myapp.middleware.RequestLogMiddleware', # Our custom middleware
]
Now, every request to your application will be logged with its processing time.
Understanding Middleware Methods
The MiddlewareMixin
provides several methods you can implement:
process_request(request)
: Called on each request before Django decides which view to executeprocess_view(request, view_func, view_args, view_kwargs)
: Called just before Django calls the viewprocess_exception(request, exception)
: Called when a view raises an exceptionprocess_template_response(request, response)
: Called just after the view has finished executing, if the response has arender()
methodprocess_response(request, response)
: Called on all responses before they're sent to the client
You don't need to implement all of these methods—only the ones you need for your specific functionality.
Practical Examples
Let's explore more real-world examples of custom middleware:
1. IP Restriction Middleware
This middleware restricts access to your application based on IP addresses:
from django.http import HttpResponseForbidden
from django.utils.deprecation import MiddlewareMixin
class IPRestrictionMiddleware(MiddlewareMixin):
"""Middleware to restrict access based on IP address."""
ALLOWED_IPS = ['127.0.0.1', '192.168.1.1']
def process_request(self, request):
client_ip = self.get_client_ip(request)
if client_ip not in self.ALLOWED_IPS:
return HttpResponseForbidden("Access denied")
def get_client_ip(self, request):
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
2. Simple Authentication Middleware
This middleware checks if a user is authenticated for certain URL patterns:
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.utils.deprecation import MiddlewareMixin
import re
class AuthenticationMiddleware(MiddlewareMixin):
"""
Middleware to check if a user is authenticated for certain URLs.
"""
def process_request(self, request):
# Exempt certain paths from authentication
exempt_urls = [r'^/login/$', r'^/public/', r'^/api/public/']
# Check if the path matches any of the exempt patterns
path = request.path_info
if any(re.match(pattern, path) for pattern in exempt_urls):
return None
# Check if the user is authenticated
if not request.user.is_authenticated:
return HttpResponseRedirect(reverse('login') + f'?next={request.path}')
3. Response Modification Middleware
This middleware adds custom headers to all responses:
from django.utils.deprecation import MiddlewareMixin
class CustomHeadersMiddleware(MiddlewareMixin):
"""Middleware to add custom headers to all responses."""
def process_response(self, request, response):
response['X-Custom-Header'] = 'CustomValue'
response['Cache-Control'] = 'no-cache, no-store, must-revalidate'
response['Pragma'] = 'no-cache'
response['Expires'] = '0'
return response
4. Performance Monitoring Middleware
This middleware tracks slow requests and logs them for further investigation:
import time
import logging
from django.utils.deprecation import MiddlewareMixin
logger = logging.getLogger('performance')
class PerformanceMonitoringMiddleware(MiddlewareMixin):
"""Middleware to monitor performance of requests."""
THRESHOLD = 1.0 # seconds
def process_request(self, request):
request.start_time = time.time()
def process_response(self, request, response):
if hasattr(request, 'start_time'):
duration = time.time() - request.start_time
if duration > self.THRESHOLD:
logger.warning(
f"Slow request: {request.method} {request.path} took {duration:.2f}s"
)
# You could also collect additional context here
# such as query parameters, user info, etc.
return response
Using Middleware for Cross-Cutting Concerns
Middleware is ideal for handling cross-cutting concerns—aspects of your application that affect multiple parts of the system. Here are some examples:
CORS (Cross-Origin Resource Sharing) Middleware
from django.utils.deprecation import MiddlewareMixin
class CORSMiddleware(MiddlewareMixin):
"""Middleware to handle CORS headers."""
def process_response(self, request, response):
response['Access-Control-Allow-Origin'] = 'https://example.com'
response['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
response['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
# Handle preflight OPTIONS requests
if request.method == 'OPTIONS':
response.status_code = 200
return response
return response
Request ID Middleware
This middleware assigns a unique ID to each request, making it easier to track requests across different systems:
import uuid
from django.utils.deprecation import MiddlewareMixin
class RequestIDMiddleware(MiddlewareMixin):
"""Middleware to assign a unique ID to each request."""
def process_request(self, request):
request_id = str(uuid.uuid4())
request.id = request_id
def process_response(self, request, response):
if hasattr(request, 'id'):
response['X-Request-ID'] = request.id
return response
Advanced Middleware Concepts
Middleware Ordering
The order of middleware in the MIDDLEWARE
list is important. Middleware are processed in the order they're defined for requests, and in reverse order for responses. Consider this carefully when designing multiple middleware that may interact with each other.
Middleware and Django Settings
It's good practice to make your middleware configurable through Django settings:
from django.conf import settings
from django.utils.deprecation import MiddlewareMixin
class ConfigurableMiddleware(MiddlewareMixin):
"""Example of a configurable middleware."""
def process_request(self, request):
# Get configuration from settings with defaults
enabled = getattr(settings, 'CUSTOM_MIDDLEWARE_ENABLED', True)
if not enabled:
return None
# Use other settings
debug_mode = getattr(settings, 'CUSTOM_MIDDLEWARE_DEBUG', False)
if debug_mode:
print(f"Processing request to {request.path}")
Class-Based vs. Function-Based Middleware
While we've focused on class-based middleware (the current recommended approach), you can also create function-based middleware:
def simple_middleware(get_response):
# One-time configuration and initialization
def middleware(request):
# Code to be executed for each request before the view is called
response = get_response(request)
# Code to be executed for each response after the view is called
return response
return middleware
Add it to your MIDDLEWARE
settings just like class-based middleware.
Summary
Custom middleware is a powerful feature in Django that allows you to:
- Process requests and responses globally
- Implement cross-cutting concerns across your application
- Add functionality that applies to all views
- Keep your view code clean by separating concerns
By creating custom middleware, you can enhance your Django application with reusable components that handle common tasks such as authentication, logging, performance monitoring, and response modification.
Remember these key points:
- Middleware processes requests in order (top to bottom) and responses in reverse order
- Middleware can short-circuit the request process by returning a response
- Consider the order of middleware carefully when multiple middleware interact
- Use settings to make your middleware configurable
Exercises
- Create a middleware that adds a random quote to the bottom of HTML responses
- Implement a middleware that blocks requests based on a rate limit (e.g., no more than 100 requests per minute from a single IP)
- Develop a middleware that logs all POST data (excluding passwords and sensitive information)
- Create a middleware that adds timing information to the response headers
Additional Resources
- Django Official Documentation on Middleware
- Django's Built-in Middleware Reference
- Django Source Code for MiddlewareMixin
- OWASP Security Headers Project - Useful for security middleware implementations
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)