响应模型与状态码
输入模型和输出模型可以不同
用户注册时会发送密码,但接口响应不应返回密码或密码哈希。分别定义输入、输出模型,可以清楚地表达这件事。
完整示例:
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class UserCreate(BaseModel):
username: str = Field(min_length=3)
password: str = Field(min_length=8)
class UserPublic(BaseModel):
id: int
username: str
@app.post("/users", response_model=UserPublic, status_code=201)
def create_user(user: UserCreate):
return {"id": 1, "username": user.username, "internal_note": "教学示例"}响应只包含 id 和 username,没有 internal_note。这个示例演示字段控制,没有实现密码存储;密码处理见第 15 章。
response_model 用于描述响应结构、校验返回数据、进行序列化并筛选字段。处理函数返回 ORM 对象时,也可以借助适当配置使用响应模型。官方响应模型说明
输出错误与输入错误不同
客户端提交不合法参数,默认常得到 422;如果你声明输出必须有 id,却返回了缺少 id 的数据,这是服务器代码不符合接口约定,通常表现为服务器错误,应由开发者修复。
不要为了让错误消失而随意去掉响应模型。它往往正在帮助你发现接口实现与约定不一致。
常见状态码
| 状态码 | 常见含义 | 场景 |
|---|---|---|
| 200 | 成功 | 查询、修改 |
| 201 | 已创建 | 创建资源 |
| 202 | 已接受,处理尚未完成 | 接收后台处理请求 |
| 204 | 成功且没有响应正文 | 删除资源 |
| 400 | 请求不符合业务要求 | 不能将订单转到该状态 |
| 401 | 缺少或无效的身份凭据 | 没登录、Token 过期 |
| 403 | 无权限 | 普通用户执行管理员操作 |
| 404 | 未找到资源或路径 | 任务不存在 |
| 409 | 冲突 | 唯一用户名已存在 |
| 422 | 请求数据校验失败 | 字段缺失、类型或范围不对 |
| 500 | 服务器内部错误 | 代码异常、输出不合法 |
这里给出常见使用习惯,具体业务仍要制定一致的接口约定。官方响应状态码说明
204 不应返回 JSON 正文
追加到本章完整示例:
from fastapi import Response, status
@app.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(user_id: int):
# 教学示例:实际删除逻辑应在这里执行
return Response(status_code=status.HTTP_204_NO_CONTENT)如果你想返回 {"message": "删除成功"},可以选用 200 并声明相应响应结构。
响应类型详解
FastAPI 的处理函数不只能返回字典。根据内容形式,可以返回不同类型的响应对象,这些类都从 fastapi.responses 导入。先看总览:
| 响应写法 | 用途 | 典型场景 |
|---|---|---|
直接返回 dict/list | JSON(默认) | 大多数 API |
JSONResponse | 定制 JSON | 自定义状态码/头 |
FileResponse | 下载文件 | 附件下载、图片 |
StreamingResponse | 分块流式传输 | 大文件、实时数据 |
HTMLResponse | HTML 页面 | 服务端渲染页面 |
PlainTextResponse | 纯文本 | robots.txt 等 |
RedirectResponse | 重定向 | 旧地址跳新地址 |
Response | 自定义原始内容 | XML、YAML、二进制 |
一个重要区别:直接返回 dict 等普通数据时,FastAPI 会走完整的响应模型处理流程(校验、序列化、字段筛选);而直接返回一个 Response 对象时,框架把它当成“已经封装好的成品”,原样发送,不再替你序列化。官方自定义响应说明
1. JSON 响应
返回字典或列表,FastAPI 会自动转成 JSON,并设好 Content-Type: application/json:
@app.get("/data")
def get_data():
return {"name": "手机", "price": 2999}需要定制状态码、响应头时,手动构造 JSONResponse:
from fastapi.responses import JSONResponse
@app.get("/data/custom")
def get_data_custom():
return JSONResponse(
content={"name": "手机", "price": 2999},
status_code=200,
headers={"X-Custom": "abc", "X-Total-Count": "1"},
)JSONResponse 适合需要额外头(比如分页总数 X-Total-Count)或自定义状态码的场景;普通场景直接 return 字典即可。
2. 文件响应 FileResponse
下载文件用 FileResponse。它像快递员直接搬走一整箱文件,不拆开、不整箱读进内存,而是从磁盘流式读取后发送,所以再大的文件也不会撑爆内存:
from fastapi.responses import FileResponse
@app.get("/download")
def download():
return FileResponse(
path="report.pdf", # 服务器上的文件路径
filename="年报.pdf", # 下载到客户端时显示的名字
media_type="application/pdf",
)path:文件在服务器上的实际路径。filename:设置后会自动加上Content-Disposition头,浏览器按这个名字下载,而不是直接打开。media_type:不写时 FastAPI 会按文件后缀猜测;猜不准或想强制指定时手动写。
图片等静态资源也可以直接返回,前端 <img src="/download"> 就能显示。
3. 流式响应 StreamingResponse
StreamingResponse 边生成边发送,不必一次性把所有数据攒齐。它像水管放水或看直播:水(数据)流到哪就送到哪,而不是先装满一桶再倒。
from fastapi.responses import StreamingResponse
def generate_lines():
for i in range(100):
yield f"第 {i} 行\n"
@app.get("/stream")
def stream():
return StreamingResponse(generate_lines(), media_type="text/plain")适用场景:
| 场景 | 说明 |
|---|---|
| 大文件/大数据导出 | 逐块读取,内存占用恒定 |
| 实时数据 | 日志、进度、监控数据持续推送 |
| AI 逐字回复 | 像聊天机器人那样一个字一个字往外蹦 |
传给 StreamingResponse 的可以是普通生成器,也可以是 async 生成器(用 async def + yield)。media_type 明确告诉客户端这是什么内容。
4. HTML、纯文本与重定向
from fastapi.responses import HTMLResponse, PlainTextResponse, RedirectResponse
@app.get("/", response_class=HTMLResponse)
def home():
return "<h1>你好,欢迎</h1>"
@app.get("/robots.txt", response_class=PlainTextResponse)
def robots():
return "User-agent: *\nDisallow: /admin"
@app.get("/old")
def old():
return RedirectResponse(url="/new")HTMLResponse:返回网页,可配合模板引擎做服务端渲染。PlainTextResponse:返回不带任何格式的纯文本。RedirectResponse:让浏览器跳到新地址,默认状态码307;也可用status_code=301表示永久跳转。
前两个用了 response_class 参数,含义是“把返回的普通数据按这个响应类来包装”;而 RedirectResponse 直接返回了响应对象。
5. 自定义响应数据
当需要返回 JSON 之外的格式(XML、YAML、纯字节等),用最基础的 Response 手动指定内容和类型:
from fastapi import Response
@app.get("/export.xml")
def export_xml():
xml = """<?xml version="1.0" encoding="UTF-8"?>
<item>
<name>手机</name>
<price>2999</price>
</item>"""
return Response(
content=xml,
media_type="application/xml",
headers={"X-From": "FastAPI"},
)Response 的 content 接收字符串或字节;media_type 决定 Content-Type 头;headers 添加自定义响应头。注意:直接返回 Response 时,框架不会帮你做 JSON 序列化,内容是什么就原样发什么。
如果只是想给普通 JSON 响应加几个头,还有一个更轻量的办法——在函数里注入 Response 对象改它的 headers:
@app.get("/data")
def get_data(response: Response):
response.headers["X-Custom"] = "abc"
return {"name": "手机", "price": 2999}这样既保留了返回字典的便利(自动 JSON 序列化),又能自定义响应头。
如何选择响应类型
| 需求 | 推荐写法 |
|---|---|
| 返回 JSON 数据 | 直接 return dict |
| JSON 但需自定义头/状态码 | JSONResponse |
| 下载文件 | FileResponse |
| 大文件/实时流 | StreamingResponse |
| 返回网页 | HTMLResponse |
| 返回纯文本 | PlainTextResponse |
| 跳转 | RedirectResponse |
| 自定义格式/字节 | Response |
本章练习
为商品系统实现以下接口:
GET /products返回商品 JSON 列表,并加上自定义头X-Total-Count;GET /products/{id}/receipt下载一份收据文件;GET /products/export以流式方式逐条导出商品数据;GET /products/{id}.xml返回 XML 格式的商品详情。
过关标准:能解释返回普通数据与返回 Response 对象的区别;能为“下载文件”“流式导出”“自定义格式”三种需求分别选出正确的响应类。