FastAPI 设置响应 Cookie 的两种方式:`Response` 参数注入与直接返回 `Response` 的完整实战指南
导读:在 FastAPI 中为 HTTP 响应添加
Set-Cookie,官方文档 docs/pt/docs/advanced/response-cookies.md(各语言版本同源)给出了两条并行的官方路径——在路径操作函数中声明Response类型的参数,由框架将参数中的 Cookie/Header/状态码"回填"进最终响应;或者直接构造并返回一个携带 Cookie 的Response对象。读完本文,你将掌握两种方式各自的代码写法、适用边界、与response_model的协作关系,以及背后的依赖注入与响应合并实现原理,并配合本仓库的源码与测试用例进行验证。
在 FastAPI 应用中,"设置响应 Cookie"是登录态下发、会话跟踪、埋点标识等场景的基础操作。与多数 Web 框架需要访问全局响应对象不同,FastAPI 的 Cookie 写入依赖其独有的依赖注入体系与可组合的响应对象设计,且官方文档刻意将用法区分为两种风格。下文先讲与"返回值模型"天然兼容的 Response 参数注入法,再讲完全掌控输出的直接返回法,最后结合仓库实现源码剖析两者在内部是如何被处理的。
一、使用 Response 参数:在"临时响应"上设置 Cookie
官方文档首先推荐的方式是:在路径操作函数中声明一个类型为 Response 的参数,然后在函数体内对这个由框架注入的响应对象调用 set_cookie()。
参考 docs_src/response_cookies/tutorial002_py310.py 中的官方示例:
from fastapi import FastAPI, Response
app = FastAPI()
@app.post("/cookie-and-object/")
def create_cookie(response: Response):
response.set_cookie(key="fakesession", value="fake-cookie-session-value")
return {"message": "Come to the dark side, we have cookies"}
# 启动服务后请求验证
curl -i -X POST http://127.0.0.1:8000/cookie-and-object/
关键要点如下:
- 返回体与响应元数据解耦:函数依然像平常一样
return {"message": ...}(也可以返回数据库模型、Pydantic 模型等任意对象),无需在返回值中拼接 Cookie 信息。对response参数的set_cookie()调用与返回值互不干扰。 response_model依旧生效:如果你同时声明了response_model,它仍会被用于过滤与转换最终返回的对象。也就是说,类型过滤、字段裁剪、序列化照常进行,Cookie 只是被"附加"到最终响应之上。- 内部机制:FastAPI 会把这个注入的
Response当作临时响应容器,提取其中的 Cookie(同样也包括自定义 Header 与状态码),随后将其合并进"包含你返回的值、并经过response_model过滤后的最终响应"。因此两者并不会互相覆盖或丢失。 - 可扩展性:同样的技巧也能用在依赖中——在依赖函数里声明
Response参数并设置 Cookie(或 Header),那么所有依赖该子依赖的路由都会自动带上这些 Cookie,非常适合做统一的会话/埋点逻辑。
依赖注入场景:在依赖中统一下发 Cookie
from fastapi import Depends, FastAPI, Response
app = FastAPI()
def set_session_cookie(response: Response):
response.set_cookie(key="session", value="s3cr3t", httponly=True)
@app.get("/items/")
def read_items(_: None = Depends(set_session_cookie)):
return [{"item": "Portal Gun"}, {"item": "Plumbus"}]
依赖中注入 Response 的原理与路径操作函数中完全一致,均由 FastAPI 的依赖求解器统一处理,因此可以天然组合出"鉴权依赖负责下发会话 Cookie"这类结构。
二、直接返回 Response:在代码中创建携带 Cookie 的响应
另一种官方给出的方式是绕开依赖注入,在代码里显式构造一个响应对象、设置 Cookie 后直接返回。参考 docs_src/response_cookies/tutorial001_py310.py 的官方示例:
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.post("/cookie/")
def create_cookie():
content = {"message": "Come to the dark side, we have cookies"}
response = JSONResponse(content=content)
response.set_cookie(key="fakesession", value="fake-cookie-session-value")
return response
curl -i -X POST http://127.0.0.1:8000/cookie/
# 响应头中应出现: Set-Cookie: fakesession=fake-cookie-session-value
这套流程的通用写法是:
- 先按照直接返回 Response 一节的方法创建任意响应对象(
JSONResponse、HTMLResponse、RedirectResponse或自定义Response子类均可); - 调用该对象的
set_cookie()写入 Cookie; - 将该对象直接作为路径操作函数的返回值返回。
直接返回时的两点重要提醒
官方文档专门给出提示,直接返回响应对象与使用 Response 参数存在本质差异,务必注意:
- 不再经过
response_model过滤:当你直接返回Response时,FastAPI 会原样直接返回它,跳过正常的响应序列化与response_model数据过滤链路。因此必须自己保证数据形态正确——例如返回JSONResponse时,其content必须是可被 JSON 序列化的数据;同时不要携带任何本应由response_model剔除的敏感或多余字段。 - Cookie 设置在响应对象上:因为响应已经由你完整掌控,设置 Cookie 需要直接在该对象上调用
set_cookie(),而不是依赖参数注入。
三、set_cookie() 可配置的参数选项
两种方式最终都汇聚到 Response.set_cookie() 方法。下表汇总了该方法的关键参数(以当前仓库所依赖的 Starlette 响应基类实现为准,该对象同样可通过 fastapi.responses 导入):
| 参数 | 含义 | 典型取值 |
|---|---|---|
key |
Cookie 名称 | 任意字符串,如 "session" |
value |
Cookie 值(默认空串) | 字符串;复杂值建议先编码 |
max_age |
过期时间(秒),或 datetime.timedelta |
如 1800 表示 30 分钟会话 |
expires |
过期时间点(datetime 或 int 时间戳) |
与 max_age 二选一使用 |
path |
Cookie 生效路径(默认 /) |
如 "/app" |
domain |
生效域名 | 如 "example.com" |
secure |
仅 HTTPS 传输 | True |
httponly |
禁止 JavaScript 读取(防 XSS 窃取) | True |
samesite |
跨站策略:"lax" / "strict" / "none" |
如 "lax" |
一个带完整属性的调用示例:
response.set_cookie(
key="fakesession",
value="fake-cookie-session-value",
max_age=1800,
path="/",
secure=True,
httponly=True,
samesite="lax",
)
官方文档指出,更多参数与选项的完整列表以 Starlette 的 set-cookie 说明为准;其底层行为等同于标准 HTTP 的 Set-Cookie 响应头。
四、源码级剖析:Cookie 是如何"回到"最终响应的
在仓库中,Response 参数注入的本质是 FastAPI 依赖求解器的一项约定。位于 fastapi/routing.py 的路由处理逻辑会针对这一约定做专门处理,使两类返回风格在底层走不同但各自正确的链路:
4.1 直接返回 Response 时
fastapi/routing.py 中执行完端点函数(run_endpoint_function)后,会对返回值做类型判断。若 raw_response 本身已是 Response 实例,则直接采纳它作为最终响应,仅在背景任务为空时把依赖中累积的 background_tasks 挂上去——可见 Cookie、状态码、响应体完全由你构造的对象决定,这也解释了为何直接返回时 response_model 不再介入。
4.2 返回普通对象时(Response 参数路径)
若端点返回的是普通数据(dict、模型等),FastAPI 会调用 _build_response_args 收集由依赖求解阶段产生的状态码、Header 与 Cookie 等"响应参数",据此构造出承载序列化结果(serialize_response,必要时走 Pydantic 直出 JSON 字节的快路径)的最终响应对象。其中,从注入的临时 Response 上提取的原始响应头(含 Set-Cookie)会被合并进最终响应。这正是文档所述"临时响应中设置的 Cookie/Header/状态码被放入最终响应"的实现落点。
4.3 导入路径的技术细节
官方文档的技术细节注明:fastapi.responses 中的响应类与 starlette.responses 一致,仅是出于便利而对开发者的再导出。对照 fastapi/responses.py 的源码可见:
from starlette.responses import Response as Response # noqa
from starlette.responses import JSONResponse as JSONResponse # noqa
# ... FileResponse / HTMLResponse / PlainTextResponse / RedirectResponse / StreamingResponse 同理
也就是说,多数响应类型直接来自 Starlette,因此 from fastapi.responses import Response 与 from starlette.responses import Response 等价;而由于 Response 常被用来设置 Header 与 Cookie,FastAPI 特意在 fastapi.Response 顶层位置也提供了它。代码中可以使用任意一种导入写法,效果相同。
五、用仓库测试用例验证两种写法
仓库为两个官方示例分别配备了端到端测试,可直接作为行为契约:
- tests/test_tutorial/test_response_cookies/test_tutorial001.py:对应
tutorial001_py310.py(直接返回JSONResponse)。测试断言POST /cookie/返回 200,JSON 体精确等于{"message": "Come to the dark side, we have cookies"},并且response.cookies["fakesession"]等于"fake-cookie-session-value"。 tests/test_tutorial/test_response_cookies/下的test_tutorial002.py对应tutorial002_py310.py(Response参数注入写法),同样验证Set-Cookie出现在最终响应中。
本地复现这些断言并不需要真实启动服务器,直接使用 FastAPI 自带的 TestClient(其底层基于 Starlette 的测试传输层)即可:
from fastapi.testclient import TestClient
from docs_src.response_cookies.tutorial001_py310 import app
client = TestClient(app)
def test_path_operation():
response = client.post("/cookie/")
assert response.status_code == 200, response.text
assert response.json() == {"message": "Come to the dark side, we have cookies"}
assert response.cookies["fakesession"] == "fake-cookie-session-value"
# 在仓库根目录执行
python -m pytest tests/test_tutorial/test_response_cookies/ -q
六、两种方式的选型建议
| 判断维度 | Response 参数注入 |
直接返回 Response |
|---|---|---|
| 返回值形态 | 任意对象(dict、模型等),保持 REST 风格 |
Response 实例本身 |
response_model |
仍然生效,会过滤/转换返回数据 | 不生效,完全由自己保证数据正确 |
| 典型场景 | 常规接口额外附加会话 Cookie;在依赖中统一下发 Cookie/Header | 需要完全控制响应内容、状态码、媒体类型与 Header(含跳转、文件、流式等) |
| 组合能力 | 可同时声明 response_model、自定义状态码、额外 Header |
借助 RedirectResponse、FileResponse、StreamingResponse 等子类获得丰富能力 |
若只是"返回 JSON 的同时顺手种一个 Cookie",优先使用 Response 参数注入,因为它让数据过滤、类型校验与 Cookie 设置各司其职;若你本就要构造 RedirectResponse、文件下载或流式响应等特殊响应,则直接在对象上调用 set_cookie() 再返回,是更自然的做法。两者的最终效果都等价于在 HTTP 响应中输出正确的 Set-Cookie 头,可在浏览器开发者工具或 curl -i 中确认。
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 StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00