一、介绍
1. 中间件的定义
Django 中间件是一个轻量级、底层的“插件”系统,用于全局地修改 Django 的输入或输出。每个中间件组件可以对请求进行处理或对响应进行处理,或者同时处理二者。
2. 中间件的功能
中间件可以执行的操作包括:
- 请求预处理:在视图函数处理请求之前,执行一些前置操作(如请求验证、设置请求参数等)。
- 响应后处理:在视图函数处理完请求后,对生成的响应进行处理(如设置 HTTP 头、压缩响应内容等)。
- 请求/响应异常处理:捕捉到处理请求或响应过程中的异常。
3. 中间件的结构
每个中间件组件需要实现以下方法之一或全部:
__init__(self, get_response)
: 初始化中间件实例。get_response
是一个指向下一个中间件的调用链或最终视图的函数。__call__(self, request)
: 在每个请求上调用,用于处理请求和生成响应。通常在这里调用self.get_response(request)
将请求传递到下一个中间件或视图。
4. 中间件的调用流程
- 请求流程:当请求到达 Django 时,它按照在
settings.py
中定义的中间件顺序,从上到下依次通过各个中间件的__call__
方法。 - 响应流程:一旦请求到达视图并由视图生成响应,响应将按照中间件的相反顺序回传,允许每个中间件对响应进行修改或处理。
5. 中间件的配置
在 settings.py
的 MIDDLEWARE
配置列表中定义项目使用的中间件。中间件的执行顺序由它们在这个列表中的顺序决定。
6. 示例:自定义语言中间件
一个简单的自定义中间件示例,用于根据请求头 Accept-Language
动态设置语言环境。
from django.utils import translation
class LanguageMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
language = request.headers.get('Accept-Language', None)
if language == 'zh-hans':
translation.activate('zh-hans')
print("使用中文")
else:
translation.activate('en')
print("使用英文")
response = self.get_response(request)
return response
一、中间件的初始化过程 (__init__ 方法)
def __init__(self, get_response):
self.get_response = get_response
- __init__(self, get_response): 这是中间件的构造函数。当 Django 启动并加载中间件时,它会为每个中间件实例传递一个 get_response 参数。这个参数是一个函数,指向中间件链中下一个中间件的 __call__ 方法,或者在链条的末端时,指向最终的视图函数。
- self.get_response = get_response: 这行代码将传入的 get_response 函数保存为中间件实例的一个属性。这样做使得在处理请求的过程中,当前中间件可以通过调用 self.get_response(request) 来继续执行请求处理链,将请求传递给下一个中间件或视图。
二、请求处理过程 (__call__ 方法)
def __call__(self, request):
language = request.headers.get('Accept-Language', None)
if language == 'zh-hans':
print("使用中文")
else:
translation.activate('en')
print("使用英文")
return self.get_response(request)
- 请求到达时:当一个请求到达 Django 时,如果 LanguageMiddleware 是中间件链中的一部分,Django 将自动调用它的 __call__ 方法,并将当前的请求对象 (request) 传递进来。
- 语言检测和设置:
- language = request.headers.get('Accept-Language', None): 这行代码尝试从请求头中获取 Accept-Language 字段的值。这是一个 HTTP 标准头部,用于指明用户的语言偏好。
- 传递请求:return self.get_response(request) 这行代码调用前面在 __init__ 方法中保存的 get_response 函数,将控制权和修改后的请求传递给中间件链中的下一个中间件或直接到视图。这保证了请求继续在中间件链中流动,直到最终被视图处理。
这个 LanguageMiddleware 的工作流程典型地展示了 Django 中间件如何拦截和处理 HTTP 请求,以及如何在处理完毕后将请求继续传递。每个中间件都可以在请求到达视图之前或视图返回响应之后执行某些操作,这提供了极大的灵活性和控制力,使得 Django 的中间件架构非常强大。
7. 中间件的注意事项
● 中间件的执行顺序极其重要,因为它会影响请求的处理逻辑。
● 在中间件中修改请求或响应时需要小心,以免影响到后续中间件或视图的行为。
● 中间件应尽量保持轻量,避免执行耗时的操作,以免影响应用的响应时间。
二、中间件调用流程
(一) 流程
1. 示例背景
假设你的 Django 项目有三个中间件:
- MiddlewareA
- MiddlewareB
- MiddlewareC
并且有一个视图函数 view_function。
2. 初始化阶段
当 Django 项目启动时,它会按照 settings.py 中的 MIDDLEWARE 配置列表(配置为 A、B、C)来创建和设置中间件。初始化阶段如下:
- 创建 MiddlewareA 实例:
- Django 调用 MiddlewareA.__init__(get_response=MiddlewareB.__call__)
- 这里 get_response 是指向下一个中间件 MiddlewareB 的 __call__ 方法。
- 创建 MiddlewareB 实例:
- Django 调用 MiddlewareB.__init__(get_response=MiddlewareC.__call__)
- get_response 现在指向 MiddlewareC 的 __call__ 方法。
- 创建 MiddlewareC 实例:
- Django 调用 MiddlewareC.__init__(get_response=view_function)
- get_response 指向视图函数 view_function,这是请求处理链的最后一环。
3. 请求处理阶段
当一个请求到达 Django 服务器时,Django 会创建一个 HttpRequest 对象,并开始通过中间件链进行处理,如下所示:
- 请求首先到达 MiddlewareA:
- 调用 MiddlewareA.__call__(request)
- MiddlewareA 处理请求(如检查请求头、设置某些参数等)。
- 调用 self.get_response(request) 继续将请求传递到 MiddlewareB。
- 请求继续到 MiddlewareB:
- 调用 MiddlewareB.__call__(request)
- MiddlewareB 进行其处理。
- 调用 self.get_response(request) 将请求传递到 MiddlewareC。
- 请求最后到达 MiddlewareC:
- 调用 MiddlewareC.__call__(request)
- MiddlewareC 完成处理。
- 调用 self.get_response(request) 将请求传递给视图函数 view_function。
- 视图函数处理请求:
- 视图函数 view_function 处理请求并生成一个响应 HttpResponse。
4. 响应处理阶段
生成的响应会沿着相同的中间件链逆向传回:
- 响应从视图返回到 MiddlewareC:
- 如果 MiddlewareC 需要对响应进行处理或修改,它在这里执行。
- 响应传回到 MiddlewareB:
- MiddlewareB 对响应进行进一步的处理或修改。
- 最后响应返回到 MiddlewareA:
- MiddlewareA 进行最终的处理或修改。
- 响应返回给用户:
- 响应完成后,由 Django 返回给发起请求的客户端。
三、场景
(一) 根据不同 az 访问不同后端视图 url 但前端 url 不变
根据请求中的自定义头部 Az-Id
来决定是否对请求的 URL 进行重写。这种中间件可以用于动态修改请求路径,以便根据某些条件路由到不同的处理逻辑或服务。
class URLRewriteMiddleware(MiddlewareMixin):
def __init__(self, get_response):
super().__init__(get_response)
self.get_response = get_response
def process_request(self, request):
# 获取自定义头部 'az_id'
az_id = request.headers.get('Az-Id')
az_obj = CloudOsAz.objects.filter(az_id=az_id).first()
supplier = "test"
if az_obj and az_obj.supplier:
supplier = az_obj.supplier
# 根据头部值定义重定向逻辑
if supplier != "test":
old_path = request.path
new_path = old_path.replace('/url/v1/', '/url/v2/')
request.path = new_path
request.path_info = new_path
logger.info(f'req rewrite url: az_id: {az_id}, supplier: {supplier}, url: {request.path_info}')
1. 代码逻辑
- 初始化:
def __init__(self, get_response):
super().__init__(get_response)
self.get_response = get_response
- 这里调用了
super().__init__(get_response)
,这在使用MiddlewareMixin
时是多余的,因为MiddlewareMixin
的__init__
方法已经完成了相关初始化。 self.get_response
被赋值为传入的get_response
参数,这是中间件链中下一个处理函数的引用。
- 这里调用了
- 处理请求:
def process_request(self, request):
# 获取自定义头部 'az_id'
az_id = request.headers.get('Az-Id')
az_obj = CloudOsAz.objects.filter(az_id=az_id).first()
supplier = "test"
if az_obj and az_obj.supplier:
supplier = az_obj.supplier
# 根据头部值定义重定向逻辑
if supplier != "test":
old_path = request.path
new_path = old_path.replace('/url/v1/', '/url/v2/')
request.path = new_path
request.path_info = new_path
logger.info(f'req rewrite url: az_id: {az_id}, supplier: {supplier}, url: {request.path_info}')
- 获取头部:从请求头中获取
Az-Id
的值。 - 查询数据库:使用
az_id
查询CloudOsAz
表中的记录,得到供应商信息(supplier
)。如果找到记录且有供应商信息,就更新supplier
。 - 重写 URL:如果
supplier
不等于"test"
,则将请求的路径中的/url/v1/
部分替换为/url/v2/
,并更新request.path
和request.path_info
。这样请求将会被重定向到新的路径。
- 获取头部:从请求头中获取