FastAPI 自定义响应详解:用 response_class 返回 HTML、流式、文件等响应并控制 OpenAPI 文档
FastAPI 默认返回 JSON,但真实项目中常常需要返回 HTML 页面、纯文本、文件、重定向或流式数据。本文基于 FastAPI 官方文档《Custom Response》(docs/en/docs/advanced/custom-response.md),系统讲解如何通过 response_class、default_response_class 以及直接返回 Response 对象这三种方式自定义响应类型,并结合源码说明 OpenAPI 文档中 Content-Type 媒体类型的生成机制、各内置响应类的参数与适用场景,以及自定义响应类的实现原理。读完本文,你可以为任意接口精确控制响应的媒体类型、状态码与文档呈现。
JSON 响应:默认行为与序列化路径
FastAPI 默认返回 JSON 响应。不同声明方式下,数据序列化的路径不同,这直接影响性能:
- 声明了 Response Model(即返回类型注解或
response_model):FastAPI 使用 Pydantic 将数据序列化为 JSON 字节,无需中间转换步骤,这是性能最优的方式。 - 未声明 response model:FastAPI 使用
jsonable_encoder(见 JSON Compatible Encoder)转换数据后放入JSONResponse。 - 显式声明 JSON 媒体类型的
response_class(如JSONResponse):返回数据仍会经过 Pydanticresponse_model的转换与过滤,但不会用 Pydantic 序列化成 JSON 字节,而是先经jsonable_encoder转换,再交给JSONResponse用 Python 标准 JSON 库序列化为字节。
JSON 性能结论(原文档核心建议):想要最高性能,声明 Response Model 且不要在路径操作装饰器中声明 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/")
async def create_item(item: Item) -> Item:
return item
@app.get("/items/")
async def read_items() -> list[Item]:
return [
Item(name="Portal Gun", price=42.0),
Item(name="Plumbus", price=32.0),
]
从源码结构看,默认的响应类就是 JSONResponse:在 fastapi/routing.py 中,路由注册方法的签名是 response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse)(L379),若传入 DefaultPlaceholder 则取其 .value 作为实际响应类(L396-L399),这解释了为什么"不声明 response_class"时行为与 JSON 媒体类型完全一致。
返回 HTML:用 response_class 声明 HTMLResponse
要让 FastAPI 直接返回 HTML,使用 HTMLResponse,只需两步:导入它,并作为 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 不仅决定响应的实际封装,还决定了响应"媒体类型":上例中 HTTP 头 Content-Type 会被设为 text/html,并且 OpenAPI 文档会按此记录。
关于媒体类型的技术细节:如果使用了没有媒体类型(media_type 为 None)的响应类,FastAPI 会认为该响应无内容,因而在生成的 OpenAPI 文档中不记录响应格式。
从源码看,OpenAPI 生成时正是直接取响应类的 media_type 属性:fastapi/openapi/utils.py 中先解包 DefaultPlaceholder 得到 current_response_class,再执行 route_response_media_type: str | None = current_response_class.media_type,随后该媒体类型被写入 OpenAPI 的 responses 结构(L456-L502 区域)。这解释了为什么 response_class=HTMLResponse 能让 Swagger UI 的 "Try it out" 显示 text/html。
方式二:直接返回一个 Response 对象
如 Return a Response directly 所述,你也可以不声明 response_class,而在路径操作函数中直接返回 Response 实例:
# 摘自 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)
注意(原文档 warning):路径操作函数直接返回的 Response 不会被记录到 OpenAPI 中(例如 Content-Type 不会被文档化),在自动生成的交互式文档中也不会可见。当然,实际的 Content-Type 头、状态码等仍来自你返回的 Response 对象本身。
方式三:既文档化 OpenAPI,又在函数内覆盖 Response
如果希望在函数内部覆盖响应,同时又在 OpenAPI 中文档化媒体类型,可以同时使用 response_class 参数并返回 Response 对象:response_class 只用于文档化 OpenAPI 路径操作,而你的 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()
这里 read_items() 返回的是 generate_html_response() 的结果,本身就是一个会覆盖 FastAPI 默认行为的 Response;但因为同时传了 response_class=HTMLResponse,FastAPI 知道如何在 OpenAPI 与交互式文档中把它记录为 text/html 的 HTML 响应:
可用响应类一览:Response、HTMLResponse、PlainTextResponse、JSONResponse
fastapi.responses 提供的响应类大多直接来自 Starlette——fastapi/responses.py 中可以看到 FileResponse、HTMLResponse、JSONResponse、PlainTextResponse、RedirectResponse、Response、StreamingResponse 都是从 starlette.responses 重新导出的,因此你也可以写 from starlette.responses import HTMLResponse,效果相同。
Response
所有响应类的基类,可直接返回。构造参数:
content-str或bytes;status_code-int类型 HTTP 状态码;headers- 字符串字典;media_type- 媒体类型字符串,如"text/html"。
FastAPI(实际由 Starlette 完成)会自动附加 Content-Length 头,并根据 media_type 附加 Content-Type 头(文本类型会自动追加 charset)。示例:
# 摘自 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 / PlainTextResponse
HTMLResponse 接收文本或字节返回 HTML 响应(上文已演示);PlainTextResponse 接收文本或字节返回纯文本响应:
# 摘自 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 的默认响应。
技术细节:如果你声明了 response model 或返回类型注解,FastAPI 会直接用它把数据序列化为 JSON 并返回带正确 JSON 媒体类型的响应,而不经过 JSONResponse 类。这就是获得最佳性能的理想方式。
RedirectResponse:HTTP 重定向
RedirectResponse 返回 HTTP 重定向,默认使用 307(Temporary Redirect)状态码。
用法一:直接返回:
# 摘自 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 字符串:
# 摘自 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 是 RedirectResponse 的默认值 307。
用法三:response_class 与 status_code 参数组合,自定义重定向状态码:
# 摘自 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:注意取消机制
StreamingResponse 接收一个 async 生成器或普通生成器/迭代器(带 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 时才能被取消。如果生成器里没有 await,它无法被正确取消,可能在取消请求后继续运行。上例中 await anyio.sleep(0) 就是为了让事件循环有机会处理取消信号;对于大型或无限流这一点尤其重要。
建议:与其直接返回 StreamingResponse,更推荐采用 Stream Data 教程的风格,它更方便且会在后台替你处理取消逻辑;如果流式传输 JSON Lines,可参考 Stream JSON Lines 教程。
文件响应 FileResponse:异步流式传输文件
FileResponse 将文件异步流式传输为响应。它的构造参数与其他响应类不同:
path- 要流式传输的文件路径;headers- 要包含的自定义头字典;media_type- 媒体类型字符串;若未设置,会根据文件名或路径推断;filename- 若设置,会包含在响应Content-Disposition头中。
文件响应会自动携带合适的 Content-Length、Last-Modified 和 ETag 头。
用法一:直接返回:
# 摘自 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
自定义响应类:重写 render() 控制序列化
你可以继承 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"
}
从源码印证这一模式:fastapi/responses.py 中的 UJSONResponse 与 ORJSONResponse 正是通过重写 render(self, content: Any) -> bytes 来切换序列化器的(L64-L66、L94-L98)。值得注意的是,这两个类在当前版本中已被标记为 @deprecated(L39-L47、L69-L77)——官方弃用理由与本节结论一致:当设置了返回类型或 response model 时,FastAPI 现在直接通过 Pydantic 把数据序列化为 JSON 字节,更快且无需自定义响应类。
追求性能:Response Model 优先于 orjson
如果你追求性能,用 Response Model 通常比 orjson 响应类更好。使用 response model 时,FastAPI 会用 Pydantic 直接把数据序列化为 JSON,不经过 jsonable_encoder 等中间步骤;而 Pydantic 底层使用的 Rust 序列化机制与 orjson 相同,因此 response model 已经能带来最佳性能。
默认响应类 default_response_class
创建 FastAPI 实例或 APIRouter 时,可以用 default_response_class 参数指定默认响应类(参数定义见 fastapi/applications.py,默认值为 Default(JSONResponse))。例如让所有路径操作默认使用 HTMLResponse:
# 摘自 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>"
在单个路径操作上声明的 response_class 仍然会覆盖这个默认值,两者优先级从高到低为:直接返回的 Response 对象 > 路由级 response_class > 应用/路由器级 default_response_class。
补充:在 OpenAPI 中声明更多响应细节
除了媒体类型,你还可以用 responses 参数在 OpenAPI 中声明额外的状态码与响应格式,详见 Additional Responses in OpenAPI。
小结
- 需要自定义响应类型时,优先在路径操作装饰器传
response_class(如HTMLResponse、PlainTextResponse、RedirectResponse、FileResponse),它同时控制实际响应与 OpenAPI 中的Content-Type文档; - 需要在函数内部完全控制响应时,直接返回
Response实例,但会牺牲 OpenAPI 文档化;两者兼得的做法是同时传response_class并返回Response; - 追求最高 JSON 性能时,声明 response model 或不声明
response_class,让 Pydantic 直接序列化,不必借助JSONResponse或自定义 orjson 响应类; - 应用级统一响应类型用
FastAPI(default_response_class=...); - 所有可用响应类都来自 Starlette 并经由
fastapi.responses再导出,自定义响应类只需实现render(content) -> bytes即可接入整套机制。
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
