Skip to content

第12章:中间件和信号

12.1 中间件系统

中间件概念

中间件是Django处理请求/响应过程中的钩子框架。它是一个轻量级、低级的"插件"系统,用于全局改变Django的输入或输出。

中间件的工作流程:

  1. 请求到达Django应用时,会依次通过每个中间件的process_request方法
  2. 然后通过process_view方法
  3. 视图函数处理完成后,响应会依次通过每个中间件的process_response方法

内置中间件

Django提供了许多内置中间件:

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

常用内置中间件说明:

  • SecurityMiddleware: 提供安全相关的HTTP头
  • SessionMiddleware: 启用会话支持
  • CommonMiddleware: 处理一些常用功能
  • CsrfViewMiddleware: 提供CSRF保护
  • AuthenticationMiddleware: 将用户与请求关联
  • MessageMiddleware: 启用基于Cookie和会话的消息支持
  • XFrameOptionsMiddleware: 防止点击劫持

自定义中间件

创建自定义中间件:

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

class TimingMiddleware(MiddlewareMixin):
    """记录请求处理时间的中间件"""
    
    def process_request(self, request):
        """在Django确定要使用的视图之前调用"""
        request.start_time = time.time()
        print(f"请求开始: {request.path}")
        return None  # 继续处理请求
    
    def process_view(self, request, view_func, view_args, view_kwargs):
        """在Django调用视图之前调用"""
        print(f"即将调用视图: {view_func.__name__}")
        return None  # 继续处理视图
    
    def process_response(self, request, response):
        """在所有响应返回浏览器之前调用"""
        if hasattr(request, 'start_time'):
            duration = time.time() - request.start_time
            print(f"请求完成: {request.path}, 耗时: {duration:.2f}秒")
        return response

class MaintenanceMiddleware(MiddlewareMixin):
    """维护模式中间件"""
    
    def process_request(self, request):
        # 检查是否处于维护模式
        from django.conf import settings
        if getattr(settings, 'MAINTENANCE_MODE', False):
            # 检查是否是管理员
            if not request.user.is_staff:
                return HttpResponse(
                    '<h1>网站维护中</h1><p>请稍后再访问</p>',
                    status=503
                )
        return None

class LoggingMiddleware(MiddlewareMixin):
    """请求日志中间件"""
    
    def process_request(self, request):
        # 记录请求信息
        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):
        """获取客户端IP地址"""
        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

# 更复杂的中间件示例
class ContentSecurityPolicyMiddleware(MiddlewareMixin):
    """内容安全策略中间件"""
    
    def process_response(self, request, response):
        # 添加CSP头
        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

在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',
    # 自定义中间件
    'myapp.middleware.TimingMiddleware',
    'myapp.middleware.LoggingMiddleware',
    'myapp.middleware.MaintenanceMiddleware',
]

请求/响应处理

中间件可以处理不同阶段的请求和响应:

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

class AdvancedMiddleware(MiddlewareMixin):
    """高级中间件示例"""
    
    def process_request(self, request):
        """
        在每个请求上调用,在Django确定要使用的视图之前
        返回None继续处理,返回HttpResponse对象则短路处理
        """
        # 检查请求频率限制
        if self.is_rate_limited(request):
            return JsonResponse({'error': '请求过于频繁'}, status=429)
        
        # 添加自定义属性到请求对象
        request.custom_data = {
            'processed_time': time.time(),
            'middleware_version': '1.0'
        }
        
        return None
    
    def process_view(self, request, view_func, view_args, view_kwargs):
        """
        在Django调用视图之前调用
        可以用来修改传递给视图的参数
        """
        # 记录视图调用
        print(f"调用视图函数: {view_func.__name__}")
        
        # 检查视图是否需要特殊权限
        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("无效的API密钥")
        
        return None
    
    def process_exception(self, request, exception):
        """
        当视图抛出异常时调用
        可以用来处理特定类型的异常
        """
        if isinstance(exception, PermissionDenied):
            return JsonResponse({'error': '权限不足'}, status=403)
        
        # 记录异常
        import logging
        logger = logging.getLogger('exception_logger')
        logger.error(f"视图异常: {exception}", exc_info=True)
        
        return None
    
    def process_response(self, request, response):
        """
        在所有响应返回浏览器之前调用
        可以用来修改响应
        """
        # 添加自定义响应头
        response['X-Processed-By'] = 'Django-Advanced-Middleware'
        
        # 记录响应时间
        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):
        """检查请求频率限制"""
        # 简单实现,实际应用中可以使用Redis等
        client_ip = self.get_client_ip(request)
        # 这里应该实现实际的频率限制逻辑
        return False
    
    def validate_api_key(self, api_key):
        """验证API密钥"""
        # 实际应用中应该查询数据库或配置文件
        valid_keys = ['valid-key-1', 'valid-key-2']
        return api_key in valid_keys
    
    def get_client_ip(self, request):
        """获取客户端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')

通过装饰器为视图添加中间件功能:

python
# decorators.py
from functools import wraps

def require_api_key(view_func):
    """为视图添加API密钥要求"""
    @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访问成功'})

中间件是Django框架中非常强大的功能,可以用来实现全局的请求处理、安全控制、性能监控等功能。

小结

Django中间件系统提供了强大的请求/响应处理能力:

  1. ✅ 理解中间件在请求/响应处理链中的位置和作用
  2. ✅ 掌握内置中间件的功能和配置方法
  3. ✅ 能够创建自定义中间件处理特定业务需求
  4. ✅ 了解中间件各处理阶段的执行顺序
  5. ✅ 通过装饰器为视图添加中间件功能

合理使用中间件能够构建更加灵活和安全的Web应用。

下一篇

我们将学习Django信号系统。

12.2 Django信号 →

目录

返回课程目录

Released under the Apache 2.0 License.