Django 中间件是一个轻量级的、底层的插件系统,用于全局修改 Django 的输入或输出。每个中间件组件负责完成某个特定的功能,例如会话管理、身份验证、CSRF保护等。
| 类型 | 描述 | 示例 |
|---|---|---|
| 请求中间件 | 处理传入请求 | 身份验证、会话 |
| 响应中间件 | 处理传出响应 | Gzip压缩、缓存 |
| 安全中间件 | 安全相关处理 | CSRF保护、XSS防护 |
| 处理中间件 | 处理流程控制 | 缓存、消息 |
Django 中间件按照在 MIDDLEWARE 设置中定义的顺序执行,形成一个处理管道。
中间件按顺序处理请求
调用对应的视图函数
处理视图抛出的异常
中间件按逆序处理响应
返回最终的 HTTP 响应
# settings.py 中的中间件顺序
MIDDLEWARE = [
'middleware1', # 1. 最先处理请求
'middleware2', # 2.
'middleware3', # 3.
# ...
'middlewareN', # N. 最后处理请求
]
# 执行流程:
# 请求 → middleware1 → middleware2 → ... → 视图
# 响应 ← middlewareN ← ... ← middleware2 ← middleware1
class SimpleMiddleware:
def __init__(self, get_response):
# 初始化,只在服务器启动时执行一次
self.get_response = get_response
# 可以在这里进行一次性初始化
def __call__(self, request):
# 在每个请求调用视图之前执行
# 请求处理逻辑
response = self.get_response(request)
# 在每个响应返回给客户端之前执行
# 响应处理逻辑
return response
def process_view(self, request, view_func, view_args, view_kwargs):
# 在调用视图之前,但在 URL 解析之后执行
# 可以返回 None 或 HttpResponse 对象
pass
def process_exception(self, request, exception):
# 当视图抛出异常时调用
# 可以返回 None 或 HttpResponse 对象
pass
def process_template_response(self, request, response):
# 在视图执行完成后调用,如果响应有 render 方法
# 必须返回实现了 render 方法的响应对象
return response
__call__ 必须 - 每个请求都会调用process_view 可选 - 视图调用前process_exception 可选 - 异常发生时process_template_response 可选 - 模板响应时Django 提供了多个内置中间件来处理常见的 Web 应用需求。
| 中间件 | 功能 | 配置 | 顺序建议 |
|---|---|---|---|
SecurityMiddleware |
安全相关功能,如 SSL 重定向、安全头设置 | 通常第一个 | 1 最早 |
SessionMiddleware |
会话支持 | 在 AuthenticationMiddleware 之前 | 2 |
CommonMiddleware |
常用功能,如 URL 重写、ETag 支持 | 位置灵活 | 3 |
CsrfViewMiddleware |
CSRF 保护 | 在视图中间件之前 | 4 |
AuthenticationMiddleware |
用户认证,添加 request.user |
在 SessionMiddleware 之后 | 5 |
MessageMiddleware |
消息框架支持 | 在 SessionMiddleware 之后 | 6 |
XFrameOptionsMiddleware |
点击劫持保护(已合并到 SecurityMiddleware) | 已弃用 | - |
MIDDLEWARE = [
# 安全中间件应该在最前面
'django.middleware.security.SecurityMiddleware',
# 会话支持
'django.contrib.sessions.middleware.SessionMiddleware',
# 通用中间件
'django.middleware.common.CommonMiddleware',
# CSRF 保护
'django.middleware.csrf.CsrfViewMiddleware',
# 用户认证
'django.contrib.auth.middleware.AuthenticationMiddleware',
# 消息框架
'django.contrib.messages.middleware.MessageMiddleware',
# 点击劫持保护 (旧版本)
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
# 自定义中间件
'myapp.middleware.CustomMiddleware',
]
# SecurityMiddleware 配置
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_SSL_REDIRECT = False # 生产环境设为 True
SECURE_HSTS_SECONDS = 31536000 # 1年
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# SessionMiddleware 配置
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
SESSION_COOKIE_AGE = 1209600 # 2周,默认
SESSION_COOKIE_SECURE = True # 仅HTTPS
SESSION_COOKIE_HTTPONLY = True
# CommonMiddleware 配置
APPEND_SLASH = True
PREPEND_WWW = False
request.session创建自定义中间件可以处理应用特定的横切关注点。
import time
from django.http import HttpResponse
from django.utils.deprecation import MiddlewareMixin
class SimpleTimingMiddleware:
"""简单的计时中间件"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 请求处理前的时间戳
start_time = time.time()
# 调用下一个中间件或视图
response = self.get_response(request)
# 计算处理时间
duration = time.time() - start_time
# 添加自定义头
response['X-Request-Duration'] = f'{duration:.2f}s'
# 记录日志(生产环境应使用 logging)
print(f'{request.method} {request.path} - {duration:.2f}秒')
return response
class MaintenanceModeMiddleware:
"""维护模式中间件"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 检查是否启用维护模式
if self.is_maintenance_mode():
# 排除管理员和特定路径
if (request.user.is_staff or
request.path.startswith('/admin/') or
request.path == '/maintenance/'):
return self.get_response(request)
# 返回维护页面
return HttpResponse(
'网站正在维护中,请稍后访问...',
status=503,
content_type='text/html'
)
return self.get_response(request)
def is_maintenance_mode(self):
# 从设置或数据库检查维护模式状态
from django.conf import settings
return getattr(settings, 'MAINTENANCE_MODE', False)
class AdvancedMiddleware:
"""使用所有钩子方法的中间件"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 请求预处理
print(f"开始处理请求: {request.method} {request.path}")
# 添加请求属性
request.processed_by_middleware = True
response = self.get_response(request)
# 响应后处理
print(f"请求处理完成: {response.status_code}")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
"""在调用视图之前执行"""
print(f"即将调用视图: {view_func.__name__}")
# 可以在这里进行权限检查或其他预处理
# 如果返回 HttpResponse,将跳过视图执行
# return None 表示继续正常流程
def process_exception(self, request, exception):
"""处理视图异常"""
print(f"捕获异常: {exception}")
# 可以记录异常或返回自定义错误页面
# 如果返回 HttpResponse,异常将被处理
# return None 表示继续传播异常
def process_template_response(self, request, response):
"""处理模板响应"""
if hasattr(response, 'render'):
# 可以修改响应上下文或添加额外数据
print("处理模板响应")
return response
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',
# 自定义中间件
'myapp.middleware.SimpleTimingMiddleware',
'myapp.middleware.MaintenanceModeMiddleware',
'myapp.middleware.AdvancedMiddleware',
]
以下是一些在实际项目中常用的自定义中间件示例。
from django.utils import timezone
from .models import UserActivity
class UserActivityMiddleware:
"""记录用户活动中间件"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
# 只记录认证用户的访问
if request.user.is_authenticated:
self.record_activity(request)
return response
def record_activity(self, request):
"""记录用户活动"""
try:
UserActivity.objects.create(
user=request.user,
path=request.path,
method=request.method,
user_agent=request.META.get('HTTP_USER_AGENT', ''),
ip_address=self.get_client_ip(request),
timestamp=timezone.now()
)
except Exception as e:
# 记录错误但不中断请求
print(f"记录用户活动失败: {e}")
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 CorsMiddleware:
"""简单的 CORS 中间件"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
# 添加 CORS 头
response['Access-Control-Allow-Origin'] = '*'
response['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
response['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
response['Access-Control-Allow-Credentials'] = 'true'
return response
def process_view(self, request, view_func, view_args, view_kwargs):
"""处理 OPTIONS 预检请求"""
if request.method == 'OPTIONS':
response = HttpResponse()
response['Access-Control-Allow-Origin'] = '*'
response['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
response['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
response['Access-Control-Allow-Credentials'] = 'true'
return response
return None
django-cors-headers 包。
class IPRestrictionMiddleware:
"""IP地址限制中间件"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
client_ip = self.get_client_ip(request)
# 检查IP是否在黑名单中
if self.is_ip_blocked(client_ip):
return HttpResponse(
'您的IP地址已被限制访问',
status=403
)
return self.get_response(request)
def get_client_ip(self, request):
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
def is_ip_blocked(self, ip):
# 从数据库或设置中检查IP黑名单
blocked_ips = getattr(settings, 'BLOCKED_IPS', [])
return ip in blocked_ips
import logging
import json
logger = logging.getLogger('django.request')
class RequestLoggingMiddleware:
"""请求日志中间件"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 记录请求信息
log_data = {
'method': request.method,
'path': request.path,
'user_agent': request.META.get('HTTP_USER_AGENT', ''),
'ip': self.get_client_ip(request),
'user': str(request.user) if request.user.is_authenticated else 'anonymous'
}
logger.info(f"请求开始: {json.dumps(log_data)}")
response = self.get_response(request)
# 记录响应信息
logger.info(f"请求完成: {request.method} {request.path} - {response.status_code}")
return response
def get_client_ip(self, request):
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
def process_exception(self, request, exception):
"""记录异常"""
logger.error(
f"请求异常: {request.method} {request.path} - {str(exception)}",
exc_info=True
)
# 使用缓存
from django.core.cache import cache
def __call__(self, request):
cache_key = f"middleware_{request.path}"
result = cache.get(cache_key)
if result is None:
# 计算并缓存结果
cache.set(cache_key, result, 300)
def __call__(self, request):
try:
response = self.get_response(request)
except Exception as e:
logger.error(f"中间件错误: {e}")
# 返回错误响应或继续传播异常
return HttpResponseServerError()
# 安全的响应头设置
response['X-Custom-Header'] = 'value'
# 避免泄露敏感信息
# response['X-Server-Path'] = sensitive_path # 不安全!
# 测试中间件
from django.test import RequestFactory
def test_middleware(self):
factory = RequestFactory()
request = factory.get('/')
middleware = MyMiddleware(lambda r: HttpResponse())
response = middleware(request)
self.assertEqual(response.status_code, 200)
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 中间件不执行 | 顺序错误或未注册 | 检查 MIDDLEWARE 设置和顺序 |
| AttributeError | 缺少必需的请求属性 | 确保依赖的中间件在前面 |
| 性能下降 | 中间件处理耗时操作 | 优化代码或使用缓存 |
| 循环导入 | 中间件导入模型或其他应用 | 使用懒加载或重构代码 |
# 调试中间件执行
class DebugMiddleware:
def __call__(self, request):
print(f"Debug: 处理请求 {request.path}")
response = self.get_response(request)
print(f"Debug: 响应状态 {response.status_code}")
return response