bigywater.tech
Flask · 62

模板渲染异常处理

模板里出错时 Flask 给用户和开发者各看到什么?行号对不对?流式模板出错时客户端会收到什么?这篇逐种错误触发一遍,并用真实的 gunicorn 看流式模板出错的下场。

18
curl 在流式模板出错、传输被中断时的退出码
2 层
include 里出错时 traceback 里出现的模板帧(outer.html 与 inner.html)
500
非测试模式下所有模板异常的状态码

各种模板错误

实测 · 本机执行输出
  GET /undef        → UndefinedError: 'user' is undefined | traceback 里的模板帧: ['undef.html:2']
  GET /syntax       → TemplateSyntaxError: Expected an expression, got 'end of statement block' | traceback 里的模板帧: ['syntax.html:4']
  GET /missing-tpl  → TemplateNotFound: nope.html | traceback 里的模板帧: []
  GET /typeerr      → TypeError: unsupported operand type(s) for +: 'NoneType' and 'int' | traceback 里的模板帧: ['typeerr.html:2']
  GET /in-include   → ZeroDivisionError: division by zero | traceback 里的模板帧: ['outer.html:2', 'inner.html:2']
  (模板帧 = Jinja 把渲染异常的 traceback 改写成指向模板文件与行号;语法错误时异常对象自带 filename 与 lineno)
  TemplateSyntaxError 的属性:lineno = 4 | name = syntax.html | filename 末尾 = syntax.html | message = Expected an expression, got 'end of statement block'

上一篇:模板中的惰性对象;下一篇:模板缓存。流式响应的更多细节见Streaming Response。

生产环境的 500

实测 · 本机执行输出
  GET /undef        → 500 '自定义 500 页'
  GET /missing-tpl  → 500 '自定义 500 页'
  GET /in-include   → 500 '自定义 500 页'

Undefined 的宽容

实测 · 本机执行输出
  默认 Undefined 渲染 [{{ nothing }}] → '[]'
  jinja_env.undefined = StrictUndefined 后 → UndefinedError: 'nothing' is undefined

流式模板出错

实测 · 本机执行输出
  GET /render-bad  → HTTP/1.1 500 INTERNAL SERVER ERROR | curl 退出码 0 | 收到的正文 '自定义 500 页'
  GET /stream-bad  → HTTP/1.1 200 OK | curl 退出码 18 | 收到的正文 '<ul>\n<li>1</li>\n<li>2</li>\n<li>3</li>\n<li>4'
  服务器日志里的错误行: ['[2026-10-02 11:42:34,175] ERROR in app: Exception on /render-bad [GET]', 'ZeroDivisionError: division by zero', '[2026-10-02 11:42:34 +0800] [53378] [ERROR] Error handling request']
  curl 退出码 18 表示「传输提前结束」(chunked 响应没有正常收尾)。/stream-bad 已经发出的 <ul>、<li>1 等内容留在了客户端,状态码仍是 200,因为状态行在渲染之前就发出去了。

debug 页面

实测 · 本机执行输出
  debug 页面:状态 500;含模板文件名 undef.html: True;含出错的模板源码行 {{ user.name.first }}: True;含 UndefinedError: True

分步演示

模板异常
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动
    • 没有验证:Sentry 等错误监控对模板帧的显示、模板里 {% raw %} 与 {# #} 注释造成的行号偏移、i18n 提取过程的模板语法错误、stream_template 配合 stream_with_context 的更复杂情形
    京ICP备2026067502号-1京公网安备11010502063487号