FastAPI 响应格式类型全解析:从 JSON 到流式文件
FastAPI 的一个核心设计原则是“选择正确的工具做正确的事”。当你需要返回数据时,它绝不仅限于一种 JSON 格式,而是内置了丰富且开箱即用的响应类型,让开发者能够针对不同场景返回最合适的内容。这篇文章将深入剖析 FastAPI 中各种响应格式类型,包括它们的使用方法、适用场景以及如何灵活地切换和控制这些类型。
1. 默认王者:JSONResponse
FastAPI 的路径操作函数默认返回 JSON 格式。当你直接返回字典、列表或 Pydantic 模型时,FastAPI 会自动将其转换为 JSON 并放入一个JSONResponse对象中。
python
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str price: float @app.get("/item") def get_item(): return {"name": "Widget", "price": 9.99}访问/item得到:
json
{"name": "Widget", "price": 9.99}适用场景:绝大多数 API 接口,前后端分离的数据交互。
你可以通过response_model进一步控制输出字段,但底层响应类型依然是JSONResponse。
2. 声明式切换:response_class参数
每个路径操作装饰器都接受一个response_class参数,用来指定返回的响应类型。这是切换响应格式最直接的方式。
python
from fastapi.responses import HTMLResponse @app.get("/page", response_class=HTMLResponse) def get_page(): return "<h1>Hello World</h1>"一旦指定了response_class,FastAPI 就会把你返回的字符串或生成器作为该类型响应的 body 内容。下面我们依次介绍各种内置响应类。
3. HTML 响应:HTMLResponse
当你需要返回 HTML 页面(例如服务端渲染或简单的静态页面)时,使用HTMLResponse。
python
from fastapi.responses import HTMLResponse @app.get("/index", response_class=HTMLResponse) def index(): html_content = """ <html> <head><title>FastAPI</title></head> <body><h1>Welcome to FastAPI</h1></body> </html> """ return html_content适用场景:返回完整的 HTML 页面、富文本片段,或结合模板引擎(如 Jinja2)渲染后的字符串。
如果直接返回str而不指定HTMLResponse,FastAPI 会将其视为 JSON 字符串(即返回"<h1>...</h1>"带上引号),导致前端无法正确渲染。
4. 纯文本响应:PlainTextResponse
PlainTextResponse用于返回纯文本内容,它设置了Content-Type: text/plain。
python
from fastapi.responses import PlainTextResponse @app.get("/robots.txt", response_class=PlainTextResponse) def robots(): return "User-agent: *\nDisallow: /admin"适用场景:返回配置文件、日志片段、或者任何需要避免浏览器解析 HTML 的文本。
5. 文件下载:FileResponse
FileResponse专为发送文件而设计,它支持异步文件流、断点续传(Range 请求),并可自动检测 MIME 类型。
python
from fastapi.responses import FileResponse @app.get("/download-report") def download_report(): file_path = "/app/reports/annual.pdf" return FileResponse( path=file_path, filename="2024-annual-report.pdf", # 下载时显示的文件名 media_type="application/pdf" # 可选,不指定则自动推断 )优势:内存占用低,直接利用操作系统级别的文件发送能力,尤其适合大文件。
适用场景:文件下载、图片/视频展示、静态资源提供。
6. 流式响应:StreamingResponse
当数据量很大或需实时生成时,使用StreamingResponse可以边生成边发送,避免撑爆服务器内存。
6.1 生成大型 CSV 文件
python
from fastapi.responses import StreamingResponse def iter_csv_rows(): yield "id,name,email\n" for i in range(100000): yield f"{i},user{i},user{i}@example.com\n" @app.get("/export-csv") def export_csv(): return StreamingResponse( iter_csv_rows(), media_type="text/csv", headers={"Content-Disposition": "attachment; filename=users.csv"} )6.2 代理远程视频流
python
import httpx from fastapi.responses import StreamingResponse async def remote_video_stream(): async with httpx.AsyncClient() as client: async with client.stream("GET", "https://example.com/video.mp4") as r: async for chunk in r.aiter_bytes(): yield chunk @app.get("/proxy-video") async def proxy_video(): return StreamingResponse( remote_video_stream(), media_type="video/mp4" )适用场景:实时日志推送、大文件导出、视频/音频流、代理转发等。
注意:如果数据源是同步生成器,FastAPI 会将其放在线程池中运行以避免阻塞事件循环。
7. 重定向:RedirectResponse
RedirectResponse用于实现 URL 跳转,默认状态码 307(临时重定向),也可指定其他 3xx 状态码。
python
from fastapi.responses import RedirectResponse @app.get("/old-url") def old_url(): return RedirectResponse(url="/new-url", status_code=301) @app.get("/new-url") def new_url(): return {"message": "You have been redirected"}适用场景:接口迁移、OAuth 回调、表单提交后的重定向(PRG 模式)。
8. 原始响应:Response 基类
当你需要返回非文本内容(如 JPEG 图片的二进制数据)或自定义 Content-Type 时,可以直接使用Response基类。
python
from fastapi.responses import Response @app.get("/image") def get_image(): with open("logo.png", "rb") as f: image_bytes = f.read() return Response(content=image_bytes, media_type="image/png")你也可以用它返回 XML 或任何自定义格式的数据:
python
@app.get("/data.xml", response_class=Response) def get_xml(): xml = """<?xml version="1.0"?><data><id>1</id></data>""" return Response(content=xml, media_type="application/xml")9. 自定义响应类:封装与扩展
所有内置响应类都继承自starlette.responses.Response,你可以轻松扩展它们来实现统一响应结构或修改序列化行为。
9.1 统一 API 响应格式
很多团队喜欢用{"code": 0, "msg": "success", "data": ...}包裹所有返回数据,我们可以通过自定义JSONResponse实现:
python
from fastapi.responses import JSONResponse from typing import Any class ApiResponse(JSONResponse): def render(self, content: Any) -> bytes: # 避免双重包裹 if isinstance(content, dict) and "code" in content: return super().render(content) wrapped = { "code": self.status_code if self.status_code < 400 else -1, "msg": "success" if self.status_code < 400 else "error", "data": content, } return super().render(wrapped) # 设为全局默认响应类 app = FastAPI(default_response_class=ApiResponse) @app.get("/item") def get_item(): return {"name": "Widget"} # 自动包裹为统一格式9.2 自定义 JSON 序列化
如果你需要全局更改datetime的格式,或让 JSON 支持更多自定义类型,可以重写render方法:
python
import json from datetime import datetime class CustomJSONResponse(JSONResponse): def render(self, content: Any) -> bytes: return json.dumps( content, ensure_ascii=False, indent=2, default=str # 所有不可序列化对象转为 str ).encode("utf-8")然后通过response_class=CustomJSONResponse或在应用层面设置。
10. 总结与选择指南
FastAPI 提供了从常见到极客的响应格式类型,选择它们的原则很简单:
| 场景 | 响应类 |
|---|---|
| 返回 JSON 数据(API 主体) | JSONResponse(默认) |
| 返回 HTML 页面 | HTMLResponse |
| 返回纯文本 | PlainTextResponse |
| 文件下载(支持断点续传) | FileResponse |
| 流式传输大数据或实时数据 | StreamingResponse |
| URL 重定向 | RedirectResponse |
| 返回二进制或非标准格式 | Response基类 |
| 自定义序列化或统一数据结构 | 继承JSONResponse |
你可以通过response_class静态声明,也可以直接返回对应响应类的实例,后者还允许在函数内动态设置状态码、Header 等。这种灵活性使得 FastAPI 在处理各种响应格式时游刃有余。
掌握这些响应格式类型后,你就能在 REST API、微服务、文件服务甚至实时数据推送等场景中游刃有余,彻底告别“只能返回 JSON”的尴尬,写出既专业又高性能的 Web 接口。