中间件

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

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

他的文档解释了中间件是如何工作的,如何激活中间件,以及如何编写自己的中间件。Django 具有一些内置的中间件,你可以直接使用。它们被记录在 built-in middleware reference 中。

编写自己的中间件

中间件工厂是一个可调用的程序,它接受 get_response 可调用并返回中间件。中间件是可调用的,它接受请求并返回响应,就像视图一样。

中间件可以被写成这样的函数:

  1. def simple_middleware(get_response):
  2. # One-time configuration and initialization.
  3. def middleware(request):
  4. # Code to be executed for each request before
  5. # the view (and later middleware) are called.
  6. response = get_response(request)
  7. # Code to be executed for each request/response after
  8. # the view is called.
  9. return response
  10. return middleware

或者它可以写成一个类,它的实例是可调用的,如下:

  1. class SimpleMiddleware:
  2. def __init__(self, get_response):
  3. self.get_response = get_response
  4. # One-time configuration and initialization.
  5. def __call__(self, request):
  6. # Code to be executed for each request before
  7. # the view (and later middleware) are called.
  8. response = self.get_response(request)
  9. # Code to be executed for each request/response after
  10. # the view is called.
  11. return response

Django 提供的 get_response 响应可能是实际视图(如果这是最后列出的中间件),或者它可能是链中的下一个中间件。不需要知道或关心当前的中间件到底是什么,它只是代表了下一步的内容。

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

中间件可以只支持同步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

  1. MIDDLEWARE = [
  2. "django.middleware.security.SecurityMiddleware",
  3. "django.contrib.sessions.middleware.SessionMiddleware",
  4. "django.middleware.common.CommonMiddleware",
  5. "django.middleware.csrf.CsrfViewMiddleware",
  6. "django.contrib.auth.middleware.AuthenticationMiddleware",
  7. "django.contrib.messages.middleware.MessageMiddleware",
  8. "django.middleware.clickjacking.XFrameOptionsMiddleware",
  9. ]

Django 安装不需要任何中间件——如果您愿意的话,MIDDLEWARE 可以为空——但是强烈建议您至少使用 CommonMiddleware

MIDDLEWARE 的顺序很重要,因为中间件会依赖其他中间件。例如:类 AuthenticationMiddleware 在会话中存储经过身份验证的用户;因此,它必须在 SessionMiddleware 后面运行 。中间件。Session中间件。请参阅 中间件顺序 ,用于一些关于 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 调用视图前被调用。

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

备注

在视图运行前或在 process_view() 内访问中间件里的 request.POST 将阻止中间件之后运行的任何视图修改请求的上传处理程序 (modify the upload handlers for the request ),通常应该避免这样。

CsrfViewMiddleware 类可以被视为一个例外,因为它提供 csrf_exempt()csrf_protect() 装饰器,它们允许视图完全控制 CSRF 验证在什么时候进行。

process_exception()

process_exception(request, exception)

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

当视图引发异常时,Django 会调用 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.template_name`` 和 response.context_data 来改变给定的 response ,或者它可以创建和返回全新的 TemplateResponse 或等效对象。

你不需要显式地渲染响应——一旦所有模板中间件被调用,响应会被自动渲染。

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

处理流式响应

HttpResponse 不同,StreamingHttpResponse 没有 content 属性。因此,中间件不能再假设所有响应有 content 属性。如果它们需要访问 content,它们必须为流式响应进行测试,并且调整其行为:

  1. if response.streaming:
  2. response.streaming_content = wrap_streaming_content(response.streaming_content)
  3. else:
  4. response.content = alter_content(response.content)

备注

streaming_content 被假设为体积太大而无法在内存中保存。响应中间件可以将其包装在一个新的生成器里,但不能使用它。包装通常如下实现:

  1. def wrap_streaming_content(content):
  2. for chunk in content:
  3. yield alter_content(chunk)

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

Changed in Django 4.2:

支持使用异步迭代器的流式响应已经添加。

异常处理

Django 自动转换视图引发的异常,或者带有错误状态代码的特定 HTTP 响应内的中间件引发的异常。某些异常( Certain exceptions )被转换为 4xx 状态代码,而未知异常被转换为 500 状态代码。

这个变换发生在每个中间件的前后(你可以把它想象成洋葱每层中间的薄膜),因此每个中间件总是可以依赖于从调用它的 get_response 回调中获得某种类型的HTTP响应。中间件不需要担心它们对 get_response 的调用包装在 try/except 里,也不需要担心处理稍后的中间件或视图引发的异常。即使链中的下一个中间件引发了 Http404 异常,比如中间件不会查看异常,相反,它会得到一个带有404 status_codeHttpResponse 对象。

你可以将 DEBUG_PROPAGATE_EXCEPTIONS 设置为 True 来跳过这个转换并将异常传播到上层。

异步支持

中间件支持同步和异步请求的任意组合。如果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 会根据需要单独调整它们,但会有额外的性能损失。

下面以一个例子来说明如何创建一个支持这两种功能的中间件函数:

  1. from asgiref.sync import iscoroutinefunction
  2. from django.utils.decorators import sync_and_async_middleware
  3. @sync_and_async_middleware
  4. def simple_middleware(get_response):
  5. # One-time configuration and initialization goes here.
  6. if iscoroutinefunction(get_response):
  7. async def middleware(request):
  8. # Do something here!
  9. response = await get_response(request)
  10. return response
  11. else:
  12. def middleware(request):
  13. # Do something here!
  14. response = get_response(request)
  15. return response
  16. return middleware

备注

如果你声明了一个同时支持同步和异步调用的混合中间件,你得到的调用种类可能与底层视图不匹配。Django 会优化中间件调用栈,使其尽可能少的同步/异步转换。

因此,即使你包装的是一个异步视图,如果在你和视图之间有其他的、同步的中间件,你也可能会在同步模式下被调用。

在使用异步的基于类的中间件时,你必须确保实例被正确标记为协程函数:

  1. from asgiref.sync import iscoroutinefunction, markcoroutinefunction
  2. class AsyncMiddleware:
  3. async_capable = True
  4. sync_capable = False
  5. def __init__(self, get_response):
  6. self.get_response = get_response
  7. if iscoroutinefunction(self.get_response):
  8. markcoroutinefunction(self)
  9. async def __call__(self, request):
  10. response = await self.get_response(request)
  11. # Some logic ...
  12. 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_response 方法返回响应而短路。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 响应,然后下一个中间件将看到响应。中间件不会因为中间件引发异常而被跳过。