FastAPI Cookie 参数详解:像声明 Query 一样优雅地读取 Cookie 请求参数
导读
在 Web 请求中,除路径参数与查询参数外,Cookie 是浏览器自动携带的又一类关键请求数据(会话标识、埋点追踪 ID、偏好设置等)。本篇教程聚焦 FastAPI 提供的 Cookie 参数声明机制,讲解如何在路径操作函数中读取并校验 Cookie,并结合本仓库源码说明其与 Query、Path 同源的实现原理,以及浏览器与 /docs 交互 UI 在传 Cookie 时的真实限制。学完本篇,你将掌握 Cookie 参数的标准声明方式、可选与校验用法、OpenAPI 文档呈现,以及如何用测试客户端验证 Cookie 读取逻辑。
完整示例:一个读取 Cookie 的最小接口
仓库中的配套示例 tutorial001_an_py310.py 给出了读取名为 ads_id 的 Cookie 的最小可运行代码:
from typing import Annotated
from fastapi import Cookie, FastAPI
app = FastAPI()
@app.get("/items/")
async def read_items(ads_id: Annotated[str | None, Cookie()] = None):
return {"ads_id": ads_id}
启动应用后,访问 http://127.0.0.1:8000/items/,并在请求中携带 Cookie: ads_id=some_value,接口就会读取该值并原样返回;若未携带任何 Cookie,则返回 {"ads_id": null}。示例同时提供了不使用 Annotated 的等价写法 tutorial001_py310.py:
from fastapi import Cookie, FastAPI
app = FastAPI()
@app.get("/items/")
async def read_items(ads_id: str | None = Cookie(default=None)):
return {"ads_id": ads_id}
两种风格任选其一即可,仓库文档与代码以 Annotated 风格为主。
声明 Cookie 参数的两种风格
参考原文教程(cookie-params.md),定义 Cookie 参数的方式与定义 Query、Path 参数完全相同:
第一步:导入 Cookie,从 fastapi 中导入:
from fastapi import Cookie, FastAPI
第二步:在路径操作函数中声明。在参数中直接写 Cookie(),并给出 Python 类型注解。默认值可以是 None,也可以带上字符串长度、正则等额外校验与注解参数:
from typing import Annotated
from fastapi import Cookie, FastAPI
app = FastAPI()
@app.get("/items/")
async def read_items(ads_id: Annotated[str | None, Cookie()] = None):
return {"ads_id": ads_id}
其要点在于:
- 函数参数
ads_id的类型为str | None,表示该 Cookie 可选,缺省时得到None; - 由于声明了默认值
None,参数非必填,OpenAPI 中会标记"required": false; - 返回值直接透传读取到的 Cookie 值,方便前端核对。
底层原理:Cookie 与 Query、Path 是同源的“姊妹类”
从源码结构看,原文所提示的“Cookie 是 Path 与 Query 的 sister 类”有清晰的代码依据。
在 params.py 中,四个参数类别统一由枚举 ParamTypes 表达:
class ParamTypes(Enum):
query = "query"
header = "header"
path = "path"
cookie = "cookie"
Query、Path、Header、Cookie 四个类都继承自共同的基类 Param(其本身继承自 Pydantic 的 FieldInfo),每个子类只是通过类属性 in_ 标记自己的“归属位置”。Cookie 类的定义片段如下:
class Cookie(Param): # type: ignore[misc]
in_ = ParamTypes.cookie
正因如此,Cookie 可以复用 Param 基类提供的全部声明能力:默认值、别名、数值大小/字符串长度校验、正则、示例、说明文字等。
值得提醒的是,从 fastapi 导入的 Cookie、Query、Path 名称虽然是“类”,但实际导出的是 param_functions.py 中定义的同名函数——调用 Cookie() 时它内部会构造并返回一个 params.Cookie 实例。也就是说:
from fastapi import Cookie
导入的是函数 Cookie,调用 Cookie(default=None) 返回的是 params.Cookie 对象,类与函数同名共存、衔接透明。
数据流向在依赖解析层得到印证:dependencies/utils.py 在 get_dependant() 阶段依据 field_info_in 把字段分流进 cookie_params,随后在求解阶段调用:
cookie_values, cookie_errors = request_params_to_args(
dependant.cookie_params, request.cookies
)
即 FastAPI 直接从 ASGI 请求对象的 request.cookies 中取出全部 Cookie,再按声明字段逐个匹配、类型转换与校验,最后把结果注入函数参数。
为什么必须用 Cookie() 显式声明
默认情况下,FastAPI 会按照“路径参数 → 查询参数 → 请求体(JSON 等)→ 表单/文件”的既定规则推断函数参数的来源。一个普通类型的参数(如 str、int)如果不加任何 Cookie()/Query() 包装,会被解释为查询参数。
因此,要声明 Cookie 必须显式使用 Cookie。一旦字段被标记为 cookie,OpenAPI 生成的参数位置 in 就是 "cookie",FastAPI 也会改从 request.cookies 中取值,而不是从 URL 查询串取值。
校验与扩展参数:默认值、长度、正则、别名等
Cookie 支持与 Query、Path 完全一致的“额外校验或注解参数”。可用的能力在 params.py 基类构造器中完整定义,常用项包括:
| 参数 | 作用 | 适用范围 |
|---|---|---|
default |
字段缺省时的默认值 | 任意 |
default_factory |
生成默认值的可调用对象 | 任意 |
alias |
参数的备用名称(取值与 OpenAPI 均使用该名) | 任意 |
description |
参数的人类可读说明 | 任意 |
deprecated |
在文档中标记该 Cookie 参数已弃用 | 任意 |
min_length / max_length |
字符串最小/最大长度 | 字符串 |
pattern |
字符串正则校验 | 字符串 |
gt / ge / lt / le |
数值大于/大于等于/小于/小于等于 | 数值 |
multiple_of |
数值倍数约束 | 数值 |
examples |
OpenAPI 示例 | 任意 |
注意这些数值与正则约束只有在类型匹配时才真正生效(例如给 int 类型的 Cookie 配 gt=0),在源码中它们会随字段元数据一并交给 Pydantic 进行编译与校验。
下面给一个综合示例:读取名为 session_id 的字符串 Cookie,要求长度介于 8 到 64 之间且必须是小写十六进制,同时附带说明与弃用标记:
from typing import Annotated
from fastapi import Cookie, FastAPI
app = FastAPI()
@app.get("/items/")
async def read_items(
session_id: Annotated[
str | None,
Cookie(
description="会话标识,8-64 位小写十六进制字符串",
min_length=8,
max_length=64,
pattern="^[a-f0-9]+$",
deprecated=True,
),
] = None,
):
return {"session_id": session_id}
当请求携带的 Cookie 不满足约束时(例如长度不足或含非法字符),FastAPI 会返回带 422 Unprocessable Entity 的校验错误,而不是直接透传非法数据。
浏览器与 /docs 交互 UI 的真实限制
一个必须牢记的实践约束:浏览器会以特殊方式“幕后”管理 Cookie,通常不允许 JavaScript 轻易读写它们(尤其带有 HttpOnly 属性的会话 Cookie)。FastAPI 自带的交互式 API 文档位于 /docs,原文明确指出:你会在接口详情里看到 Cookie 参数的说明字段,但由于 Swagger UI 基于 JavaScript 运行,即使你在文档页填好 Cookie 值并点击 Execute,这些 Cookie 也不会被随请求发送,结果会表现为“好像什么都没填”的报错或空值。
因此实际联调 Cookie 相关接口时,更可靠的手段是:
- 使用 curl / HTTPie 手动带上
-H "Cookie: ads_id=xxx"; - 使用支持管理 Cookie 的 REST 客户端(如 Postman、Insomnia);
- 使用 httpx / TestClient 在自动化测试中显式传入
cookies。
用测试验证 Cookie 读取:TestClient 携带 Cookie
仓库配套测试 test_tutorial001.py 对上述示例做了完整覆盖,它同时参数化了两种声明风格,并断言四类场景:
- 不带任何 Cookie 访问
/items→200,响应{"ads_id": null}; - 带
ads_id=ads_track→200,响应{"ads_id": "ads_track"}; - 同时带
ads_id与无关的sessionCookie → 只读取声明的ads_id; - 只带无关的
sessionCookie →ads_id为null。
测试通过 TestClient(mod.app, cookies=cookies) 注入 Cookie,等价于 HTTP 层发送 Cookie 请求头。你也可以在本地用 uvicorn 启动后手动验证:
uvicorn docs_src.cookie_params.tutorial001_an_py310:app --reload
curl -H "Cookie: ads_id=hello" http://127.0.0.1:8000/items/
Cookie 参数在 OpenAPI 文档中的呈现
由于 Cookie 参数在 OpenAPI 规范中被定义为独立的参数位置,FastAPI 会为声明的 Cookie 生成标准条目。根据测试中断言的 /openapi.json 快照,上述 ads_id 参数最终被生成为:
{
"required": false,
"schema": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Ads Id"
},
"name": "ads_id",
"in": "cookie"
}
可见其 in 字段为 "cookie",与查询参数("in": "query")、路径参数("in": "path")区分开。这一生成的入口位于 openapi/utils.py:它把依赖解析出的 cookie_params 与其他三类参数组合后统一序列化到 OpenAPI 的 parameters 列表中,因此基于 OpenAPI 的工具链(客户端生成器、API 网关、文档站点)都能正确识别 Cookie 参数。
延伸阅读与小结
如果你的 Cookie 数量较多,可以考虑用 Pydantic 模型批量声明,见教程 cookie-param-models.md;若要基于 Cookie 实现 API Key 认证,可直接使用 fastapi.security.APIKeyCookie,其底层同样是从 request.cookies 中按名字取值(见 api_key.py)。
本篇小结:
- 用
from fastapi import Cookie导入,并用与Query、Path相同的方式在函数参数中声明; - 支持默认值、可空类型以及长度、正则、别名等全部校验与注解参数;
Cookie在源码层面与Query、Path同为Param的子类,仅通过in_ = ParamTypes.cookie区分取值位置;- 不写
Cookie()的参数会被当成查询参数,声明时必须显式标记; - 浏览器与基于 JavaScript 的
/docsUI 通常无法发送 Cookie,联调请改用 curl、REST 客户端或 TestClient 显式携带。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00