3. FastAPI路由与请求处理机制

FastAPI 的核心工作方式可以概括为一句话:用装饰器把 Python 函数注册成某个 URL 路径上的 HTTP 方法处理器,框架负责解析请求、校验参数、序列化响应。理解这条链路,才能真正掌控接口行为,而不是停留在“能跑就行”。本节围绕路径操作、HTTP 方法、请求参数解析、响应模型与状态码控制五个环节展开。

路径操作与 HTTP 方法

路径操作装饰器由“HTTP 方法 + 路径”组成,例如 @app.get("/items/{item_id}")。同一个路径可以绑定多个方法,FastAPI 会按方法分派到不同函数。路径中的 {item_id} 是路径参数占位符,函数形参名必须与占位符一致,否则框架无法注入。

from fastapi import FastAPI, Path, Query, status
from pydantic import BaseModel, Field

app = FastAPI(title="Demo API")

class ItemIn(BaseModel):
    name: str = Field(min_length=1, max_length=50)
    price: float = Field(gt=0)
    tags: list[str] = []

class ItemOut(BaseModel):
    id: int
    name: str
    price: float

@app.get("/items/{item_id}", response_model=ItemOut)
def get_item(
    item_id: int = Path(ge=1, description="商品ID,从1开始"),
    q: str | None = Query(default=None, max_length=20),
):
    return {"id": item_id, "name": f"item-{item_id}", "price": 9.9, "q": q}

这里 Path(ge=1) 表示路径参数必须大于等于 1,Query(max_length=20) 限制查询串长度。若请求 /items/0,FastAPI 会在进入函数前返回 422,并给出字段级错误定位,函数体根本不会执行——这是“声明式校验”的关键价值。

请求参数解析的优先级与来源

FastAPI 依据参数类型和声明自动判断参数来源,规则如下:路径占位符对应路径参数;简单类型(int、str、bool 等)默认视为查询参数;Pydantic 模型默认视为请求体;Header、Cookie、Form、File 需显式声明。多个来源可以同时出现在一个函数签名中。

FastAPI 请求处理链路:从 HTTP 请求到响应 HTTP 请求 GET /items/5?q=ab POST /items (JSON) 路由匹配 方法 + 路径 提取路径参数 参数解析与校验 Path / Query / Body Pydantic 类型转换 路径操作函数 业务逻辑执行 返回 dict / 模型 响应模型过滤 response_model 字段裁剪与序列化 状态码控制 status_code 默认值 HTTPException 覆盖 HTTP 响应 200 / 201 / 404 JSON 响应体 422 校验失败 字段级错误详情 函数不执行 校验不通过 参数来源判定规则 • 路径占位符 {item_id} → Path 参数,必须与函数形参同名 • 简单类型(int/str/bool)默认 → Query 查询参数;Pydantic 模型默认 → Body 请求体 • Header / Cookie / Form / File 需显式声明;多来源可共存于同一函数签名

上图展示了完整链路:请求先经路由匹配提取路径参数,再进入参数解析与校验;校验失败直接返回 422,函数不执行;校验通过后执行业务逻辑,返回值经响应模型过滤,最终按状态码规则输出。理解这条链路,排障时就能快速定位问题出在哪一环。

响应模型:字段裁剪与数据契约

response_model 不只是类型标注,它会在返回前对数据进行过滤和序列化。上面的 get_item 返回了 q 字段,但 ItemOut 未声明该字段,最终响应中不会出现 q。这一机制能有效防止内部字段(如密码哈希、内部状态)意外泄露。

若希望保留额外字段,可用 response_model_exclude_unset=True 只输出显式赋值的字段,或用 response_model_include / response_model_exclude 精细控制。对于列表接口,直接写 response_model=list[ItemOut] 即可。

状态码控制

默认情况下,GET 返回 200,POST 返回 200。创建资源时更规范的做法是返回 201:

@app.post("/items", response_model=ItemOut, status_code=status.HTTP_201_CREATED)
def create_item(item: ItemIn):
    return {"id": 1, "name": item.name, "price": item.price}

@app.get("/items/{item_id}")
def read_item(item_id: int):
    if item_id > 100:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"id": item_id}

当业务条件不满足时,用 HTTPException 抛出异常,FastAPI 会将其转换为对应状态码和 {"detail": ...} 响应体。注意:HTTPException 应在函数内部抛出,而不是返回错误字典——后者会得到 200 状态码,语义错误。

状态码选择建议:200 表示成功读取或更新;201 表示资源创建成功;204 表示成功但无响应体(如删除);400 表示客户端请求格式错误;404 表示资源不存在;422 表示参数校验失败(FastAPI 自动返回);500 表示服务端未捕获异常。

参数校验的边界与常见陷阱

路径参数和查询参数都支持 ge、le、gt、lt、min_length、max_length、pattern 等约束。请求体则通过 Pydantic 的 Field 声明。一个常见陷阱是:路径参数声明为 int,但客户端传入非数字字符串时,FastAPI 返回 422 而非 404。若希望“格式错误”和“资源不存在”都返回 404,需要在路由层做额外处理或自定义异常处理器。

另一个陷阱是查询参数的默认值。若声明 q: str | None = None,则 q 可选;若声明 q: str,则 q 必填,缺失时返回 422。布尔类型查询参数会接受 true、1、yes 等多种写法,但建议客户端统一使用 true/false 以避免歧义。

实操:一个可运行的完整示例

下面给出一个包含创建、查询、删除的迷你接口集,可直接复制运行并观察状态码与响应体变化:

from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field

app = FastAPI()
_db: dict[int, dict] = {}
_next_id = 1

class ItemIn(BaseModel):
    name: str = Field(min_length=1, max_length=50)
    price: float = Field(gt=0)

class ItemOut(BaseModel):
    id: int
    name: str
    price: float

@app.post("/items", response_model=ItemOut, status_code=status.HTTP_201_CREATED)
def create_item(item: ItemIn):
    global _next_id
    new_id = _next_id
    _next_id += 1
    _db[new_id] = {"id": new_id, "name": item.name, "price": item.price}
    return _db[new_id]

@app.get("/items/{item_id}", response_model=ItemOut)
def get_item(item_id: int):
    if item_id not in _db:
        raise HTTPException(status_code=404, detail="Item not found")
    return _db[item_id]

@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_item(item_id: int):
    if item_id not in _db:
        raise HTTPException(status_code=404, detail="Item not found")
    del _db[item_id]

启动后可用以下命令验证:curl -X POST http://127.0.0.1:8000/items -H "Content-Type: application/json" -d '{"name":"book","price":29.9}' 应返回 201 和完整对象;curl http://127.0.0.1:8000/items/999 应返回 404 和 {"detail":"Item not found"};curl -X DELETE http://127.0.0.1:8000/items/1 应返回 204 且无响应体。通过观察状态码和响应体,可以确认路由、参数解析、响应模型和状态码控制是否按预期协同工作。

注意:status_code=204 时不应返回任何响应体,否则部分客户端会报错。FastAPI 对 204 响应会自动忽略返回值,但函数仍应保持无返回或返回 None。

掌握以上机制后,接口的输入边界、输出契约和错误语义都由声明式代码统一约束,后续接入依赖注入、中间件和异步处理时,这条链路仍然是理解一切行为的基础。