Intercepting Requests: A Guide to Custom Django Middleware
Have you ever wondered how Django knows who a logged-in user is on every view? Or how CRSF protection works globally? The answer is Middleware.
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.
The Onion Architecture
Think of your Django app as the center of an onion.
1. When a Request comes in from the internet, it passes through the layers of the onion (Middleware) from the outside in.
2. It hits your View (the center).
3. The Response travels back out through the layers from the inside out.
Writing Custom Middleware
Let's solve a real-world problem. We want to measure exactly how long every request takes to process and add that time to a custom HTTP header.
Create a file middleware.py in your app folder.
import time
class TimingMiddleware:
def __init__(self, get_response):
# One-time configuration and initialization.
self.get_response = get_response
def __call__(self, request):
# Code to be executed for each request BEFORE
# the view (and later middleware) are called.
start_time = time.time()
# This line actually calls the view (or the next middleware)
response = self.get_response(request)
# Code to be executed for each request/response AFTER
# the view is called.
duration = time.time() - start_time
# Add custom header
response['X-Page-Generation-Duration-ms'] = int(duration * 1000)
return response
Installing the Middleware
Django executes middleware in the order they are listed in settings.py.
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# ...
'myapp.middleware.TimingMiddleware', # Add ours here
]
Another Example: IP Blocking
Imagine you are under attack from a specific IP address. You can block them before they even touch your database.
from django.core.exceptions import PermissionDenied
class IPBlockMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# In real life, load this from a database or cache (Redis)
self.BANNED_IPS = ['123.45.67.89']
def __call__(self, request):
user_ip = request.META.get('REMOTE_ADDR')
if user_ip in self.BANNED_IPS:
raise PermissionDenied("You are banned.")
return self.get_response(request)
Order Matters!
If you put your IPBlockMiddleware at the very bottom of the list, the request will go through Session middleware, Authentication middleware, and everything else before it gets blocked. That's a waste of resources.
Security middleware usually belongs at the top of the list.
Conclusion
Middleware is powerful because it allows you to apply logic globally without repeating code in every single View. Whether it's logging, authentication, styling headers, or exception handling, middleware is the place to do it.