第 8 章 · Cookie 与 Header 参数
本章目标:学会用
Cookie和Header声明参数读取请求中的 Cookie 与请求头,理解下划线自动转换规则,并掌握在响应中安全地设置 Cookie。
8.1 又一对"姐妹"参数类
Cookie 和 Header 与 Query、Path 同属一个家族:它们都继承自同一个 Param 公共类,因此支持完全相同的默认值、校验和别名机制。
为什么必须显式声明
如果只写 session_id: str = None,FastAPI 会把参数解释为查询参数。要让它去读 Cookie 或请求头,必须用 Cookie() / Header() 显式指定来源——这四个类从 fastapi 导入时实际是返回特殊类的工厂函数。
from typing import Optional
from fastapi import FastAPI, Cookie, Header
app = FastAPI()
@app.get("/items/")
async def read_items(
session_id: Optional[str] = Cookie(None), # 读取名为 session_id 的 Cookie
user_agent: Optional[str] = Header(None), # 读取 User-Agent 请求头
x_token: Optional[str] = Header(None), # 读取 X-Token 请求头
):
return {"session_id": session_id, "user_agent": user_agent, "x_token": x_token}测试:
curl http://127.0.0.1:8000/items/ \
-H "User-Agent: my-app/1.0" -H "X-Token: abc123" \
-b "session_id=xyz789"8.2 下划线自动转换:convert_underscores
HTTP 标准头的命名习惯是连字符(user-agent、content-type),但 Python 变量名不允许出现 -。Header 默认开启自动转换:把参数名中的 _ 映射到 -,同时 HTTP 头本身大小写不敏感,所以 user_agent 能匹配 User-Agent。
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/header-strict/")
async def read_strict_header(
# 某些自定义头确实带下划线(如 x_custom_key)时才需要关闭转换
x_custom_key: str = Header(..., convert_underscores=False),
):
return {"x_custom_key": x_custom_key}关闭转换前想清楚
部分 HTTP 代理和服务器禁止使用带下划线的头(Nginx 默认会丢弃这类头),开启 convert_underscores=False 前先确认整条链路都放行。
重复出现的同名头(如多个 X-Token)可以用 Optional[List[str]] 接收为列表,这在声明单个 str 时只会取其中一个值。
8.3 在响应中设置 Cookie
读取靠参数声明,写入则通过 response.set_cookie()。给路径函数加一个 Response 类型参数即可(不需要手动 return 它):
from fastapi import FastAPI, Response
app = FastAPI()
@app.post("/login/")
async def login(response: Response, username: str):
response.set_cookie(
key="session_id",
value="s-123",
max_age=3600, # 有效期(秒)
httponly=True, # 禁止 JS 读取,防 XSS 窃取
secure=True, # 仅 HTTPS 发送
samesite="lax", # 防 CSRF:跨站请求不携带
path="/",
)
return {"msg": f"{username} 已登录"}也可以直接 return JSONResponse(...) 时在其上调用 set_cookie。三个安全标志位是面试高频题:
| 标志 | 作用 |
|---|---|
httponly | 浏览器禁止 JavaScript 通过 document.cookie 读取,缓解 XSS 盗取会话 |
secure | 仅在 HTTPS 连接上发送 |
samesite | strict/lax/none 三档,控制跨站请求是否携带,缓解 CSRF |
删除 Cookie 用 response.delete_cookie(key)(内部就是设置一个过期时间为纪元的 Set-Cookie 头)。
8.4 典型场景:会话追踪与客户端信息
把本章知识组合成一个常见中间层需求——统计接口的客户端构成并维持简单会话:
import uuid
from fastapi import FastAPI, Cookie, Header, Response
app = FastAPI()
@app.get("/track/")
async def track(
response: Response,
visitor: str = Cookie(None), # 老访客的标识
accept_language: str = Header("unknown"), # 客户端语言偏好
referer: str = Header("direct"), # 来源页
):
if visitor is None:
# 新访客:签发一个不可预测的随机 ID
visitor = uuid.uuid4().hex
response.set_cookie(
key="visitor", value=visitor,
max_age=30 * 24 * 3600,
httponly=True, samesite="lax",
)
return {
"visitor_id": visitor,
"lang": accept_language.split(",")[0],
"referer": referer,
}Swagger UI 的坑
浏览器对 Cookie 有特殊的安全管控:在 /docs 的交互文档里填了 Cookie 参数点 Execute 也发不出去(JS 无法随意触碰 Cookie)。测试 Cookie 参数请用 curl -b 参数或 Postman。
生产级会话管理不要手写 Cookie 逻辑,应使用第 14 章的 OAuth2/JWT 方案或 Starlette SessionMiddleware;本章的手动方式适合理解原理和小型内部工具。
8.5 本章小结
Cookie/Header与Query/Path同源同能力,必须显式声明否则会被当作查询参数;Header默认把参数名的_转成-,convert_underscores=False可关闭,但要小心代理丢头的兼容性问题;- 写 Cookie 靠注入
response: Response后调用set_cookie,httponly + secure + samesite是安全三件套; - 重复头用
List[str]接收; /docs页面无法真正发送 Cookie,联调要用 curl/Postman。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. 参数 user_agent: str = Header(None) 会去读取哪个请求头?
2. set_cookie 中设置 httponly=True 的目的是?
3. 为什么在 /docs 交互界面里填写 Cookie 参数后点 Execute 收不到值?
4. 客户端连续发送两个同名头 X-Token: a 和 X-Token: b,参数写成 x_token: str 时会发生什么?
🛠️ 动手实践
- 实现
/theme/接口:GET 读取themeCookie 返回当前主题色,POST 接收theme查询参数并写入themeCookie(有效期一年、httponly)。 - 写一个
/debug-headers/接口,用Request.headers直接遍历打印全部请求头,对比"声明式读取"与"字典式读取"两种风格。 - 给第 7 章的上传接口加上限流雏形:读取
X-Forwarded-For头识别来源 IP(注意多级代理时它是逗号分隔列表,取第一个),同一 IP 一分钟内最多上传 10 次(内存计数即可)。
输入参数的最后一块拼图完成。下一章进入第 9 章 · 错误处理与自定义异常。