Skip to content

路径参数、查询参数、请求头与 Cookie ​

一个 HTTP 请求到达 FastAPI 时,会携带很多信息。本章要讲清楚其中四类“数据从哪来、怎么取、怎么校验”:

数据来源生活化类比在请求里的位置FastAPI 里的写法
路径参数快递地址上的门牌号URL 路径中 {...}函数参数 + 类型标注
查询参数点外卖时的“筛选条件”URL 中 ? 之后带默认值的函数参数
请求头快递面单上的备注说明HTTP 头部Annotated[类型, Header()]
Cookie商家发的会员卡(贴身上)请求头部里的 Cookie 字段Annotated[类型, Cookie()]

下面逐一展开。示例统一从下面的骨架开始:

python
from fastapi import FastAPI

app = FastAPI()

关于 Annotated 写法:从 FastAPI 0.95 起,官方推荐用 Python 的 Annotated 来声明带校验的参数——把“类型”和“校验规则”写在一起,代码更整洁。以查询参数为例:

python
# 旧写法(仍可用,但不推荐)
limit: int = Query(default=10, ge=1, le=100)

# 新写法(推荐)
limit: Annotated[int, Query(ge=1, le=100)] = 10

要点:默认值从 Query(default=...) 里挪到了参数本身的 = 10;必填参数则干脆不写默认值。本章后续所有需要 Path / Query / Header / Cookie 的示例统一用 Annotated 写法。


一、路径参数(Path Parameters) ​

1. 什么是路径参数 ​

路径参数是“长在 URL 路径里”的那部分数据,用花括号占位。它天生表达“到底是哪一个资源”。

比如 /users/3 里的 3,就是在说“第 3 号用户”,而不是 1 号或 2 号。它像快递地址里的门牌号:北京市/朝阳区/xx小区/3号楼,最后一个数字决定了包裹送到哪一户。

2. 基本用法 ​

在装饰器路径里用 {名字} 占位,函数里用同名参数接收即可:

python
@app.get("/users/{user_id}")
def read_user(user_id):
    return {"user_id": user_id}

访问 /users/3,返回 {"user_id": "3"}。

注意:没有写类型时,FastAPI 默认把它当字符串处理,3 是字符串 "3",不是数字 3。

3. 用类型标注做自动转换 + 校验 ​

给参数加上类型标注,FastAPI 会自动做两件事:转换类型和校验合法性。

python
@app.get("/users/{user_id}")
def read_user(user_id: int):
    return {"user_id": user_id}

现在访问 /users/3,返回 {"user_id": 3}(真正的整数)。

而访问 /users/abc,会直接返回 422 Unprocessable Entity,并附上一段 JSON 告诉你错在哪:

json
{
  "detail": [
    {
      "loc": ["path", "user_id"],
      "msg": "value is not a valid integer",
      "type": "type_error.integer"
    }
  ]
}

这就是 FastAPI 的核心卖点之一:你只写类型标注,它替你挡掉非法输入,不再需要手写 if not user_id.isdigit() 之类的判断。

4. 用枚举限制取值范围 ​

有些路径参数的合法取值只有固定几个。用 Python 的 Enum 可以让文档和校验都更清晰:

python
from enum import Enum


class Color(str, Enum):
    red = "red"
    green = "green"
    blue = "blue"


@app.get("/lights/{color}")
def set_light(color: Color):
    if color is Color.red:
        return {"color": color, "msg": "红灯亮"}
    return {"color": color}

访问 /lights/red 正常;访问 /lights/pink 会得到 422,因为 pink 不在枚举里。str 继承让枚举值能像普通字符串一样参与返回和比较。

5. 路径参数里含有斜杠的特殊情况 ​

默认 {...} 匹配到 / 就停。如果路径参数本身可能带斜杠(比如文件路径 images/a/b.png),需要写 {参数名:path}:

python
@app.get("/files/{file_path:path}")
def read_file(file_path: str):
    return {"file_path": file_path}

访问 /files/images/a/b.png,file_path 得到 images/a/b.png。这是 Starlette 提供的路径转换器语法。

6. 用 Path 给路径参数加约束 ​

除了类型,还能用 Path 加更多校验规则,比如数值范围、字符串长度:

python
from typing import Annotated

from fastapi import Path


@app.get("/items/{item_id}")
def read_item(
    item_id: Annotated[int, Path(title="商品ID", ge=1, le=1000)],
):
    return {"item_id": item_id}

Annotated[int, Path(...)] 里的 int 声明类型,Path(...) 附加校验规则。不写默认值时,参数默认就是必填。

  • ge=1:大于等于 1(greater than or equal)。
  • le=1000:小于等于 1000。
  • title:会在自动生成的 /docs 文档里显示为字段说明。

常用约束一览:

参数含义适用类型
ge / gt大于等于 / 大于数字
le / lt小于等于 / 小于数字
min_length最小长度字符串
max_length最大长度字符串
pattern正则匹配字符串
title文档中的字段名任意
description文档中的字段说明任意

7. 固定路径放在动态路径之前 ​

这一条在《路由与 HTTP 方法》一章已讲过,这里再强调一次,因为它是路径参数最容易踩的坑:

python
# 正确:具体的固定路径写在前面
@app.get("/users/me")
def current_user():
    return {"name": "当前用户"}


@app.get("/users/{user_id}")
def read_user(user_id: int):
    return {"user_id": user_id}

如果顺序反了,/users/me 会先被 /users/{user_id} 截获,再因为 me 不是整数而报 422。官方路径参数说明


二、查询参数(Query Parameters) ​

1. 什么是查询参数 ​

查询参数是 URL 里 ? 后面的那串 key=value,多个之间用 & 连接:

https://shop.com/items?skip=0&limit=20&keyword=手机

它表达“对同一批资源做筛选、分页、排序”等附加条件,像点外卖时选的“辣度、份量、不要香菜”——不改变“点的是哪个菜”,只改变“这道菜怎么做”。

和路径参数的区别很直观:

对比项路径参数 /items/{id}查询参数 /items?q=手机
是否必填通常必填通常可选(可给默认值)
定位对象某个具体资源一批资源的筛选/分页
位置路径中间? 之后

2. 基本用法:只要不是路径参数,剩下的默认都是查询参数 ​

python
@app.get("/items/")
def read_items(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

FastAPI 的规则很简单:函数参数名如果没出现在路径里,也没有被 Body 之类标记,它就是查询参数。

  • 访问 /items/:用默认值,返回 {"skip": 0, "limit": 10}。
  • 访问 /items/?skip=5&limit=3:返回 {"skip": 5, "limit": 3}。
  • 访问 /items/?limit=20:skip 用默认值,limit 用 20。

3. 可选查询参数 ​

有些查询参数允许“不传”。用 None 作默认值即可:

python
@app.get("/items/")
def read_items(q: str | None = None):
    if q:
        return {"q": q, "msg": "搜索模式"}
    return {"q": None, "msg": "列表模式"}

str | None 表示“可以是字符串,也可以是空”。Python 3.9 及以下用 Optional[str] 或 Union[str, None]。

4. 必填查询参数 ​

不加默认值就是必填:

python
@app.get("/items/")
def read_items(limit: int):
    return {"limit": limit}

访问 /items/ 不传 limit 会得到 422 校验错误。一般来说,能提供合理默认值的查询参数就别设成必填,对调用方更友好。

5. 用 Query 加校验和说明 ​

查询参数同样支持校验,用 Query:

python
from typing import Annotated

from fastapi import Query


@app.get("/items/")
def read_items(
    q: Annotated[str | None, Query(min_length=3, max_length=20)] = None,
    limit: Annotated[int, Query(ge=1, le=100)] = 10,
):
    return {"q": q, "limit": limit}
  • min_length=3:q 只要传了,就必须至少 3 个字符。
  • ge=1、le=100:limit 必须在 1~100 之间,超出返回 422。
  • q 的默认值是 = None,表示可省略;limit 的默认值是 = 10。

Query 的约束参数和上一节的 Path 是同一套(ge、le、min_length、pattern 等)。

6. 用 alias 支持带特殊字符的参数名 ​

Python 变量名不能带 -,但某些接口约定查询参数就是 user-name。用 alias 打通两者:

python
@app.get("/items/")
def read_items(user_name: Annotated[str | None, Query(alias="user-name")] = None):
    return {"user_name": user_name}

这样 URL 里传 /items/?user-name=张三,函数里的 user_name 能拿到值。alias 同样适用于 Path。

7. 接收多个值(列表参数) ​

有时同一个参数要传多次,比如多选筛选 ?tag=手机&tag=数码:

python
@app.get("/items/")
def read_items(tag: Annotated[list[str], Query()] = []):
    return {"tag": tag}
  • 传一次:?tag=手机 → ["手机"]。
  • 传多次:?tag=手机&tag=数码 → ["手机", "数码"]。

注意 list[str] 写法需要 Python 3.9+;更低版本用 from typing import List 后写 List[str]。

8. 布尔值查询参数的小心机 ​

FastAPI 会自动把查询参数里的 1/0、true/false、yes/no、on/off 识别成布尔值:

python
@app.get("/items/")
def read_items(in_stock: bool = False):
    return {"in_stock": in_stock}

?in_stock=true、?in_stock=1、?in_stock=yes、?in_stock=on 都会得到 True。如果传了个它不认识的字符串(比如 ?in_stock=maybe),会返回 422。


三、请求头(Request Headers) ​

1. 什么是请求头 ​

请求头是 HTTP 请求里“键值对”形式的元信息,描述请求本身,而不是业务数据。它像快递面单上除了收件地址之外的备注:易碎品、到付、本人签收——不影响包裹寄到哪,但影响对方怎么处理它。

常见请求头:

头部常见内容用途
User-Agent浏览器/客户端标识识别来访客户端
AuthorizationBearer <token> 等身份认证
Content-Typeapplication/json 等说明请求体格式
Acceptapplication/json 等期望的响应格式
X-Request-Id自定义追踪 ID链路追踪、日志关联

2. 用 Header 读取请求头 ​

python
from typing import Annotated

from fastapi import Header


@app.get("/whoami")
def whoami(user_agent: Annotated[str | None, Header()] = None):
    return {"User-Agent": user_agent}

请求里的 User-Agent 头就能被读出来。用 curl 验证:

bash
curl http://127.0.0.1:8000/whoami -H "User-Agent: MyClient/1.0"

返回:

json
{ "User-Agent": "MyClient/1.0" }

3. 大小写不敏感 ​

HTTP 头的名字大小写不敏感,User-Agent、user-agent、USER-AGENT 指的都是同一个头,FastAPI 会自动统一处理,放心接收。

4. 下划线 _ 与连字符 - 的自动转换 ​

HTTP 头习惯用 -,Python 变量名却必须用 _。FastAPI 会自动把参数名里的 _ 换成 - 去找头:

python
@app.get("/whoami")
def whoami(x_request_id: Annotated[str | None, Header()] = None):
    return {"x-request-id": x_request_id}

上面的 x_request_id 对应请求头 x-request-id。

如果业务里确实有个带下划线的头(极少见),想关闭这个自动转换,加 convert_underscores=False:

python
x_request_id: Annotated[str | None, Header(convert_underscores=False)] = None

5. Header 也能加校验 ​

和 Path、Query 一样,Header 支持 min_length、pattern、alias 等参数:

python
@app.get("/whoami")
def whoami(
    x_token: Annotated[str, Header(min_length=8)],
):
    return {"x_token": x_token}

不写默认值时,x-token 这个头必填,且至少 8 个字符,否则返回 422。

6. 读取认证信息的典型场景 ​

很多接口在函数里直接读 Authorization 头来做鉴权(生产环境建议用 FastAPI 的依赖注入 Depends,会更干净,见后续章节):

python
@app.get("/profile")
def profile(authorization: Annotated[str | None, Header()] = None):
    if not authorization or not authorization.startswith("Bearer "):
        return {"msg": "未登录"}
    token = authorization.removeprefix("Bearer ")
    return {"token": token}

四、Cookie ​

Cookie 是服务器让浏览器“记住”的一小段数据,之后浏览器每次请求都会自动带上。它像商家发的会员卡:办卡时(服务器 Set-Cookie)发到你手里,之后每次消费(请求)你把它贴身上,商家一眼就能认出你是谁。

典型用途:登录态(Session)、用户偏好、购物车标识、广告追踪 ID 等。

python
from typing import Annotated

from fastapi import Cookie


@app.get("/items/")
def read_items(ads_id: Annotated[str | None, Cookie()] = None):
    return {"ads_id": ads_id}

访问时带上 Cookie,就能读到:

bash
curl http://127.0.0.1:8000/items/ -H "Cookie: ads_id=abc123"

返回:

json
{ "ads_id": "abc123" }

Cookie 的校验参数和 Header、Query 也是一套。

读取用 Cookie,写入则通过 Response 对象:

python
from fastapi import Response


@app.post("/login")
def login(response: Response):
    # 模拟登录成功,种下一个 session cookie
    response.set_cookie(
        key="session",
        value="abc123",
        httponly=True,     # 禁止 JS 读取,降低 XSS 风险
        max_age=3600,      # 有效期 1 小时(秒)
        samesite="lax",    # 跨站请求时是否携带
    )
    return {"msg": "登录成功"}

set_cookie 常用参数:

参数含义
keyCookie 名
valueCookie 值
max_age有效期(秒)
path生效路径,默认 / 全站生效
httponlyTrue 时 JS 读不到,更安全
secureTrue 时仅 HTTPS 传输
samesite跨站携带策略:lax / strict / none

删除 Cookie 则是设一个已过期的 Cookie:

python
@app.post("/logout")
def logout(response: Response):
    response.delete_cookie(key="session")
    return {"msg": "已退出"}

4. 读取 vs 设置的完整闭环 ​

下面用一个极简的“登录 → 识别 → 退出”示例串起 Cookie 的读和写:

python
@app.post("/login")
def login(response: Response):
    response.set_cookie(key="session", value="abc123", httponly=True)
    return {"msg": "登录成功"}


@app.get("/whoami")
def whoami(session: Annotated[str | None, Cookie()] = None):
    if session == "abc123":
        return {"msg": "你是已登录用户"}
    return {"msg": "匿名访客"}


@app.post("/logout")
def logout(response: Response):
    response.delete_cookie(key="session")
    return {"msg": "已退出"}

流程:POST /login 种 Cookie → GET /whoami 浏览器自动带上 Cookie 被识别 → POST /logout 删除 Cookie。这个例子只演示机制,真实登录态需要加密和会话存储,不能这样明文判断。


五、四者对比速查表 ​

维度路径参数查询参数请求头Cookie
位置URL 路径 {}URL ? 之后HTTP 头部HTTP 头部 Cookie
典型用途定位具体资源筛选/分页/排序元信息、鉴权会话、偏好、追踪
必填性通常必填通常可选视情况视情况
是否自动携带手动写进 URL手动写进 URL客户端/浏览器决定浏览器自动带上
FastAPI 写法函数参数 + 类型带默认值参数Header(...)Cookie(...)
校验工具Path(...)Query(...)Header(...)Cookie(...)

六、一个综合示例 ​

把四类数据放进一个接口里,体会它们各司其职:

python
from typing import Annotated
from enum import Enum

from fastapi import FastAPI, Cookie, Header, Path, Query


class Lang(str, Enum):
    zh = "zh"
    en = "en"


app = FastAPI()


@app.get("/shops/{shop_id}/items/")
def search_items(
    shop_id: Annotated[int, Path(ge=1, title="店铺ID")],
    keyword: Annotated[str | None, Query(min_length=2)] = None,
    page: Annotated[int, Query(ge=1)] = 1,
    lang: Annotated[Lang, Query()] = Lang.zh,
    x_request_id: Annotated[str | None, Header()] = None,
    session: Annotated[str | None, Cookie()] = None,
):
    return {
        "shop_id": shop_id,        # 来自路径:定位哪家店
        "keyword": keyword,        # 来自查询:搜什么
        "page": page,              # 来自查询:第几页
        "lang": lang,              # 来自查询:什么语言
        "x_request_id": x_request_id,  # 来自请求头:追踪本次请求
        "session": session,        # 来自 Cookie:识别登录态
    }

一个可能的请求:

GET /shops/12/items/?keyword=手机&page=2&lang=zh

对应的四类数据被 FastAPI 自动解析、转换、校验后,干干净净地交到函数里。


本章练习 ​

为图书系统设计一个接口:GET /books/{book_id} 返回书籍详情,同时支持:

  1. 用查询参数 fields 控制返回哪些字段(可选);
  2. 用请求头 x-client 记录调用方标识;
  3. 用 Cookie recent_view 读取用户最近浏览记录。

过关标准:能说出四类参数各自的位置和适用场景;能解释为什么 fields 适合做查询参数而不是路径参数;能写出一个同时读取四类数据的接口。