Django: Creating Custom Middleware
Custom middleware in Django allows developers to create reusable components that process HTTP requests and responses, extending the framework’s built-in functionality. Integrated into Django’s request-response cycle, custom middleware enables tasks like request logging, authentication checks, or response modification. This tutorial explores creating custom middleware in Django, covering setup, implementation, performance considerations, and practical use cases for applications like e-commerce or content platforms.
01. What Is Custom Middleware?
Middleware in Django is a layer that processes requests before they reach views and responses before they leave the server. Custom middleware is user-defined code that hooks into this cycle to perform specific tasks, such as adding headers, tracking metrics, or enforcing custom authentication. It’s ideal for cross-cutting concerns in monolithic or microservices-based Django applications, ensuring modularity and reusability.
Example: Setting Up a Django Project for Custom Middleware
# Install Django
pip install django
# Create a Django project
django-admin startproject custom_middleware
# Navigate to project directory
cd custom_middleware
# Create an app
python manage.py startapp core
# Run the development server
python manage.py runserver
Output:
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
May 15, 2025 - 22:39:00
Django version 4.2, using settings 'custom_middleware.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Explanation:
startproject
- Initializes a Django project for custom middleware.- The
core
app will house middleware logic.
02. Core Concepts of Custom Middleware
Custom middleware is created as a Python class or function that processes requests, responses, or exceptions. It’s added to the MIDDLEWARE
setting to integrate with Django’s request-response pipeline. Below is a summary of key concepts and their roles:
Concept | Description | Use Case |
---|---|---|
Middleware Class | Class with methods like process_request |
Handle request/response lifecycle |
Function-based Middleware | Simple callable for quick tasks | Lightweight processing |
Middleware Order | Position in MIDDLEWARE list matters |
Control execution sequence |
Performance | Minimize overhead in middleware | Ensure scalability |
2.1 Creating a Class-based Middleware
Example: Logging Request Middleware
# core/middleware.py
import logging
logger = logging.getLogger(__name__)
class RequestLoggingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
logger.info(f"Request: {request.method} {request.path}")
response = self.get_response(request)
logger.info(f"Response: {response.status_code}")
return response
# custom_middleware/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',
'core.middleware.RequestLoggingMiddleware',
]
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'': {
'handlers': ['console'],
'level': 'INFO',
},
},
}
# core/views.py
from django.http import HttpResponse
def home(request):
return HttpResponse("Welcome to the site!")
# custom_middleware/urls.py
from django.contrib import admin
from django.urls import path
from core.views import home
urlpatterns = [
path('admin/', admin.site.urls),
path('', home, name='home'),
]
Output:
Request: GET /
Response: 200
Explanation:
__call__
- Processes each request and response.- Logs request method, path, and response status for debugging.
2.2 Function-based Middleware
Example: Adding Custom Header
# core/middleware.py
def custom_header_middleware(get_response):
def middleware(request):
response = get_response(request)
response['X-Custom-Header'] = 'MyApp'
return response
return middleware
# custom_middleware/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',
'core.middleware.custom_header_middleware',
]
Output:
Response Headers: X-Custom-Header: MyApp
Explanation:
- Function-based middleware is simpler for lightweight tasks.
- Adds a custom header to every response.
2.3 Handling Exceptions
Example: Exception Handling Middleware
# core/middleware.py
from django.http import JsonResponse
class ExceptionHandlingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception):
return JsonResponse({
'error': str(exception),
'path': request.path
}, status=500)
# custom_middleware/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',
'core.middleware.ExceptionHandlingMiddleware',
]
# core/views.py
def error_view(request):
raise ValueError("Test error")
Output:
{"error": "Test error", "path": "/error/"}
Explanation:
process_exception
- Catches unhandled exceptions.- Returns a JSON response for better API error handling.
2.4 Incorrect Middleware Implementation
Example: Blocking Middleware
# core/middleware.py (Incorrect)
import time
class SlowMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
time.sleep(2) # Simulate heavy processing
response = self.get_response(request)
return response
# custom_middleware/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',
'core.middleware.SlowMiddleware',
]
Output:
Request delayed by 2 seconds
Explanation:
- Heavy processing in middleware slows down all requests.
- Solution: Offload heavy tasks to asynchronous workers like Celery.
03. Effective Usage
3.1 Recommended Practices
- Keep middleware lightweight to avoid performance bottlenecks.
Example: Lightweight API Key Middleware
# core/middleware.py
from django.http import JsonResponse
class APIKeyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
api_key = request.headers.get('X-API-Key')
if api_key != 'secret-key':
return JsonResponse({'error': 'Invalid API key'}, status=401)
return self.get_response(request)
# custom_middleware/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',
'core.middleware.APIKeyMiddleware',
]
Output:
{"error": "Invalid API key"} # If key is missing or incorrect
- Simple header check ensures fast execution.
- Secures API endpoints without heavy logic.
3.2 Practices to Avoid
- Avoid modifying requests or responses unnecessarily.
Example: Overwriting Response Content
# core/middleware.py (Incorrect)
class OverwriteMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response.content = b"Overwritten content" # Overrides all responses
return response
Output:
Overwritten content # Breaks intended response
- Overwriting content can disrupt views or APIs.
- Solution: Modify responses only when necessary and with clear intent.
04. Common Use Cases
4.1 Request Rate Limiting
Limit requests to prevent abuse in an API.
Example: Rate Limiting Middleware
# core/middleware.py
from django.core.cache import cache
from django.http import JsonResponse
class RateLimitMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
client_ip = request.META.get('REMOTE_ADDR')
key = f"rate_limit:{client_ip}"
request_count = cache.get(key, 0)
if request_count >= 100: # 100 requests per minute
return JsonResponse({'error': 'Rate limit exceeded'}, status=429)
cache.set(key, request_count + 1, timeout=60)
return self.get_response(request)
# custom_middleware/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',
'core.middleware.RateLimitMiddleware',
]
Output:
{"error": "Rate limit exceeded"} # After 100 requests
Explanation:
- Uses cache to track request counts per IP.
- Prevents abuse without affecting legitimate users.
4.2 Custom Authentication
Enforce token-based authentication for microservices.
Example: Token Authentication Middleware
# core/middleware.py
from django.http import JsonResponse
class TokenAuthMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
token = request.headers.get('Authorization')
if token != 'Bearer valid-token':
return JsonResponse({'error': 'Invalid token'}, status=401)
return self.get_response(request)
# custom_middleware/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',
'core.middleware.TokenAuthMiddleware',
]
Output:
{"error": "Invalid token"} # If token is missing or invalid
Explanation:
- Validates tokens for secure service communication.
- Lightweight check suitable for microservices.
Conclusion
Custom middleware in Django empowers developers to extend the request-response cycle with application-specific logic, enhancing flexibility and modularity. By creating lightweight, well-structured middleware, you can address cross-cutting concerns efficiently. Key takeaways:
- Use class-based middleware for complex logic, function-based for simple tasks.
- Handle exceptions with
process_exception
. - Keep middleware lightweight to maintain performance.
- Avoid heavy processing or unnecessary response modifications.
With custom middleware, Django enables you to build scalable, maintainable web applications!
Comments
Post a Comment