在 FastAPI 中,请求(Request)和响应(Response)的处理是其最核心、也是最优雅的部分。FastAPI 基于 Pydantic 模型和 Python 类型提示,实现了自动的数据解析、验证、序列化和文档生成。
下面我将从请求处理、响应控制到高级技巧,为你系统梳理 FastAPI 的请求与响应机制。
一、 请求处理 (Handling Requests)
FastAPI 通过函数参数来声明它期望接收的数据。根据参数的定义方式,FastAPI 会自动从不同的位置提取数据。
1. 路径参数 (Path Parameters)
从 URL 路径中提取。
@app.get("/items/{item_id}")
def get_item(item_id: int): # item_id 必须与路径中的 {item_id} 同名
return {"item_id": item_id}2. 查询参数 (Query Parameters)
从 URL 的 ?key=value 部分提取。任何不在路径中声明的参数都会被视为查询参数。
# 访问: /items/?skip=0&limit=10
@app.get("/items/")
def list_items(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}3. 请求体 (Request Body) – POST/PUT/PATCH
当需要发送复杂的 JSON 数据时,使用 Pydantic 模型作为参数。FastAPI 会自动读取请求体中的 JSON,验证其结构,并转换为 Pydantic 对象。
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str
price: float
description: str | None = None
tags: list[str] = []
@app.post("/items/")
def create_item(item: Item):
# item 是一个已经过验证的 Pydantic 对象
print(item.name)
return item4. 混合使用:路径 + 查询 + 请求体
FastAPI 能智能区分它们,你只需要正确声明参数即可。
@app.put("/items/{item_id}")
def update_item(
item_id: int, # 路径参数
q: str | None = None, # 查询参数
item: Item # 请求体
):
return {"item_id": item_id, "query": q, "updated_item": item}5. 其他请求来源
- Header: 使用
Header依赖。python from fastapi import Header def read_items(user_agent: str | None = Header(None)): return {"User-Agent": user_agent} - Cookie: 使用
Cookie依赖。python from fastapi import Cookie def read_items(session_id: str | None = Cookie(None)): return {"session_id": session_id} - Form Data: 使用
Form依赖 (需安装python-multipart)。python from fastapi import Form def login(username: str = Form(), password: str = Form()): return {"username": username} - Files: 使用
File和UploadFile。python from fastapi import File, UploadFile def upload_file(file: UploadFile = File(...)): return {"filename": file.filename}
二、 响应控制 (Handling Responses)
默认情况下,FastAPI 会将你的返回值(字典、列表、Pydantic 模型)自动转换为 JSON 格式,并设置状态码为 200 OK。但你可以通过以下方式精细控制响应。
1. 使用 response_model (推荐 🌟)
这是 FastAPI 最强大的特性之一。它不仅定义了返回数据的结构,还会:
- 自动序列化:将 Pydantic 模型转为 JSON。
- 数据过滤:只返回模型中定义的字段(例如隐藏密码)。
- 生成文档:在 Swagger UI 中显示预期的响应结构。
class UserOut(BaseModel):
username: str
email: str
# 注意:这里没有定义 password
class UserIn(BaseModel):
username: str
email: str
password: str
@app.post("/users/", response_model=UserOut)
def create_user(user: UserIn):
# 即使返回了包含 password 的对象,response_model 也会将其过滤掉
return {"username": user.username, "email": user.email, "password": "secret"}
# 客户端实际收到: {"username": "...", "email": "..."}2. 自定义状态码
使用 status_code 参数或 Response 对象。
from fastapi import status
# 方法 A: 在装饰器中指定 (推荐用于固定状态码)
@app.post("/items/", status_code=status.HTTP_201_CREATED)
def create_item(item: Item):
return item
# 方法 B: 在函数内部动态指定 (推荐用于条件性状态码)
from fastapi.responses import JSONResponse
@app.delete("/items/{item_id}")
def delete_item(item_id: int):
if not item_exists(item_id):
return JSONResponse(status_code=404, content={"detail": "Item not found"})
return {"message": "Deleted"}3. 直接返回 Response 对象
如果你需要返回非 JSON 内容(如 HTML、XML、图片),或者需要完全控制 Header,可以直接返回 Response 或其子类。
from fastapi.responses import PlainTextResponse, HTMLResponse
@app.get("/text", response_class=PlainTextResponse)
def get_text():
return "Hello World" # 返回纯文本,而不是 JSON 字符串
@app.get("/html", response_class=HTMLResponse)
def get_html():
return "<h1>Hello HTML</h1>"4. 自定义响应头 (Headers)
from fastapi.responses import JSONResponse
@app.get("/headers")
def get_headers():
content = {"message": "Hello"}
headers = {"X-Custom-Header": "MyValue"}
return JSONResponse(content=content, headers=headers)三、 常见陷阱与最佳实践
1. 陷阱:忘记安装 python-multipart
如果你尝试使用 Form 或 File 参数,但没有安装这个库,FastAPI 会报错。
pip install python-multipart2. 陷阱:response_model 与 return 类型不一致
虽然 FastAPI 很宽容,但最好确保 return 的数据能够被 response_model 解析。如果返回的数据缺少 response_model 中必需的字段,可能会报错或返回 null。
3. 最佳实践:使用 HTTPException 处理错误
不要手动返回 4xx/5xx 的 JSONResponse,而是抛出异常,让 FastAPI 统一处理。
from fastapi import HTTPException, status
@app.get("/items/{item_id}")
def get_item(item_id: int):
if item_id not in items_db:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Item {item_id} not found",
headers={"X-Error": "There goes my error"}
)
return items_db[item_id]这样做的好处是:Swagger UI 会自动记录这些可能的错误响应,方便前端开发者处理。
4. 最佳实践:大型项目使用 APIRouter
不要在 main.py 中写所有路由。使用 APIRouter 将不同模块的路由拆分到不同文件,并保持 response_model 的一致性。
四、 总结:请求与响应速查表
| 场景 | 推荐方式 | 示例 |
|---|---|---|
| 获取 URL 路径变量 | 路径参数 | def func(id: int) |
| 获取 URL 问号后参数 | 查询参数 | def func(q: str = None) |
| 接收 JSON 数据 | Pydantic 模型 | def func(item: Item) |
| 接收表单/文件 | Form / UploadFile | def func(file: UploadFile) |
| 返回标准 JSON | 直接返回 dict/模型 | return {"ok": True} |
| 过滤敏感字段/规范输出 | response_model | @app.get(..., response_model=UserOut) |
| 返回非 JSON (HTML/文本) | response_class | response_class=HTMLResponse |
| 处理业务错误 | HTTPException | raise HTTPException(404, ...) |
掌握了这些,你就能够处理绝大多数 Web API 的数据交互需求了。
你想深入了解如何验证更复杂的请求数据(如正则表达式匹配、自定义验证器),还是如何全局处理异常(Global Exception Handlers)?随时告诉我!