FastAPI 自定义响应(Custom Response)实战指南:HTML 页面、流式传输、文件下载与自定义 Response 类
本指南围绕 FastAPI 官方文档中「自定义响应」章节(对应仓库中的 docs/es/docs/advanced/custom-response.md)展开,系统讲解如何在默认 JSON 之外按需返回 HTML、纯文本、重定向、流式数据、文件下载等任意响应类型,以及两种自定义路径(直接返回 Response 与使用 response_class)的区别与组合用法。读完你不仅能在路由中灵活装配各类响应,还能基于 Response 基类自建响应类(例如格式化 JSON 的 orjson 响应),并掌握声明 default_response_class 让整套应用默认返回非 JSON 内容的方法。
两种覆盖默认 JSON 的方式:直接返回 Response 与 response_class
FastAPI 默认会把一切返回值包装成 JSON response。你可以在 path operation function 中直接 return 一个 Response 实例来覆盖它(参见 直接返回 Response),也可以在 path operation decorator 中使用 response_class 参数声明想要使用的 Response 子类,此时函数体内返回的内容会被装入该 Response 中。
两者存在重要区别:如果在函数内部直接返回 Response(或任意子类如 JSONResponse),那么——
- 数据不会被自动转换(即使你声明了
response_model也一样); - OpenAPI 文档不会自动生成对应信息(例如不会在生成的 OpenAPI 中记录 HTTP 头
Content-Type的具体 media type)。
因此,若希望返回内容被自动处理、并在交互式文档中正确展示 media type,应优先在 decorator 上声明 response_class。
[!NOTE] 如果使用的是没有 media type 的 response 类,FastAPI 会认为该响应没有实体内容,因此不会在生成的 OpenAPI 中记录响应的格式。
JSON responses 与性能取舍
默认情况下 FastAPI 返回 JSON:
- 若声明了 Response Model,FastAPI 会用 Pydantic 将数据序列化为 JSON;
- 若未声明 response model,FastAPI 会使用 JSON 兼容编码器(
jsonable_encoder)转换数据后放入JSONResponse。
当你在 decorator 上声明了 media type 为 application/json 的 response_class(例如 JSONResponse)时,返回值会先经 response_model(Pydantic 模型)转换并过滤字段,但不会直接用 Pydantic 序列化成 JSON 字节,而是先经 jsonable_encoder 转换,再交给 JSONResponse 用 Python 标准库 json 序列化为字节——存在一次中间转换步骤。
[!IMPORTANT] 追求最大性能的做法:声明 Response Model(或返回类型注解),同时在 decorator 上不要显式传
response_class。这样 FastAPI 会让 Pydantic 直接生成 JSON 字节,省去jsonable_encoder与二次序列化。代码示例如下(源文件 docs_src/response_model/tutorial001_01_py310.py):
@app.post("/items/")
async def create_item(item: Item) -> Item:
return item
从源码层面看,FastAPI 的路由默认 response_class 就是 JSONResponse(见 fastapi/routing.py 的 response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse)),而当你声明了返回类型时,FastAPI 会走“Pydantic 直接序列化字节”的快路径,这正是官方文档建议不要显式覆盖 JSON 响应类的原因。
返回 HTML:HTMLResponse
想要从 FastAPI 直接返回 HTML,使用 HTMLResponse 即可,只需两步:
- 导入
HTMLResponse; - 在 path operation decorator 中把它传给
response_class。
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/items/", response_class=HTMLResponse)
async def read_items():
return """
<html>
<head>
<title>Some HTML in here</title>
</head>
<body>
<h1>Look ma! HTML!</h1>
</body>
</html>
"""
[!NOTE]
response_class还会被用来决定响应的 media type。在上例中,HTTP 头Content-Type会被设为text/html,并会这样记录到 OpenAPI 中。
方式一:在函数内直接返回 Response
参照 直接返回 Response 的写法,也可以在 path operation function 里直接返回一个 HTMLResponse 实例来覆盖默认行为:
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/items/")
async def read_items():
html_content = """
<html>
<head>
<title>Some HTML in here</title>
</head>
<body>
<h1>Look ma! HTML!</h1>
</body>
</html>
"""
return HTMLResponse(content=html_content, status_code=200)
[!WARNING] 直接返回的
Response不会被记录进 OpenAPI(例如Content-Type不会被记录),在自动交互式文档中也不可见。
[!NOTE] 当然,真实响应中
Content-Type头、状态码等都以你返回的那个Response对象为准。
方式二:既记录 OpenAPI,又在函数内覆盖 Response
如果想既在函数内部自由构造并返回 Response,又能在 OpenAPI 中正确声明 media type,可以同时使用 response_class 参数并返回 Response 对象:response_class 仅用于 OpenAPI 文档生成,实际生效的仍然是你返回的那个 Response。完整示例见 docs_src/custom_response/tutorial004_py310.py:
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
def generate_html_response():
html_content = """
<html>
<head>
<title>Some HTML in here</title>
</head>
<body>
<h1>Look ma! HTML!</h1>
</body>
</html>
"""
return HTMLResponse(content=html_content, status_code=200)
@app.get("/items/", response_class=HTMLResponse)
async def read_items():
return generate_html_response()
这个例子里,generate_html_response() 已经帮你构造好并返回了一个 Response。由于 response_class=HTMLResponse 也同时传给了 decorator,FastAPI 会知道如何在 OpenAPI 中把它记录为 text/html 的 HTML 交互式文档——在 Swagger UI 的 /items/ 接口下,200 响应的 media type 会显示为 text/html:
常用 Response 一览
下面列出 FastAPI 内置且最常用的 response 类。注意:你可以直接用 Response 返回任意内容,甚至创建自定义子类。
[!NOTE] 也可以使用
from starlette.responses import HTMLResponse。FastAPI 把starlette.responses里的类以fastapi.responses的名字重新导出,纯粹是为了开发者方便;这些 response 类绝大部分源自 Starlette。仓库中的 fastapi/responses.py 即通过from starlette.responses import ... as ...逐类重导出(此外还额外导出了 FastAPI 的EventSourceResponse)。
Response(基类)
主类 Response,其余所有 response 类都继承自它,也可以直接返回。它接受的参数:
content——str或bytes;status_code—— HTTP 状态码int;headers—— 字符串键值对组成的dict;media_type—— 描述 media type 的str,例如"text/html"。
FastAPI(实际上由 Starlette 完成)会自动补上 Content-Length 头;也会依据 media_type 生成 Content-Type 头,并对文本类 media type 追加字符集。下面的 XML 示例可见,通过自定义 media_type 即可返回任意格式(完整代码见 docs_src/response_directly/tutorial002_py310.py):
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/legacy/")
def get_legacy_data():
data = """<?xml version="1.0"?>
<shampoo>
<Header>
Apply shampoo here.
</Header>
<Body>
You'll have to use soap here.
</Body>
</shampoo>
"""
return Response(content=data, media_type="application/xml")
HTMLResponse
接收文本或字节并返回 HTML 响应,用法见上文。
PlainTextResponse
接收文本或字节并返回纯文本响应:
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse
app = FastAPI()
@app.get("/", response_class=PlainTextResponse)
async def main():
return "Hello World"
(对应仓库示例 docs_src/custom_response/tutorial005_py310.py。)
JSONResponse
接收数据并返回编码为 application/json 的响应——正是 FastAPI 的默认响应类。但注意:若声明了 response model 或返回类型注解,FastAPI 会直接用其把数据序列化成 JSON 字节并返回正确 media type 的响应,不再经过 JSONResponse 类;这也正是获取最佳性能的理想方式。
RedirectResponse
返回 HTTP 重定向,默认状态码为 307(临时重定向)。三种使用方式如下:
① 直接返回 RedirectResponse(docs_src/custom_response/tutorial006_py310.py):
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/typer")
async def redirect_typer():
return RedirectResponse("https://typer.tiangolo.com")
② 作为 response_class 使用,这样函数内直接返回目标 URL 字符串即可,状态码取 RedirectResponse 的默认值 307(docs_src/custom_response/tutorial006b_py310.py):
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/fastapi", response_class=RedirectResponse)
async def redirect_fastapi():
return "https://fastapi.tiangolo.com"
③ 结合 status_code 修改重定向状态码(如改为 302,见 docs_src/custom_response/tutorial006c_py310.py):
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/pydantic", response_class=RedirectResponse, status_code=302)
async def redirect_pydantic():
return "https://docs.pydantic.dev/"
StreamingResponse
接收一个 async 生成器,或普通的生成器/迭代器(带 yield 的函数),并将响应体流式传输给客户端:
import anyio
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
async def fake_video_streamer():
for i in range(10):
yield b"some fake video bytes"
await anyio.sleep(0)
@app.get("/")
async def main():
return StreamingResponse(fake_video_streamer())
(完整代码见 docs_src/custom_response/tutorial007_py310.py。)
[!NOTE] 一个
async任务只有执行到await时才能被取消。如果协程里没有任何await,该生成器(带yield的函数)无法被正确取消,可能在客户端请求取消后仍继续运行。上面的小例子本身不需要任何await语句,因此特意加了await anyio.sleep(0)把控制权交还给事件循环以处理取消;对大型甚至无限流,这一点更加重要。
[!TIP] 相比直接返回
StreamingResponse,更推荐 流式数据(Stream Data) 一章的风格,它更便捷且由 FastAPI 在后台替你处理取消。若传输的是 JSON Lines,请参照 流式传输 JSON Lines(Stream JSON Lines) 教程。
FileResponse
以异步方式把文件流式传输给客户端。相比其他 response 类,它接收一组不同的构造参数:
path—— 待传输文件的路径;headers—— 需要附带的自定义头(字典形式);media_type—— media type 字符串;若未设置,会依据文件名或路径推断;filename—— 若设置,会包含在响应的Content-Disposition中。
文件响应会自动带上合适的 Content-Length、Last-Modified 与 ETag 头。
① 直接返回 FileResponse(docs_src/custom_response/tutorial009_py310.py):
from fastapi import FastAPI
from fastapi.responses import FileResponse
some_file_path = "large-video-file.mp4"
app = FastAPI()
@app.get("/")
async def main():
return FileResponse(some_file_path)
② 作为 response_class 使用,此时函数内直接返回文件路径字符串即可(docs_src/custom_response/tutorial009b_py310.py):
from fastapi import FastAPI
from fastapi.responses import FileResponse
some_file_path = "large-video-file.mp4"
app = FastAPI()
@app.get("/", response_class=FileResponse)
async def main():
return some_file_path
自定义 Response 类
你可以继承 Response 创建自己的 response 类再使用。核心只需要覆写 render(content) 方法,让它返回 bytes。
例如希望使用 orjson 并启用其 orjson.OPT_INDENT_2 选项,输出带缩进、格式化后的 JSON,就可以这样写(完整代码见 docs_src/custom_response/tutorial009c_py310.py):
from typing import Any
import orjson
from fastapi import FastAPI, Response
app = FastAPI()
class CustomORJSONResponse(Response):
media_type = "application/json"
def render(self, content: Any) -> bytes:
assert orjson is not None, "orjson must be installed"
return orjson.dumps(content, option=orjson.OPT_INDENT_2)
@app.get("/", response_class=CustomORJSONResponse)
async def main():
return {"message": "Hello World"}
此时返回结果不再是紧凑的 {"message": "Hello World"},而是:
{
"message": "Hello World"
}
[!TIP] 仓库本身的历史响应类可作参考:在 fastapi/responses.py 中,
UJSONResponse与ORJSONResponse都是通过覆写render()返回字节实现的自定义 JSON 响应类——不过它们目前已标记为弃用(触发FastAPIDeprecationWarning),因为新版本在声明返回类型时已能由 Pydantic 直接完成高性能序列化,不再需要这些类。对应的单元测试可参见仓库的 tests/test_orjson_response_class.py。
orjson 还是 Response Model?
如果你的目标是性能,与其写一个 orjson 自定义响应,不如直接使用 Response Model:声明 response model 后,FastAPI 会让 Pydantic 直接把数据序列化为 JSON 字节,省去中间用 jsonable_encoder 转换后再交给其他序列化器的步骤。并且 Pydantic 底层与 orjson 使用相同的 Rust 序列化机制,用 response model 已经能获得最佳性能。
应用级默认响应类:default_response_class
创建 FastAPI 实例或 APIRouter 时,可以用 default_response_class 参数指定全局默认 response 类。例如下面的代码会让该应用所有 path operations 默认使用 HTMLResponse 而不是 JSON(docs_src/custom_response/tutorial010_py310.py):
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI(default_response_class=HTMLResponse)
@app.get("/items/")
async def read_items():
return "<h1>Items</h1><p>This is a list of items.</p>"
[!TIP] 即便设置了
default_response_class,你依然可以在单个 path operation 中用response_class覆盖它。
该参数在路由层由 APIRoute/APIRouter 逐级继承:从源码看,路由解析时会用 get_value_or_default(...) 在「路由显式声明 → 父 Router 的 default_response_class → 默认 JSONResponse」之间取值(见 fastapi/routing.py)。仓库中对应行为亦有专门测试覆盖,例如 tests/test_default_response_class.py 与 tests/test_default_response_class_router.py。
扩展阅读
- 在 OpenAPI 中附加响应声明(Additional Responses):可进一步声明 OpenAPI 中 media type 与其他响应细节(
responses参数); - 直接返回 Response:在不使用
response_class时如何直接返回任意Response; - Response Model 与 JSON 兼容编码器:理解默认 JSON 序列化的完整链路;
- 流式数据 与 流式 JSON Lines:以更优雅的方式处理大体积或持续输出的响应;
- 本指南对应的原文文档:docs/es/docs/advanced/custom-response.md(另有同主题英文版位于 docs/en/docs 的对应章节),全部可运行示例集中在 docs_src/custom_response/ 目录下,可直接对照源码验证。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
