Django Request Lifecycle
When a client request arrives at a Django application, it passes through several stages before reaching the view layer. Django uses the Web Server Gateway Interface (WSGI) to handle this communication. While Django ships with a built-in wsgiref module for development purposes, production environments typically employ uwsgi for better performance, often combined with Nginx for load balancing and reverse proxy functionality.
Key distinctions:
- WSGI — A specification defining the interface between web servers and Python web applications
- uwsgi — A protocol and implementation that provides faster communication than the standard WSGI
- wsgiref — Python's reference WSGI implementation, included in the standard library
Django Middleware Architecture
Middleware acts as a hook system that sits between the web server and your view functions. It intercepts incoming requests and outgoing responses, allowing developers to process or modify them globally across the entire application. Each middleware component handles a specific responsibility, such as session management, CSRF protection, or authentication.
Default Middleware Stack
Django includes seven middleware components registered in the settings.py file under MIDDLEWARE:
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',
]
These components execute sequentially: incoming requests flow downward through the stack, while outgoing responses travel upward.
The Five Middleware Methods
Django middleware can implement five lifecycle methods. Each method can return None to continue normal processing, or return an HttpResponse object to short-circuit the request pipeline and send that response directly to the client.
process_request(self, request)
process_view(self, request, view_func, view_args, view_kwargs)
process_template_response(self, request, response)
process_exception(self, request, exception)
process_response(self, request, response)
process_request
Signature: process_request(self, request)
This method executes before Django dispatches the request to the appropriate view. It receives the same request object that views receive, enabling you to inspect headers, session data, or cookies before the request reaches your business logic.
Execution timing: Called in top-to-bottom order as the request enters the middleware pipeline.
process_response
Signature: process_response(self, request, response)
This method runs when the view function returns an HttpResponse object. The response travels back up through the middleware stack in reverse order.
Important: This method must return a response object. Returning anything other than an HttpResponse will cause the browser to fail rendering the response.
Execution timing: Called in bottom-to-top order as the response exits the middleware pipeline.
process_view
Signature: process_view(self, request, view_func, view_args, view_kwargs)
Parameters:
request— TheHttpRequestobjectview_func— The actual function object that will handle the request (not a string name)view_args— Positional arguments to pass to the view functionview_kwargs— Keyword arguments to pass to the view function
Note: Neither view_args nor view_kwargs includes the first positional argument (the request object).
Execution timing: After all process_request methods complete and URL routing resolves the target view, each middleware's process_view executes top-to-bottom.
process_exception
Signature: process_exception(self, request, exception)
This method fires only when an unhandled exception occurs within a view function. The exception parameter contains the actual exception that was raised.
Execution timing: Called in bottom-to-top order when an exception propagates up the stack.
process_template_response
Signature: process_template_response(self, request, response)
This method only executes when the view returns a object with a render() method (a template response object). It enables middleware to modify or enhance the template rendering process.
Execution timing: Called in bottom-to-top order when the view returns a response object containing a custom render method.
class CustomRenderer:
def __init__(self, request, message):
self.request = request
self.message = message
def render(self):
return HttpResponse(self.message)
def index(request):
return CustomRenderer(request, "Welcome to the dashboard")
Middleware Execution Flow
Consider a three-middleware stack: ['M1', 'M2', 'M3']
Request phase:
M1.process_request()executesM2.process_request()executesM3.process_request()executes- URL routing resolves to a view
M1.process_view()executesM2.process_view()executesM3.process_view()executes- View function executes
Response phase:
9. M3.process_template_response() or M3.process_exception() executes (one or the other)
10. M2.process_response() executes
11. M1.process_response() executes
Short-circuit behavior: If any method returns an HttpResponse directly, subsequent methods in the pipeline are bypassed entirely, and execution immediately jumps to the response phase.
Building Custom Middleware
Step 1: Create the middleware module
Create a directory within your project to house the middleware classes. The directory structure and naming are flexible.
Step 2: Define the middleware class
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
class RequestLogger(MiddlewareMixin):
def process_request(self, request):
print("Request arrived:", request.path)
def process_response(self, request, response):
print("Response ready for:", request.path)
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("View being invoked:", view_func.__name__)
def process_exception(self, request, exception):
print("Exception caught:", str(exception))
def process_template_response(self, request, response):
print("Template response being processed")
return response
Step 3: Register in settings
Add the full dotted path to your middleware class in the MIDDLEWARE list:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'mymiddleware.middleware.RequestLogger',
]
Practical Example: Authentication Middleware
This example demonstrates how to protect multiple views by requiring authentication without modifying each view function.
views.py
from django.shortcuts import render, redirect, HttpResponse
def login(request):
if request.method == "GET":
return render(request, "login.html")
username = request.POST.get("username")
password = request.POST.get("password")
if username == "admin" and password == "secret":
request.session["authenticated"] = True
return redirect(request.GET.get("next", "/dashboard/"))
return HttpResponse("Invalid credentials")
def dashboard(request):
return HttpResponse("Dashboard content")
def settings(request):
return HttpResponse("Settings page")
middleware.py
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect
class AuthCheck(MiddlewareMixin):
def process_request(self, request):
current_path = request.path
if not current_path.startswith("/auth/login"):
if not request.session.get("authenticated"):
return redirect(f"/auth/login/?next={current_path}")
login.html
<html>
<head>
<meta charset="UTF-8">
<title>Authentication</title>
<link rel="stylesheet" href="/static/bootstrap3/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h2>Sign In</h2>
<form method="post">
<div class="form-group">
<label>Username</label>
<input type="text" name="username" class="form-control">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="password" class="form-control">
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
</div>
</div>
</div>
</body>
</html>
This authentication middleware intercepts every request. If the user hasn't authenticated, they're redirected to the login page with the original destination stored in the next parameter. After successful login, the user automatically returns to their original requested page. The middleware requires no changes to existing view functions, making it a clean separation of concerns.