在 FastAPI 中,路由函数返回 dict 或 Pydantic 模型时,框架会自动将其序列化为 JSON 并包装成 200 响应。但在真实项目中,你还需要返回 HTML 页面、纯文本、文件下载、SSE 流、重定向等多种格式。FastAPI 从 Starlette 继承了丰富的响应类,让这些场景变得非常简单。

为什么需要自定义响应类

默认情况下,FastAPI 按以下规则处理返回值:

返回值类型 自动行为
dict / list JSONResponse
Pydantic 模型 JSONResponse(经 response_model 过滤)
str PlainTextResponse(直接返回字符串时)
Response 子类实例 → 直接使用,不做转换

当你需要控制 Content-Type、添加自定义响应头、设定特定状态码,或者返回非 JSON 格式时,就需要显式返回一个 Response 子类实例。

JSONResponse:控制 JSON 响应的细节

JSONResponse 是 FastAPI 中最常用的响应类,但除了默认行为,你还可以直接实例化它来精确控制响应头和状态码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()


@app.get("/custom-json/")
async def custom_json():
data = {"message": "操作成功", "code": 0}
return JSONResponse(
content=data,
status_code=201,
headers={"X-Custom-Header": "my-value", "X-Request-Id": "abc-123"},
)

这种方式最适合在错误处理或特定业务逻辑中返回非 200 的 JSON 响应,或附加追踪类响应头。

HTMLResponse:返回 HTML 页面

使用 HTMLResponse 可以直接返回一段 HTML 内容,默认 Content-Type 为 text/html

1
2
3
4
5
6
7
8
9
from fastapi.responses import HTMLResponse


@app.get("/welcome/", response_class=HTMLResponse)
async def welcome():
return """
<h1>你好,FastAPI!</h1>
<p>这是一个由 <code>HTMLResponse</code> 渲染的页面。</p>
"""

这里用到了 response_class 参数——它告诉 FastAPI 用 HTMLResponse 来包装视图的字符串返回值。

对于动态模板,推荐搭配 Jinja2 使用:

1
2
3
4
5
6
7
8
9
from fastapi.templating import Jinja2Templates
from fastapi import Request

templates = Jinja2Templates(directory="templates")


@app.get("/page/{name}")
async def page(request: Request, name: str):
return templates.TemplateResponse("page.html", {"request": request, "name": name})

PlainTextResponse:纯文本响应

当你需要返回纯文本(如 robots.txt、日志输出、纯文本 API)时,使用 PlainTextResponse

1
2
3
4
5
6
from fastapi.responses import PlainTextResponse


@app.get("/robots.txt", response_class=PlainTextResponse)
async def robots():
return "User-agent: *\nDisallow: /admin/\nAllow: /"

RedirectResponse:重定向

RedirectResponse 用于将客户端重定向到另一个 URL。默认返回 307 Temporary Redirect

1
2
3
4
5
6
7
8
9
10
11
12
from fastapi.responses import RedirectResponse


@app.get("/old-page/")
async def old_page():
return RedirectResponse(url="/new-page/")


@app.get("/gone/")
async def gone():
# 301 永久重定向,告诉搜索引擎资源已迁移
return RedirectResponse(url="/new-location/", status_code=301)

StreamingResponse:流式响应

当你需要返回大文件、生成实时数据流或实现 Server-Sent Events(SSE)时,StreamingResponse 是最佳选择。它接受一个生成器作为 content,边生成边发送,不会一次性加载全部内容到内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from fastapi.responses import StreamingResponse
import asyncio


async def generate_large_data():
"""模拟分块生成数据(如大日志文件、实时行情)"""
for i in range(1, 101):
yield f"data: 第 {i} 行数据\n\n"
await asyncio.sleep(0.05) # 模拟数据处理延迟


@app.get("/stream-data/")
async def stream_data():
return StreamingResponse(
generate_large_data(),
media_type="text/plain",
headers={"X-Stream": "chunked"},
)

SSE 事件流的典型写法:

1
2
3
4
5
6
7
8
9
10
11
12
@app.get("/sse/")
async def sse_events():
async def event_generator():
while True:
yield f"data: {asyncio.get_event_loop().time()}\n\n"
await asyncio.sleep(1)

return StreamingResponse(
event_generator(),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache"},
)

FileResponse:文件下载

FileResponse 专为文件下载设计,支持断点续传(Range 请求)、自动设置 Content-LengthContent-Type

1
2
3
4
5
6
7
8
9
10
11
12
from fastapi.responses import FileResponse


@app.get("/download/report/")
async def download_report():
file_path = "/data/reports/annual_report.pdf"
return FileResponse(
path=file_path,
filename="年度报告.pdf", # 下载时显示的文件名
media_type="application/pdf", # 可选,自动推断
headers={"X-File-Version": "v2"},
)

FileResponse 会自动处理 HTTP Range 请求头。客户端中断下载后可以续传,非常适合大文件场景。

自定义响应类

如果内置响应类不满足需求,你可以继承 Response 类来实现自定义格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from fastapi.responses import Response
import json


class XMLResponse(Response):
"""返回 XML 格式的响应类"""
media_type = "application/xml"

def render(self, content: dict) -> bytes:
# 将字典简单序列化为 XML
xml_parts = ['<?xml version="1.0" encoding="UTF-8"?>', '<response>']
for key, value in content.items():
xml_parts.append(f"<{key}>{value}</{key}>")
xml_parts.append("</response>")
return "\n".join(xml_parts).encode("utf-8")


@app.get("/data.xml")
async def xml_data():
data = {"user": "Alice", "status": "active"}
return XMLResponse(
content=data,
status_code=200,
)

自定义响应类的核心是覆盖 render() 方法——它接收 content(即你传入的任意数据),返回编码后的 bytes。FastAPI 内部调用 render() 来生成最终的响应体。

你还可以通过 media_type 类属性设置默认 Content-Type,或在实例化时通过 media_type= 参数覆盖。

与 response_model 的区别

初学者容易混淆 response_model 和自定义 Response 类,这里划清界限:

特性 response_model 自定义 Response
数据来源 视图返回值(dict/模型/ORM 对象) 手动构造的 Response 实例
序列化 FastAPI 自动序列化并过滤 你在视图内显式构造
校验 ✅ 按 Pydantic 模型校验 ❌ 不经过模型校验
文档 ✅ 自动生成 OpenAPI schema ❌ 不会生成请求/响应 schema
适用场景 JSON API 的数据安全过滤 非 JSON 响应、流式数据、文件下载

简单说:JSON API 优先用 response_model;需要非 JSON 格式或特殊响应行为时,用自定义 Response

小结

FastAPI 的响应类体系简洁而强大:

  1. **JSONResponse**:在需要自定义状态码或响应头时直接实例化。
  2. **HTMLResponse**:配合 Jinja2 模板引擎返回动态页面。
  3. **PlainTextResponse**:返回纯文本内容,如 robots.txt 或日志。
  4. **RedirectResponse**:实现 301/307 等重定向。
  5. **StreamingResponse**:处理大文件流、SSE 事件流等边生成边发送的场景。
  6. **FileResponse**:高效的文件下载响应,自动支持断点续传。
  7. **自定义 Response**:继承 Response 并实现 render(),应对任意输出格式。

掌握了这些响应类,你就能从容应对 Web 开发中绝大部分的输出需求。