bigywater.tech
Django · 18

process_view、process_exception、process_template_response 的执行位置

除了 __call__,中间件还有三个钩子。它们各在请求生命周期的哪个位置?顺序一样吗?返回响应会怎样?这篇用三层带日志的中间件把答案直接打出来。

顺序相反
process_view 按 M1→M2→M3;process_exception 与 process_template_response 按 M3→M2→M1
短路
process_view 返回响应:后面的 process_view 与视图都不执行,响应仍从内层逐层返回
先钩子后渲染
TemplateResponse 在所有 process_template_response 之后才渲染

process_view 的位置

实测 · 本机执行输出
  [普通视图]
   M1.__call__ 进入
   M2.__call__ 进入
   M3.__call__ 进入
   M1.process_view(视图=ok)
   M2.process_view(视图=ok)
   M3.process_view(视图=ok)
   view 执行
   M3.__call__ 返回 status=200
   M2.__call__ 返回 status=200
   M1.__call__ 返回 status=200
   ==> 客户端得到 status = 200  body = b'ok'

所有中间件的 __call__ 先依次进入,此时 URL 已经解析,然后统一调用 process_view。所以 process_view 里能拿到 view_func 和参数,做「按视图决定是否放行」的事(比如 CSRF 就在这一层)。

TemplateResponse 与渲染

实测 · 本机执行输出
  [TemplateResponse]
   M1.__call__ 进入
   M2.__call__ 进入
   M3.__call__ 进入
   M1.process_view(视图=tpl)
   M2.process_view(视图=tpl)
   M3.process_view(视图=tpl)
   view 返回 TemplateResponse(还没渲染)
   M3.process_template_response(rendered=False)
   M2.process_template_response(rendered=False)
   M1.process_template_response(rendered=False)
   M3.__call__ 返回 status=200
   M2.__call__ 返回 status=200
   M1.__call__ 返回 status=200
   ==> 客户端得到 status = 200  body = b'hello 1'

process_template_response 只对有 render 方法的响应调用,此时还没渲染;可以在这里改 template_name、context_data。渲染发生在所有钩子之后。

process_view 返回响应

实测 · 本机执行输出
  [M2.process_view 返回响应]
   M1.__call__ 进入
   M2.__call__ 进入
   M3.__call__ 进入
   M1.process_view(视图=ok)
   M2.process_view(视图=ok)
     M2.process_view 直接返回响应
   M3.__call__ 返回 status=203
   M2.__call__ 返回 status=203
   M1.__call__ 返回 status=203
   ==> 客户端得到 status = 203  body = b'from M2.process_view'

返回响应就等于短路了视图,但因为 __call__ 已经全部进入过,响应仍会按洋葱顺序返回。

process_exception

实测 · 本机执行输出
  [三个 process_exception 都返回 None]
   M1.__call__ 进入
   M2.__call__ 进入
   M3.__call__ 进入
   M1.process_view(视图=bad)
   M2.process_view(视图=bad)
   M3.process_view(视图=bad)
   view 抛出 ValueError
   M3.process_exception(ValueError)
   M2.process_exception(ValueError)
   M1.process_exception(ValueError)
   M3.__call__ 返回 status=500
   M2.__call__ 返回 status=500
   M1.__call__ 返回 status=500
   ==> 客户端得到 status = 500  body = b'\n<!doctype html>\n<html lang="e'
  [M2.process_exception 返回响应]
   M1.__call__ 进入
   M2.__call__ 进入
   M3.__call__ 进入
   M1.process_view(视图=bad)
   M2.process_view(视图=bad)
   M3.process_view(视图=bad)
   view 抛出 ValueError
   M3.process_exception(ValueError)
   M2.process_exception(ValueError)
     M2.process_exception 返回了响应
   M3.__call__ 返回 status=200
   M2.__call__ 返回 status=200
   M1.__call__ 返回 status=200
   ==> 客户端得到 status = 200  body = b'handled by M2'

视图抛异常时按相反顺序询问每个中间件,第一个返回响应的就是最终结果。全都返回 None 时,异常继续向上,最终成为 500(第 17 篇)。

分步演示:三个钩子的顺序

中间件钩子
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。钩子写在同一个类里,用 django.test.Client 触发
    • 没有验证:异步中间件的钩子、process_view 抛异常时的行为、process_template_response 返回新响应对象、基于 MiddlewareMixin 的旧式中间件与本文写法的差别
    京ICP备2026067502号-1京公网安备11010502063487号