bigywater.tech
Flask · 41

自定义 Response Class

想让所有响应都带一个头、让视图直接返回 dataclass 或 Pydantic 对象,该改哪里?自定义 Response 类能做到什么、做不到什么?这篇踩一遍坑,再给出能用的写法。

500
自定义 force_type 后视图返回模型对象仍然得到的状态码
2
真正起作用的扩展点:Response 子类(统一加头)与 Flask.make_response(处理模型对象)
X-Api-Version
本篇统一加到所有响应(含 404)的头

不加自定义类

实测 · 本机执行输出
  默认 response_class,视图返回 User 对象 → 500
  make_response 的报错: The view function did not return a valid response. The return type must be a string, dict, list, tuple with headers or status, Response instance, or WSGI callable, but it was a User.

上一篇:JSON Response 的序列化;下一篇:Streaming Response。make_response 的返回值规则见make_response() 的响应转换过程。

Response 子类

实测 · 本机执行输出
  源码:
      1 | class ApiResponse(Response):
      2 |     default_mimetype = "application/json"
      3 |     @classmethod
      4 |     def force_type(cls, response, environ=None):
      5 |         if isinstance(response, (User, Pyd)): ...  # 转成 JSON 响应
      6 |         return super().force_type(response, environ)
      7 |     def __init__(self, *a, **kw): super().__init__(*a, **kw); self.headers["X-Api-Version"] = "1"
      8 | app.response_class = ApiResponse
  GET /user     → 500 text/html; charset=utf-8 | X-Api-Version=1 X-T=None | '<!doctype html>\n<html lang=en>\n<title>500 Internal Server Er'
  GET /pyd      → 500 text/html; charset=utf-8 | X-Api-Version=1 X-T=None | '<!doctype html>\n<html lang=en>\n<title>500 Internal Server Er'
  GET /str      → 200 application/json | X-Api-Version=1 X-T=None | 'plain string'
  GET /dict     → 200 application/json | X-Api-Version=1 X-T=None | '{"k":"v"}'
  GET /tuple    → 500 text/html; charset=utf-8 | X-Api-Version=1 X-T=None | '<!doctype html>\n<html lang=en>\n<title>500 Internal Server Er'
  GET /missing  → 404 text/html; charset=utf-8 | X-Api-Version=1 X-T=None | '<!doctype html>\n<html lang=en>\n<title>404 Not Found</title>\n'

错误页

实测 · 本机执行输出
  500 页: 500 text/html; charset=utf-8 1

force_type 的调用时机

实测 · 本机执行输出
  返回 str                → 200 | force_type 被调用:[]
  返回 dict               → 200 | force_type 被调用:[]
  返回 Response           → 200 | force_type 被调用:['Response']
  返回 werkzeug Response  → 200 | force_type 被调用:['Response']
  返回 WSGI callable      → 200 | force_type 被调用:['function']

重写 make_response

实测 · 本机执行输出
  GET /user   → 200 application/json | X-T=None | '{"id":1,"name":"\\u5f20\\u4e09"}'
  GET /pyd    → 200 application/json | X-T=None | '{"a":1}'
  GET /tuple  → 201 application/json | X-T=1 | '{"id":2,"name":"b"}'
  GET /str    → 200 text/html; charset=utf-8 | X-T=None | 'plain'

default_mimetype

实测 · 本机执行输出
  Csv("a,b")                                               → text/csv; charset=utf-8
  Csv("a,b", mimetype="text/plain")                        → text/plain; charset=utf-8
  Csv("a,b", content_type="text/tab-separated-values")     → text/tab-separated-values

分步演示

自定义 Response
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动
    • 没有验证:Pydantic 真实对象(用的是模仿 model_dump 的简单类)、重写 make_response 对蓝图与异步视图的影响、第三方扩展(如 Flask-RESTX)与自定义 response_class 的冲突
    京ICP备2026067502号-1京公网安备11010502063487号