Skip to content

Chapter 12: Middleware and Signals

12.1 Middleware System

Middleware Concept

Middleware is Django's hook framework for request/response processing. It is a lightweight, low-level "plugin" system for globally changing Django's input or output.

Middleware workflow:

  1. When request reaches Django application, it passes through each middleware's process_request method in order
  2. Then through process_view method
  3. After view function processing completes, response passes through each middleware's process_response method in order

Built-in Middleware

Django provides many built-in middleware:

python
# 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',
]

Common built-in middleware explanation:

  • SecurityMiddleware: Provides security-related HTTP headers
  • SessionMiddleware: Enables session support
  • CommonMiddleware: Handles some common functions
  • CsrfViewMiddleware: Provides CSRF protection
  • AuthenticationMiddleware: Associates users with requests
  • MessageMiddleware: Enables cookie and session-based message support
  • XFrameOptionsMiddleware: Prevents clickjacking

Custom Middleware

Create custom middleware:

python
# middleware.py
import time
from django.utils.deprecation import MiddlewareMixin
from django.http import HttpResponse

class TimingMiddleware(MiddlewareMixin):
    """Middleware to record request processing time"""
    
    def process_request(self, request):
        """Called before Django determines which view to use"""
        request.start_time = time.time()
        print(f"Request started: {request.path}")
        return None  # Continue processing request
    
    def process_view(self, request, view_func, view_args, view_kwargs):
        """Called before Django calls view"""
        print(f"About to call view: {view_func.__name__}")
        return None  # Continue processing view
    
    def process_response(self, request, response):
        """Called before all responses return to browser"""
        if hasattr(request, 'start_time'):
            duration = time.time() - request.start_time
            print(f"Request completed: {request.path}, time taken: {duration:.2f} seconds")
        return response

class MaintenanceMiddleware(MiddlewareMixin):
    """Maintenance mode middleware"""
    
    def process_request(self, request):
        # Check if in maintenance mode
        from django.conf import settings
        if getattr(settings, 'MAINTENANCE_MODE', False):
            # Check if admin
            if not request.user.is_staff:
                return HttpResponse(
                    '<h1>Website under maintenance</h1><p>Please visit again later</p>',
                    status=503
                )
        return None

class LoggingMiddleware(MiddlewareMixin):
    """Request logging middleware"""
    
    def process_request(self, request):
        # Record request information
        import logging
        logger = logging.getLogger('request_logger')
        logger.info(
            f"IP: {self.get_client_ip(request)}, "
            f"Method: {request.method}, "
            f"Path: {request.path}, "
            f"User: {getattr(request.user, 'username', 'Anonymous')}"
        )
        return None
    
    def get_client_ip(self, request):
        """Get client IP address"""
        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

# More complex middleware example
class ContentSecurityPolicyMiddleware(MiddlewareMixin):
    """Content Security Policy middleware"""
    
    def process_response(self, request, response):
        # Add CSP header
        csp_policy = (
            "default-src 'self'; "
            "script-src 'self' 'unsafe-inline' https://cdn.example.com; "
            "style-src 'self' 'unsafe-inline' https://cdn.example.com; "
            "img-src 'self' data: https:; "
            "font-src 'self' https://fonts.googleapis.com https://fonts.gstatic.com;"
        )
        response['Content-Security-Policy'] = csp_policy
        return response

Register middleware in settings.py:

python
# 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',
    # Custom middleware
    'myapp.middleware.TimingMiddleware',
    'myapp.middleware.LoggingMiddleware',
    'myapp.middleware.MaintenanceMiddleware',
]

Request/Response Processing

Middleware can handle different stages of request and response:

python
# advanced_middleware.py
from django.utils.deprecation import MiddlewareMixin
from django.http import JsonResponse
from django.core.exceptions import PermissionDenied

class AdvancedMiddleware(MiddlewareMixin):
    """Advanced middleware example"""
    
    def process_request(self, request):
        """
        Called on each request, before Django determines which view to use
        Return None to continue, return HttpResponse object to short-circuit
        """
        # Check request rate limit
        if self.is_rate_limited(request):
            return JsonResponse({'error': 'Too many requests'}, status=429)
        
        # Add custom attributes to request object
        request.custom_data = {
            'processed_time': time.time(),
            'middleware_version': '1.0'
        }
        
        return None
    
    def process_view(self, request, view_func, view_args, view_kwargs):
        """
        Called before Django calls view
        Can be used to modify parameters passed to view
        """
        # Record view call
        print(f"Calling view function: {view_func.__name__}")
        
        # Check if view requires special permissions
        if getattr(view_func, 'require_api_key', False):
            api_key = request.META.get('HTTP_X_API_KEY')
            if not api_key or not self.validate_api_key(api_key):
                raise PermissionDenied("Invalid API key")
        
        return None
    
    def process_exception(self, request, exception):
        """
        Called when view throws exception
        Can be used to handle specific exception types
        """
        if isinstance(exception, PermissionDenied):
            return JsonResponse({'error': 'Insufficient permissions'}, status=403)
        
        # Log exception
        import logging
        logger = logging.getLogger('exception_logger')
        logger.error(f"View exception: {exception}", exc_info=True)
        
        return None
    
    def process_response(self, request, response):
        """
        Called before all responses return to browser
        Can be used to modify response
        """
        # Add custom response headers
        response['X-Processed-By'] = 'Django-Advanced-Middleware'
        
        # Record response time
        if hasattr(request, 'custom_data'):
            process_time = time.time() - request.custom_data['processed_time']
            response['X-Process-Time'] = f"{process_time:.3f}s"
        
        return response
    
    def is_rate_limited(self, request):
        """Check request rate limit"""
        # Simple implementation, can use Redis etc. in real applications
        client_ip = self.get_client_ip(request)
        # Should implement actual rate limiting logic here
        return False
    
    def validate_api_key(self, api_key):
        """Validate API key"""
        # In real applications should query database or config file
        valid_keys = ['valid-key-1', 'valid-key-2']
        return api_key in valid_keys
    
    def get_client_ip(self, request):
        """Get client IP"""
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            return x_forwarded_for.split(',')[0]
        return request.META.get('REMOTE_ADDR')

Add middleware functionality to views using decorators:

python
# decorators.py
from functools import wraps

def require_api_key(view_func):
    """Add API key requirement to view"""
    @wraps(view_func)
    def wrapper(*args, **kwargs):
        return view_func(*args, **kwargs)
    
    wrapper.require_api_key = True
    return wrapper

# views.py
from .decorators import require_api_key

@require_api_key
def api_view(request):
    return JsonResponse({'message': 'API access successful'})

Middleware is a very powerful feature in Django framework, can be used to implement global request processing, security control, performance monitoring, etc.

Summary

Django middleware system provides powerful request/response processing capabilities:

  1. ✅ Understand middleware position and function in request/response processing chain
  2. ✅ Master built-in middleware functions and configuration methods
  3. ✅ Able to create custom middleware for specific business needs
  4. ✅ Understand middleware processing stages execution order
  5. ✅ Add middleware functionality to views using decorators

Proper use of middleware can build more flexible and secure web applications.

Next Article

We will learn Django signal system.

12.2 Django Signals →

Directory

Return to Course Directory

Released under the [BY-NC-ND License](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.en).