中间件

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

每个中间件组件负责执行某些特定功能。例如,Django 包含一个中间件组件,AuthenticationMiddleware,它使用会话将用户与请求关联。

本文档解释了中间件的工作原理、如何激活中间件以及如何编写自己的中间件。Django 附带了一些开箱即用的内置中间件。它们在内置中间件参考中进行了介绍。

编写自己的中间件

中间件工厂是一个可调用对象,它接受一个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

Django 提供的get_response可调用对象可能是实际的视图(如果这是最后列出的中间件),也可能是链中的下一个中间件。当前中间件不需要知道或关心它到底是什么,只需要知道它代表接下来发生的事情。

以上是一个轻微的简化——链中最后一个中间件的get_response可调用对象不会是实际的视图,而是处理程序中的一个包装方法,它负责应用视图中间件,使用适当的 URL 参数调用视图,并应用模板响应异常中间件。

中间件可以只支持同步 Python(默认)、只支持异步 Python 或两者都支持。有关如何宣传您支持的内容以及了解您正在获取哪种请求的详细信息,请参见异步支持

中间件可以位于 Python 路径上的任何位置。

__init__(get_response)

中间件工厂必须接受一个get_response参数。您还可以为中间件初始化一些全局状态。请记住一些注意事项

  • Django 只使用get_response参数初始化中间件,因此您不能将__init__()定义为需要任何其他参数。

  • 与每个请求调用一次的__call__()方法不同,__init__()只调用一次,即在 Web 服务器启动时。

将中间件标记为未使用

有时在启动时确定是否应使用中间件的一部分非常有用。在这些情况下,中间件的__init__()方法可能会引发MiddlewareNotUsed。然后,Django 将从中间件进程中删除该中间件,并在django.request记录器中记录调试消息(当DEBUGTrue时)。

激活中间件

要激活中间件组件,请将其添加到 Django 设置中的MIDDLEWARE列表。

MIDDLEWARE中,每个中间件组件都由一个字符串表示:中间件工厂的类或函数名称的完整 Python 路径。例如,这是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 安装不需要任何中间件——如果需要,MIDDLEWARE可以为空——但强烈建议您至少使用CommonMiddleware

MIDDLEWARE中的顺序很重要,因为中间件可能依赖于其他中间件。例如,AuthenticationMiddleware将已验证的用户存储在会话中;因此,它必须在SessionMiddleware之后运行。有关 Django 中间件类的排序的一些常见提示,请参见中间件排序

中间件顺序和分层

在请求阶段,在调用视图之前,Django 会按MIDDLEWARE中定义的顺序自上而下应用中间件。

您可以将其想象成洋葱:每个中间件类都是一个“层”,它包装视图(位于洋葱的核心)。如果请求穿过洋葱的所有层(每一层都调用get_response将请求传递到下一层),一直到核心的视图,则响应将在返回时穿过每一层(反向顺序)。

如果其中一层决定短路并返回响应而从未调用其get_response,则该层内部的任何洋葱层(包括视图)都将看不到请求或响应。响应只会通过请求传入的相同层返回。

其他中间件钩子

除了前面描述的基本请求/响应中间件模式之外,您还可以向基于类的中间件添加三种其他特殊方法

process_view()

process_view(request, view_func, view_args, view_kwargs)

request是一个HttpRequest对象。view_func是 Django 将要使用的 Python 函数。(它是实际的函数对象,而不是函数名称的字符串。)view_args是将传递给视图的位置参数列表,view_kwargs是将传递给视图的关键字参数字典。view_argsview_kwargs都不包括第一个视图参数(request)。

process_view()在 Django 调用视图之前调用。

它应该返回 None 或一个 HttpResponse 对象。如果返回 None,Django 将继续处理此请求,执行任何其他 process_view() 中间件,然后执行相应的视图。如果返回一个 HttpResponse 对象,Django 将不会调用相应的视图;它将把响应中间件应用于该 HttpResponse 并返回结果。

注意

在视图运行之前或在 process_view() 中访问 request.POST 将阻止任何在中间件之后运行的视图 修改请求的上传处理器,通常应该避免这种情况。

CsrfViewMiddleware 类可以被认为是一个例外,因为它提供了 csrf_exempt()csrf_protect() 装饰器,允许视图明确控制 CSRF 验证应该发生的时间点。

process_exception()

process_exception(request, exception)

request 是一个 HttpRequest 对象。exception 是视图函数引发的 Exception 对象。

当视图引发异常时,Django 会调用 process_exception()process_exception() 应该返回 None 或一个 HttpResponse 对象。如果返回一个 HttpResponse 对象,则将应用模板响应和响应中间件,并将生成的响应返回给浏览器。否则,将启动 默认异常处理

同样,中间件在响应阶段以相反的顺序运行,包括 process_exception。如果异常中间件返回响应,则位于该中间件上方的中间件类的 process_exception 方法将根本不会被调用。

process_template_response()

process_template_response(request, response)

request 是一个 HttpRequest 对象。response 是 Django 视图或中间件返回的 TemplateResponse 对象(或等效对象)。

如果响应实例具有 render() 方法(表示它是 TemplateResponse 或等效对象),则在视图执行完毕后立即调用 process_template_response()

它必须返回一个实现 render 方法的响应对象。它可以通过更改 response.template_nameresponse.context_data 来更改给定的 response,或者它可以创建并返回一个全新的 TemplateResponse 或等效对象。

您不需要显式呈现响应——一旦调用所有模板响应中间件,响应将自动呈现。

中间件在响应阶段以相反的顺序运行,包括 process_template_response()

处理流式响应

HttpResponse 不同,StreamingHttpResponse 没有 content 属性。结果,中间件不再假设所有响应都具有 content 属性。如果它们需要访问内容,则必须测试流式响应并相应地调整其行为。

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

注意

streaming_content 应假定其大小太大而无法存储在内存中。响应中间件可能会将其包装在一个新的生成器中,但不能使用它。包装通常如下实现:

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

StreamingHttpResponse 允许同步和异步迭代器。包装函数必须匹配。如果您的中间件需要支持这两种类型的迭代器,请检查 StreamingHttpResponse.is_async

异常处理

Django 会自动将视图或中间件引发的异常转换为具有错误状态代码的适当 HTTP 响应。某些异常 会转换为 4xx 状态代码,而未知异常会转换为 500 状态代码。

此转换发生在每个中间件之前和之后(您可以将其视为洋葱之间的一层薄膜),以便每个中间件都可以始终依赖于从其 get_response 可调用对象获得某种 HTTP 响应。中间件不需要担心将对 get_response 的调用包装在 try/except 中并处理可能由后面的中间件或视图引发的异常。即使链中的下一个中间件引发 Http404 异常,例如,您的中间件也不会看到该异常;相反,它将获得一个 HttpResponse 对象,其 status_code 为 404。

您可以将 DEBUG_PROPAGATE_EXCEPTIONS 设置为 True 以跳过此转换并向上传播异常。

异步支持

中间件可以支持同步和异步请求的任何组合。如果Django无法同时支持两者,它将调整请求以适应中间件的要求,但这会降低性能。

默认情况下,Django 假设您的中间件只能处理同步请求。要更改这些假设,请在您的中间件工厂函数或类上设置以下属性:

  • sync_capable 是一个布尔值,指示中间件是否可以处理同步请求。默认为 True

  • async_capable 是一个布尔值,指示中间件是否可以处理异步请求。默认为 False

如果您的中间件同时具有 sync_capable = Trueasync_capable = True,则 Django 将传递请求而不会转换它。在这种情况下,您可以通过使用 asgiref.sync.iscoroutinefunction 检查传递给您的 get_response 对象是否为协程函数来确定您的中间件是否将接收异步请求。

django.utils.decorators 模块包含 sync_only_middleware()async_only_middleware()sync_and_async_middleware() 装饰器,允许您将这些标志应用于中间件工厂函数。

返回的可调用对象必须与get_response方法的同步或异步性质匹配。如果您有一个异步的get_response,则必须返回一个协程函数(async def)。

如果提供了process_viewprocess_template_responseprocess_exception方法,也应该调整它们以匹配同步/异步模式。但是,如果您没有这样做,Django 会根据需要单独调整它们,但这会带来额外的性能损耗。

以下是如何创建一个支持同步和异步的中间件函数的示例:

from asgiref.sync 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 asgiref.sync 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 之前的中间件

class django.utils.deprecation.MiddlewareMixin

Django 提供了 django.utils.deprecation.MiddlewareMixin 来简化创建与 MIDDLEWARE 和旧的 MIDDLEWARE_CLASSES 都兼容的中间件类,并支持同步和异步请求。Django 包含的所有中间件类都与这两种设置兼容。

此 mixin 提供了一个 __init__() 方法,该方法需要一个 get_response 参数并将其存储在 self.get_response 中。

__call__() 方法:

  1. 调用 self.process_request(request)(如果已定义)。

  2. 调用 self.get_response(request) 以从后续中间件和视图获取响应。

  3. 调用 self.process_response(request, response)(如果已定义)。

  4. 返回响应。

如果与 MIDDLEWARE_CLASSES 一起使用,则 __call__() 方法将永远不会被使用;Django 直接调用 process_request()process_response()

在大多数情况下,继承此 mixin 就足以使旧式中间件与新系统兼容,并具有足够的向后兼容性。新的短路语义将对现有中间件无害,甚至是有益的。在少数情况下,中间件类可能需要一些更改以适应新的语义。

使用 MIDDLEWAREMIDDLEWARE_CLASSES 之间的行为差异:

  1. MIDDLEWARE_CLASSES 下,每个中间件的 process_response 方法将始终被调用,即使较早的中间件通过从其 process_request 方法返回响应而短路。在 MIDDLEWARE 下,中间件的行为更像洋葱:响应在返回途中经过的层与请求在进入途中看到的层相同。如果中间件短路,则只有该中间件和 MIDDLEWARE 中之前的中间件会看到响应。

  2. MIDDLEWARE_CLASSES 下,process_exception 应用于从中间件 process_request 方法引发的异常。在 MIDDLEWARE 下,process_exception 仅应用于从视图(或从 TemplateResponserender 方法)引发的异常。从中间件引发的异常将转换为相应的 HTTP 响应,然后传递给下一个中间件。

  3. MIDDLEWARE_CLASSES 下,如果 process_response 方法引发异常,则所有较早中间件的 process_response 方法将被跳过,并且始终返回 500 Internal Server Error HTTP 响应(即使引发的异常例如是 Http404)。在 MIDDLEWARE 下,从中间件引发的异常将立即转换为相应的 HTTP 响应,然后下一个中间件将看到该响应。由于中间件引发异常,中间件永远不会被跳过。

返回顶部