中间件

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

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

本文介绍中间件的工作原理、如何激活中间件以及如何编写自己的中间件。Django附带一些内置中间件,您可以直接使用。它们被记录在 built-in middleware reference .

编写自己的中间件

中间件工厂是一个可调用的 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 Django提供的可调用可能是实际视图(如果这是最后列出的中间件),也可能是链中的下一个中间件。当前的中间件不需要知道或关心它到底是什么,只需要它表示接下来会发生什么。

以上只是一个小小的简化 get_response 链中最后一个中间件的可调用不是实际视图,而是处理程序中负责应用的包装方法 view middleware ,使用适当的URL参数调用视图,并应用 template-responseexception 中间件。

中间件可以只支持同步Python(默认),也可以只支持异步Python,或者两者都支持。看到了吗 异步支持 了解如何宣传您支持的内容的详细信息,并了解您收到的请求类型。

中间件可以在您的python路径上的任何地方生存。

__init__(get_response)

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

  • Django只使用 get_response 参数,因此无法定义 __init__() 需要任何其他论证。

  • 不像 __call__() 方法,每个请求调用一次, __init__() 仅被调用 once ,当Web服务器启动时。

将中间件标记为未使用

在启动时确定是否应该使用中间件有时很有用。在这些情况下,您的中间件 __init__() 方法可能会提高 MiddlewareNotUsed . 然后Django将从中间件进程中删除该中间件,并将调试消息记录到 django.request 记录器何时 DEBUGTrue .

激活中间件

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

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_args 也不 view_kwargs 包含第一个视图参数 (request

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

它也应该返回 NoneHttpResponse 对象。如果它回来 None ,Django将继续处理此请求,执行任何其他 process_view() 中间件,然后是适当的视图。如果它返回 HttpResponse 对象,Django不需要调用适当的视图;它将对其应用响应中间件。 HttpResponse 并返回结果。

备注

访问 request.POST 在视图运行之前或在 process_view() 将阻止在中间件之后运行的任何视图 modify the upload handlers for the request ,通常应避免。

这个 CsrfViewMiddleware 类可以被视为异常,因为它提供了 csrf_exempt()csrf_protect() 允许视图显式控制CSRF验证应该在什么时候发生的修饰符。

process_exception()

process_exception(request, exception)

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

Dango调用 process_exception() 当视图引发异常时。 process_exception() 应该返回 NoneHttpResponse 对象。如果它返回 HttpResponse 对象,将应用模板响应和响应中间件,并将结果响应返回到浏览器。否则, default exception handling 踢进去。

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

process_template_response()

process_template_response(request, response)

request 是一个 HttpRequest 对象。 responseTemplateResponse 对象(或等效对象),由Django视图或中间件返回。

process_template_response() 如果响应实例具有 render() 方法,指示它是 TemplateResponse 或等同的。

它必须返回实现 render 方法。它可以改变给定的 response 通过改变 response.template_nameresponse.context_data 或者它可以创建并返回一个全新的 TemplateResponse 或等同的。

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

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

处理流响应

不像 HttpResponseStreamingHttpResponse 没有一个 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响应。 Certain exceptions 转换为4xx状态代码,而未知异常转换为500状态代码。

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

您可以设置 DEBUG_PROPAGATE_EXCEPTIONSTrue 跳过此转换并向上传播异常。

异步支持

中间件可以支持同步和异步请求的任何组合。如果Django不能同时支持这两种需求,Django将调整请求以满足中间件的需求,但会降低性能。

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

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

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

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

这个 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

升级pre-django 1.10风格中间件

class django.utils.deprecation.MiddlewareMixin

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

混音器提供 __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 , the __call__() 永远不会使用方法;Django调用 process_request()process_response() 直接。

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

这些是使用 MIDDLEWAREMIDDLEWARE_CLASSES

  1. MIDDLEWARE_CLASSES 每个中间件都将 process_response 方法,即使早期的中间件通过返回其 process_request 方法。下 MIDDLEWARE 中间件的行为更像洋葱:响应在退出时经过的层与在进入时看到请求的层相同。如果一个中间件短路,那么只有那个中间件和它前面的那些 MIDDLEWARE 将看到响应。

  2. MIDDLEWARE_CLASSESprocess_exception 应用于从中间件引发的异常 process_request 方法。下 MIDDLEWAREprocess_exception 仅适用于从视图(或从 render A方法 TemplateResponse )从中间件引发的异常被转换为适当的HTTP响应,然后传递到下一个中间件。

  3. MIDDLEWARE_CLASSES ,如果 process_response 方法引发异常, process_response 跳过所有早期中间件的方法,并 500 Internal Server Error 始终返回HTTP响应(即使引发的异常是 Http404 )下 MIDDLEWARE ,从中间件引发的异常将立即转换为适当的HTTP响应,然后行中的下一个中间件将看到该响应。中间件从不被跳过,因为中间件引发了异常。