FastAPI 自定义响应完整指南:HTML、Stream、File 与自定义 Response 类
本篇文章围绕 FastAPI(当前仓库 fastapi)官方文档中的 Custom Response 高级用法展开,系统讲解如何在 JSON 之外返回 HTML、纯文本、重定向、流式数据与文件响应,并实现自定义 Response 子类与全局默认响应类的配置。读完你将掌握 response_class、直接返回 Response、default_response_class 三者之间的取舍与正确姿势,以及在获取最高 JSON 性能时为何应优先使用 Response Model。
文章以 docs/en/docs/advanced/custom-response.md(以及 docs/hi/ 等多语言版本)为骨架,代码示例取自 docs_src/custom_response 目录下的真实可运行源码,实现依据可回溯至 fastapi/responses.py。
FastAPI 默认返回 JSON,如何覆盖?
默认情况下,FastAPI 的 path operation 会返回 JSON 响应。当你想返回其他格式时,官方提供了两种互补的覆盖方式:
- 在函数内部直接返回
Response对象(或其子类,如JSONResponse)。详细说明见 Return a Response directly。 - 在 path operation decorator 中通过
response_class参数声明希望使用的响应类(可以是任意Response子类)。
两种方式的语义差别很重要:
- 如果直接返回
Response(而不声明response_class),返回的数据不会自动转换(即使声明了response_model也不生效),同时不会自动生成文档(例如不会在 OpenAPI 中记录 HTTP headerContent-Type中的具体 media type)。 - 如果通过
response_class声明响应类,那么从 path operation function 返回的内容会被放入该Response实例中,交给 FastAPI 统一处理。
注意:如果使用一个不带 media type 的响应类,FastAPI 会认为你的响应不需要包含内容,因此不会在其生成的 OpenAPI 文档中记录 response 的格式。
JSON 响应:从默认行为到性能最优
默认的 JSON 序列化链路
默认返回 JSON 时,FastAPI 的序列化策略取决于你是否声明了返回类型 / response_model:
- 声明了 Response Model:FastAPI 使用 Pydantic 将数据序列化为 JSON。
- 未声明 response model:FastAPI 使用 JSON Compatible Encoder 中介绍的
jsonable_encoder先把数据转成 JSON 兼容的 Python 对象,再放进JSONResponse中发送。 - 声明了 JSON media type(
application/json)的response_class(如JSONResponse):你返回的数据仍会与 decorator 中声明的 Pydanticresponse_model做自动转换与过滤;但数据不会由 Pydantic 直接序列化为 JSON bytes,而是先经jsonable_encoder转换,再交给JSONResponse类,由 Python 标准库中的 JSON 模块序列化为 bytes。
换句话说,response_model 与 response_class 同时存在时,response_class 会接管"编码"这一步,从而多出一次中间转换。
JSON 性能要点
官方文档给出的结论非常直白:想要最大性能,请使用 Response Model,并且不要在 path operation decorator 中额外声明 response_class。示例见 docs_src/response_model/tutorial001_01_py310.py:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []
@app.post("/items/", response_model=Item)
async def create_item(item: Item) -> Item:
return item
这一性能建议在当前仓库源码中有直接印证:在 fastapi/responses.py 中,UJSONResponse 与 ORJSONResponse 已被标记为 deprecated,其弃用说明明确指出:当设置了 return type 或 response model 时,FastAPI 会通过 Pydantic 直接把数据序列化为 JSON bytes,速度更快且不再需要自定义响应类。
返回 HTML 响应
返回 HTML 最简单的方式是使用 HTMLResponse:
- 从
fastapi.responses导入HTMLResponse; - 在 path operation decorator 中将其传给参数
response_class。
示例见 docs_src/custom_response/tutorial002_py310.py:
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>
"""
注意:
response_class参数同时被用来定义响应的 media type。在本例中,HTTP headerContent-Type会被设置为text/html,并作为响应格式记录在 OpenAPI 中(自动文档会展示正确的 media type)。
直接返回一个 Response
如 Return a Response directly 所述,你也可以在 path operation 内部构造响应并直接返回,从而完全覆盖 FastAPI 的默认行为。上面的例子改写为直接返回 HTMLResponse 如下,见 docs_src/custom_response/tutorial003_py310.py:
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)
警告:由 path operation function 直接返回的
Response不会被记录进 OpenAPI(例如Content-Type不会出现在文档中),也不会在自动交互式文档中可见。说明:当然,实际发出的
Content-Typeheader、status code 等都来自你返回的那个Response对象本身。
同时在 OpenAPI 中记录并覆盖 Response
如果你既想从函数内部返回自定义的 Response 对象,又想让接口文档显示正确的 media type,可以把两种方式结合:同时使用 response_class 参数与返回 Response 对象。
此时 response_class 只负责 OpenAPI path operation 的文档化,而实际发送的仍是你返回的那个 Response。
直接返回 HTMLResponse 的例子
示例见 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(而不是返回 HTML 字符串);read_items() 返回该函数的调用结果,即一个已经构造好的 Response,它会覆盖 FastAPI 的默认行为。与此同时,由于你在 response_class 中传入了 HTMLResponse,FastAPI 知道应把该接口在 OpenAPI 与交互式文档中记录为 media type 为 text/html 的 HTML 响应。
下图即该接口在自动交互式文档中的效果——成功响应(200)的 Content-Type 下拉框已显示为 text/html:
可用的响应类清单
以下是常用响应类的概览。需要注意:Response 基类足够通用,你也可以基于它返回任何内容,甚至创建自定义子类。
技术细节:你完全可以写
from starlette.responses import HTMLResponse。FastAPI 只是出于开发者便利,把与starlette.responses相同的类以fastapi.responses的形式重新导出,绝大多数响应类实际上直接来自 Starlette。这一导出关系见 fastapi/responses.py。
Response:所有响应的基类
主 Response 类,其余所有响应都继承自它,可直接返回。它接受以下参数:
| 参数 | 类型与说明 |
|---|---|
content |
一个 str 或 bytes,作为响应体内容 |
status_code |
一个 int 类型的 HTTP 状态码 |
headers |
由字符串组成的 dict,作为自定义响应头 |
media_type |
一个 str,指明 media type,例如 "text/html" |
FastAPI(底层是 Starlette)会自动加入 Content-Length header;同时基于 media_type 生成 Content-Type header,并对文本类 media type 追加 charset。
直接返回自定义 media type(例如 XML)的典型用法见 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
接收一段文本或 bytes,返回 HTML 响应,即上文所讲的用法。
PlainTextResponse
接收一段文本或 bytes,返回纯文本响应。示例见 docs_src/custom_response/tutorial005_py310.py:
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse
app = FastAPI()
@app.get("/", response_class=PlainTextResponse)
async def main():
return "Hello World"
JSONResponse
接收任意数据,返回以 application/json 编码的响应。这是 FastAPI 中使用的默认响应(如前述 JSON 部分)。
技术细节:如果你声明了 response model 或 return type,FastAPI 会直接用它们把数据序列化为 JSON,并直接返回一个具有正确 JSON media type 的响应,不再经过
JSONResponse类。这是获取最佳性能的理想做法——与文档"JSON Performance"一节及ORJSONResponse/UJSONResponse被弃用的原因完全一致。
RedirectResponse
返回 HTTP 重定向。默认使用 307 状态码(Temporary Redirect)。有三种使用方式:
方式一:直接返回 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 参数中使用,见 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"
这样做之后,你可以直接从 path operation function 返回 URL 字符串。此时实际使用的 status_code 是 RedirectResponse 的默认值 307。
方式三:status_code 与 response_class 参数组合,见 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 generator 或普通 generator/iterator(即含 yield 的函数),将响应体以流式方式发送。示例见 docs_src/custom_response/tutorial007_py310.py:
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())
技术细节:一个
async任务只有在到达await时才能被取消。如果 generator(含yield的函数)内部没有任何await,它无法被正确取消,即使在取消请求发出后仍可能继续运行。上面的小例子本身不需要任何await语句,因此特意加入await anyio.sleep(0),给事件循环一个处理取消的机会。对于大型或无限流,这一点的意义会更加重要。
提示:与其直接返回
StreamingResponse,官方更推荐采用 Stream Data 中的写法——它更便利,并且会在背后帮你处理取消。若你要流式发送 JSON Lines,请参考 Stream JSON Lines 教程。此外,仓库中还提供基于流式传输的EventSourceResponse(SSE),可从 fastapi/sse.py 及 docs_src/server_sent_events 中进一步了解。
FileResponse
以异步方式把一个文件作为响应流式返回。它的构造参数与其它响应类型不同:
| 参数 | 说明 |
|---|---|
path |
要流式传输的文件的文件路径 |
headers |
以字典形式提供的任意自定义响应头 |
media_type |
指明 media type 的字符串;若未设置,会用 filename 或 path 推断 media type |
filename |
若设置,会包含在响应的 Content-Disposition 中 |
文件响应会自动携带合适的 Content-Length、Last-Modified 与 ETag header。
直接返回示例见 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
此时你可以直接从 path operation function 返回文件路径字符串。
自定义响应类(Custom Response Class)
从 Response 继承即可创建自己的响应类。官方示例以接入 orjson 并输出缩进格式化 JSON 为例,核心工作只有一个:实现 Response.render(content) 方法,让它把内容转成 bytes 返回。
完整示例见 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"}
于是响应体从紧凑的一行 JSON:
{"message": "Hello World"}
变为经过缩进与格式化的多行 JSON:
{
"message": "Hello World"
}
需要提醒的是,render() 的自定义绝不仅限于美化 JSON——你可以通过它实现任何自定义的字节级序列化逻辑。
相关事实:当前仓库的 fastapi/responses.py 还保留了内置的
ORJSONResponse(同样继承自JSONResponse,在render()中使用orjson.OPT_NON_STR_KEYS | orjson.OPT_SERIALIZE_NUMPY),但它目前已被标记为弃用,弃用说明建议改用 response model 由 Pydantic 直接序列化。
orjson 还是 Response Model?
如果你追求的是性能,官方建议使用 Response Model 而非 orjson 自定义响应:
- 使用 response model 时,FastAPI 会用 Pydantic 直接把数据序列化为 JSON,没有中间步骤(例如不会像其他情况那样先用
jsonable_encoder转换一次)。 - Pydantic 底层序列化 JSON 所使用的 Rust 机制与
orjson相同,因此使用 response model 本身就已经能得到最优性能。
默认响应类(Default Response Class)
在创建 FastAPI 类实例或 APIRouter 时,可以通过参数 default_response_class 指定全局默认使用的响应类。
下面示例中,app 的所有 path operation 默认都会使用 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>"
提示:全局设置了
default_response_class后,你依然可以在单个 path operation 中用response_class覆盖,优先级为接口级声明高于应用级默认值。同样的机制也适用于通过APIRouter(default_response_class=...)为某个路由分组单独设置默认响应类。
补充:在 OpenAPI 中记录更多响应细节
除了 media type,你还可以用 responses 在 OpenAPI 中声明更丰富的响应细节(如不同状态码对应的响应模型、描述等),详见 Additional Responses in OpenAPI。
小结
围绕 FastAPI 的 Custom Response 用法,本文覆盖了如下决策路径,可以直接作为实战速查:
| 目标 | 推荐做法 |
|---|---|
| 返回 HTML / 纯文本 / 重定向 / 文件 | 在 decorator 中用 response_class 声明对应响应类,让 OpenAPI 正确记录 media type |
| 需要完全自定义响应对象 | 在函数内直接返回 Response(注意不会写入 OpenAPI 文档) |
| 既要自定义对象又要文档化 | 同时使用 response_class 与直接返回 Response,两者各司其职 |
| 流式响应 | 优先参考 Stream Data,而不是手动构造 StreamingResponse |
| 最大 JSON 性能 | 使用 Response Model,不额外声明 response_class |
| 全局统一响应格式 | 在 FastAPI(...) 或 APIRouter(...) 中设置 default_response_class |
| 全新响应格式 | 继承 Response 并实现 render(content) -> bytes |
所有代码示例均为仓库内真实源码,可对照 docs_src/custom_response、docs_src/response_directly 与 docs_src/response_model 目录,结合 uvicorn 本地运行验证。
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 StartedRust0625
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
