中间件¶ T0>

中间件是一个钩子框架,它们可以介入Django 的请求和响应处理过程。 它是一个轻量级、底层的“插件”系统,用于在全局修改Django 的输入或输出。

每个中间件组件负责完成某个特定的功能。 例如,Django 包含的一个中间件组件AuthenticationMiddleware ,它使用会话将用户和请求关联起来。

这篇文档讲解中间件如何工作、如何激活中间件以及如何编写你自己的中间件。 Django集成了一些内置的中间件可以直接开箱即用。 它们被归档在 built-in middleware reference.

在Django更改1.10:

引入了新的中间件风格,用于新的MIDDLEWARE设置。 如果您使用旧的MIDDLEWARE_CLASSES设置,则在使用新设置之前,您需要adapt old, custom middleware 本文档介绍新型中间件。 有关旧式中间件的工作原理的说明,请参阅旧版本的文档中的此页。

编写自己的中间件

一个中间件工厂是一个可调用的,它采用一个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(object):
    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可调用可能是实际的视图(如果这是最后列出的中间件),或者它可能是链中的下一个中间件。 当前的中间件不需要知道或者关心它究竟是什么,只是它代表了接下来的任何事情。

The above is a slight simplification – the get_response callable for the last middleware in the chain won’t be the actual view but rather a wrapper method from the handler which takes care of applying view middleware, calling the view with appropriate URL arguments, and applying template-response and exception middleware.

中间件可以在Python路径的任何地方生活。

__init__(get_response)

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

  • Django只使用get_response参数初始化您的中间件,因此您不能将__init__()定义为需要任何其他参数。
  • 与每个请求一次调用的__call__()方法不同,当Web服务器启动时,__init__()仅被调用一次
在Django更改1.10:

在旧版本中,在Web服务器响应其第一个请求之前,才调用__init__()

在旧版本中,__init__()不接受任何参数。 为了让您的中间件在Django 1.9及更早版本中使用,请使get_response可选参数(get_response=None)。

将中间件标记为未使用

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

激活中间件

要激活中间件组件,请将其添加到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中的顺序很重要,因为中间件可以依赖于其他中间件。 For instance, AuthenticationMiddleware stores the authenticated user in the session; therefore, it must run after SessionMiddleware. 一些关于Django中间件类的顺序的常见提示,请见Middleware ordering

中间件顺序和分层

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

你可以像洋葱一样想起来:每个中间件类都是一个“层”,它覆盖了洋葱核心的视图。 如果请求通过洋葱的所有层(每个调用get_response将请求传递到下一层),一直到核心的视图,响应将通过在每一层(以相反的顺序)的路上退出。

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

其他中间件钩子

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

process_view()

process_view请求view_funcview_argsview_kwargs/ T5>

request是一个HttpRequest 对象。 view_func是 Django会调用的一个Python的函数。 (它是一个真实的函数对象,不是函数的字符名称。) view_kwargs是一个会被传递到视图的位置参数列表,而view_args 是一个会被传递到视图的关键字参数字典。 view_kwargsview_args 都不包括第一个视图参数(request)。

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

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

Accessing request.POST inside middleware before the view runs or in process_view() will prevent any view running after the middleware from being able to modify the upload handlers for the request, and should normally be avoided.

CsrfViewMiddleware可以被认为是个例外,因为它提供csrf_exempt()csrf_protect()两个装饰器,允许视图显式控制在哪个点需要开启CSRF验证。

process_exception()

process_exception请求异常

request是一个HttpRequest 对象。 Exception是一个被视图中的方法抛出来的 exception对象。

当一个视图抛出异常时,Django会调用process_exception()来处理。 None应该返回一个process_exception() 或者一个HttpResponse对象。 如果它返回一个HttpResponse对象,则将应用模板响应和响应中间件,并将生成的响应返回给浏览器。 否则,default exception handling开始。

再次提醒,在处理响应期间,中间件的执行顺序是倒序执行的,这包括process_exception 如果异常中间件返回响应,那么中间件上面的中间件类的process_exception方法根本就不会被调用。

process_template_response()

process_template_response请求响应

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

如果响应的实例有render()方法,process_template_response()在视图刚好执行完毕之后被调用,这表明了它是一个TemplateResponse对象(或等价的对象)。

这个方法必须返回一个实现了render方法的响应对象。 它可以修改给定的response.template_name对象,通过修改 responseresponse.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)

Exception handling

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

这种转换发生在每个中间件之前和之后(您可以将其视为每个洋葱层之间的薄膜),因此每个中间件总是可以依靠从调用其get_response可调用。 中间件不需要担心在try/except中调用get_response,并处理由稍后的中间件或视图引发的异常。 Even if the very next middleware in the chain raises an Http404 exception, for example, your middleware won’t see that exception; instead it will get an HttpResponse object with a status_code of 404.

升级Django之前的1.10风格的中间件

django.utils.deprecation。 MiddlewareMixin T0> ¶ T1>

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()

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

这些是使用MIDDLEWAREMIDDLEWARE_CLASSES之间的行为差​​异:

  1. MIDDLEWARE_CLASSES下,每个中间件总是会调用它的process_response方法,即使早期的中间件通过从process_request方法返回一个响应来短路。 MIDDLEWARE下,中间件的行为更像一个洋葱:一个响应在出路上经历的层是同样的层,可以看到请求的路径。 如果中间件发生短路,则只有中间件和MIDDLEWARE之前的中间件才会看到响应。
  2. MIDDLEWARE_CLASSES下,process_exception适用于从中间件process_request方法引发的异常。 MIDDLEWARE下,process_exception仅适用于从TemplateResponse的视图(或render方法)引发的异常, 。 从中间件引发的异常转换为适当的HTTP响应,然后传递给下一个中间件。
  3. MIDDLEWARE_CLASSES下,如果process_response方法引发异常,则会跳过所有较早中间件的process_response方法,并且500 内部 服务器 错误 HTTP响应总是返回(即使引发的异常例如是一个Http404 MIDDLEWARE下,从中间件引发的异常将立即转换为适当的HTTP响应,然后下一个中间件将会看到该响应。 由于中间件引发异常,中间件不会被跳过。