Django 6.1 中间件

中间件是 Django 请求与响应处理的钩子框架,是一种轻量、底层的插件系统,用来全局改变输入或输出。每个组件负责特定功能,例如 AuthenticationMiddleware 根据会话把用户关联到请求。本文介绍工作方式、启用方法以及自定义中间件;现成组件见内置中间件参考。

编写自己的中间件

中间件工厂是一个可调用对象,接收 get_response 并返回中间件。返回的中间件接收请求并返回响应,接口与视图类似。函数写法:

def simple_middleware(get_response):
    # One-time configuration and initialization.

    def middleware(request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        response = get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        return response

    return middleware

也可以定义一个实例可调用的类:

class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        return response

get_response 代表处理链的下一步,无需关心它究竟是后续中间件还是视图。严格说,最后一层拿到的也不是视图本身,而是处理器的包装方法:它应用视图钩子,带着正确 URL 参数调用视图,再应用模板响应和异常钩子。

中间件默认支持同步 Python,也可以支持异步,或两者兼有。代码可以放在 Python 导入路径上的任何位置。

__init__(get_response)

工厂必须接收 get_response,并可初始化全局状态。Django 初始化时只传这一个参数,因此构造函数不能要求额外参数。__init__() 在服务器启动时调用一次,__call__() 则每个请求调用一次。

标记不使用的中间件

如果启动时判断某中间件无需启用,可在 __init__() 中抛出 MiddlewareNotUsed。Django 会将它移出处理链;当 DEBUG=True 时,还会向 django.request 记录调试消息。

激活中间件

将工厂的完整 Python 导入路径加入 MIDDLEWARE。django-admin startproject 生成的默认列表如下:

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

Django 并不强制使用中间件,列表可以为空,但强烈建议至少使用 CommonMiddleware。顺序很重要:AuthenticationMiddleware 依赖会话,必须在 SessionMiddleware 后运行。更多建议见中间件顺序。

执行顺序与分层

请求进入时,Django 按列表自顶向下执行中间件。可以把它想象成洋葱:每层调用 get_response,请求才进入下一层,最终到达视图。响应随后逆序穿过这些层。

若某一层直接返回响应、不调用 get_response,其内侧所有层以及视图都不会收到请求或响应。响应只经过本次请求已经穿过的层。

其他钩子

基于类的中间件可以增加以下三种特殊方法。

process_view(request, view_func, view_args, view_kwargs)

request 是 HttpRequest。view_func 是即将调用的实际函数对象,而非函数名。view_args 是位置参数列表,view_kwargs 是关键字参数字典,两者都不包含第一个参数 request。

此方法恰在调用视图之前执行,应返回 None 或 HttpResponse。返回 None 时继续执行其他 process_view() 以及视图;返回响应时跳过视图,把响应交给响应处理链。

应避免在视图运行前访问 request.POST,包括在 process_view() 中访问。这会使后续视图无法修改上传处理器。CsrfViewMiddleware 是例外:它提供 csrf_exempt() 和 csrf_protect(),使视图可以控制 CSRF 验证时机。

process_exception(request, exception)

exception 是视图抛出的 Exception。Django 在视图发生异常时调用此钩子。返回 None 则继续默认异常处理;返回 HttpResponse 则应用模板响应和响应中间件,再把结果返回浏览器。

异常钩子按响应方向逆序运行。一旦某层返回响应,更外层的 process_exception() 不再执行。

process_template_response(request, response)

response 是视图或中间件返回的 TemplateResponse 或同类对象。当视图执行完毕且响应具有 render() 方法时调用本钩子。

它必须返回同样具有 render() 的响应。可以修改 response.template_name、response.context_data,也可以返回新的模板响应。无需显式渲染;所有模板响应钩子执行完后,Django 自动渲染。此钩子同样按逆序执行。

处理流式响应

StreamingHttpResponse 没有 content 属性。因此需要检查流式标记并分别处理:

if response.streaming:
    response.streaming_content = wrap_streaming_content(response.streaming_content)
else:
    response.content = alter_content(response.content)

流内容可能大到无法放入内存。可以用新生成器包装它,但不能先消费整个迭代器:

def wrap_streaming_content(content):
    for chunk in content:
        yield alter_content(chunk)

流式响应既支持同步迭代器,也支持异步迭代器,包装函数必须匹配。需要同时支持两者时检查 StreamingHttpResponse.is_async。

异常处理

Django 把视图和中间件抛出的异常转换成 HTTP 错误响应:某些已知异常得到 4xx,未知异常得到 500。转换发生在每层中间件前后,像洋葱层之间的薄膜。因此正常情况下,调用 get_response 总会拿到 HTTP 响应,不必专门包裹 try/except 来捕获下游异常。即使下游抛出 Http404,当前层看到的也是状态为 404 的 HttpResponse。

将 DEBUG_PROPAGATE_EXCEPTIONS 设为 True 可以跳过转换,让异常继续向上传播。

异步支持

中间件可以支持同步、异步或两者。需要适配时 Django 会转换调用模式,但会增加开销。工厂函数或类可以声明:

  • sync_capable:是否支持同步,默认 True。
  • async_capable:是否支持异步,默认 False。

两者都为 True 时,Django 不转换请求。使用 inspect.iscoroutinefunction(get_response) 判断当前调用模式。django.utils.decorators 提供 sync_only_middleware()、async_only_middleware()、sync_and_async_middleware() 来设置这些标记。

返回的可调用对象必须与 get_response 匹配:异步时返回协程函数(async def)。可选的三个钩子也应匹配;否则 Django 会分别适配,增加额外开销。混合函数示例:

from inspect import iscoroutinefunction
from django.utils.decorators import sync_and_async_middleware


@sync_and_async_middleware
def simple_middleware(get_response):
    # One-time configuration and initialization goes here.
    if iscoroutinefunction(get_response):

        async def middleware(request):
            # Do something here!
            response = await get_response(request)
            return response

    else:

        def middleware(request):
            # Do something here!
            response = get_response(request)
            return response

    return middleware

混合中间件实际收到的调用模式可能与最终视图不同,因为 Django 会尽量减少整个栈的模式转换。如果当前层与异步视图之间有同步中间件,当前层仍可能同步执行。

异步类中间件必须正确标记实例为协程函数:

from inspect import iscoroutinefunction, markcoroutinefunction


class AsyncMiddleware:
    async_capable = True
    sync_capable = False

    def __init__(self, get_response):
        self.get_response = get_response
        if iscoroutinefunction(self.get_response):
            markcoroutinefunction(self)

    async def __call__(self, request):
        response = await self.get_response(request)
        # Some logic ...
        return response

升级 Django 1.10 之前的中间件

django.utils.deprecation.MiddlewareMixin 帮助编写同时兼容 MIDDLEWARE 和旧 MIDDLEWARE_CLASSES 的类,并支持同步、异步请求。Django 的内置中间件兼容两种配置。

Mixin 的构造函数接收 get_response 并保存为 self.get_response。其 __call__() 依次执行:已定义的 process_request(request)、后续处理链 self.get_response(request)、已定义的 process_response(request, response),最后返回响应。若请求钩子已返回响应,就不再进入后续处理链。

在旧 MIDDLEWARE_CLASSES 下不会调用 __call__(),Django 直接调用请求和响应钩子。通常只需继承此 Mixin 就能兼容;新的短路语义多半无害,甚至有益,少数类需要调整。主要区别是:

  1. 旧机制中,即使较早的请求钩子短路,每个中间件的 process_response() 仍会执行。新机制只让响应经过实际收到请求的层。
  2. 旧机制的 process_exception() 也处理 process_request() 抛出的异常。新机制只处理视图或 TemplateResponse.render() 的异常;中间件异常先转换为响应,再交给下一层。
  3. 旧机制中,某个 process_response() 抛出异常时,所有更早层的响应钩子被跳过,并总是返回 500,即使抛出的是 Http404。新机制立即转换为相应错误响应,后续响应钩子仍继续执行。

原文:中间件(Django 6.1)。© Django Software Foundation 及独立贡献者;中文整理与补译。Django 文档采用 BSD 3-Clause 许可证,版权声明、许可条件及免责声明见随附 licenses/Django-BSD-3-Clause.txt,转载时应保留。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容