bigywater.tech
Django · 17

Middleware 如何处理异常和短路响应

中间件里怎么处理异常?一层直接返回响应会发生什么?这篇不背结论,用 M1、M2、M3 三层中间件加日志,在真实请求里把每种情形的调用顺序和每层看到的东西都记下来。

洋葱
进入 M1→M2→M3,返回 M3→M2→M1;短路则内层不执行,外层照常收到响应
响应而非异常
视图或内层中间件抛异常时,外层看到的是 500(或 404)响应,不是异常
DEBUG
DEBUG 只改变 500 响应的正文,不改变异常转换的方式

正常与短路

实测 · 本机执行输出
  [三层都放行]
   M1 进入
   M2 进入
   M3 进入
   view 执行
   M3 收到响应 status=200
   M2 收到响应 status=200
   M1 收到响应 status=200
   ==> 客户端得到 status = 200  body 前 40 字节 = b'ok'
  [M2 直接返回响应(短路)]
   M1 进入
   M2 进入
   M2 直接返回响应(短路)
   M1 收到响应 status=202
   ==> 客户端得到 status = 202  body 前 40 字节 = b'short-circuit by M2'

M2 的短路在响应阶段仍会流过 M1,因此「在外层做统一的响应头 / 日志」对短路的请求同样有效。

视图抛异常

实测 · 本机执行输出
  [视图抛 ValueError(DEBUG=False)]
   M1 进入
   M2 进入
   M3 进入
   view 抛出 ValueError
   M3 收到响应 status=500
   M2 收到响应 status=500
   M1 收到响应 status=500
   ==> 客户端得到 status = 500  body 前 40 字节 = b'\n<!doctype html>\n<html lang="en">\n<head>'
  [视图抛 Http404]
   M1 进入
   M2 进入
   M3 进入
   view 抛出 Http404
   M3 收到响应 status=404
   M2 收到响应 status=404
   M1 收到响应 status=404
   ==> 客户端得到 status = 404  body 前 40 字节 = b'\n<!doctype html>\n<html lang="en">\n<head>'

写 try: response = self.get_response(request) except Exception: 在新式中间件里通常是抓不到视图异常的:异常已经在更里层被转换成响应了。要在异常发生时做事,用 process_exception 钩子(第 18 篇)或 got_request_exception 信号(本文没测)。

中间件自己抛异常

实测 · 本机执行输出
  [M2 在调用 get_response 之前抛出 ValueError]
   M1 进入
   M2 进入
   M2 抛出 ValueError
   M1 收到响应 status=500
   ==> 客户端得到 status = 500  body 前 40 字节 = b'\n<!doctype html>\n<html lang="en">\n<head>'

M2 抛出的异常由 M1 外面的转换层处理,M1 收到 500 响应;M3 与视图不会执行。

DEBUG=True

实测 · 本机执行输出
  [视图抛 ValueError(DEBUG=True)]
   M1 进入
   M2 进入
   M3 进入
   view 抛出 ValueError
   M3 收到响应 status=500
   M2 收到响应 status=500
   M1 收到响应 status=500
   ==> 客户端得到 status = 500  body 前 40 字节 = b'<!DOCTYPE html>\n<html lang="en">\n<head>\n'

同样的转换流程,正文换成调试页。

分步演示:洋葱模型与异常

中间件与异常
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。用 django.test.Client(raise_request_exception=False),日志写在进程内列表里
    • 没有验证:异步中间件(async def __call__)下的行为、StreamingHttpResponse 出错时的处理(响应已经开始发送后异常无法转换)、got_request_exception 信号、真实服务器(gunicorn / runserver)与测试客户端在异常处理上的细微差异
    京ICP备2026067502号-1京公网安备11010502063487号