FastAPI 设置 Response 响应头(Headers)的完整指南:`Response` 参数、直接返回与自定义 Header
**响应头(Response Headers)**是 HTTP 响应中承载元信息(语言、缓存策略、自定义业务标识等)的关键载体。本篇文章基于 FastAPI 官方高级用法文档 response-headers.md(源文档为多语言翻译版本之一,正文以英文原版 response-headers.md 为基准)整理而成,讲解在 FastAPI 中设置响应头的两种推荐方式,并结合仓库源码与测试用例说明其底层运行机制。读完本文,你将掌握:通过 Response 参数向"临时"响应对象写入头信息、在直接返回 Response 对象时附带 headers,以及如何让自定义 Header 在浏览器中被前端 JavaScript 读取。
概述:FastAPI 中设置响应头的两条技术路径
在 FastAPI 中,绝大多数场景下你并不直接构造 HTTP 响应——框架会根据你返回的对象(dict、模型等)自动完成序列化与响应构建。因此要"优雅地"给这类响应附加自定义 Header,官方提供了两种推荐写法,对应仓库 docs_src/response_headers 目录下的两个独立示例:
| 写法 | 代码示例 | 适用场景 |
|---|---|---|
声明 Response 参数并写入 headers |
tutorial002_py310.py | 返回普通对象(dict、模型),同时附带自定义头 |
直接返回带 headers 的 Response |
tutorial001_py310.py | 需要完全控制响应对象本身(含状态码、内容类型等) |
下面依次展开两种方式的具体写法、组合规则与底层实现。
方式一:在路径操作函数中声明 Response 参数
FastAPI 允许你在 path operation function 中声明一个类型为 Response 的参数(这与操作 Cookie 的方式完全一致)。声明之后,你就可以向这个"临时"(temporary)的响应对象写入 header。
完整的官方示例位于 tutorial002_py310.py:
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/headers-and-object/")
def get_headers(response: Response):
response.headers["X-Cat-Dog"] = "alone in the world"
return {"message": "Hello World"}
关键点逐条拆解如下:
Response来自哪里:这里的Response直接导入自fastapi顶层包。由于设置 headers 与 cookies 是高频操作,FastAPI 特意把Response暴露在fastapi.Response中供开发者直接使用(源码见 fastapi/init.py 中from .responses import Response,而 fastapi/responses.py 又将其复导出自starlette.responses)。- 返回值不受影响:在写入 header 之后,你依然可以像平常一样返回任意对象——
dict、数据库模型等。响应体的序列化照常进行。 response_model依旧生效:如果路径操作声明了response_model,它仍然会用于对返回值进行过滤与类型转换,不会因为声明了Response参数而被绕过。- headers 会被"搬运"到最终响应:FastAPI 会从那个临时响应中提取 headers(同时还有 cookies 与状态码),把它们合并进携带了你返回值的最终响应中,再经由
response_model过滤后发给客户端。
在真正返回 Response 对象(即"方式二")时,这一合并逻辑同样会执行:先从临时响应提取头信息并附加到最终响应之上,从而保证两种写法可以组合使用而不丢失任何 header。
在依赖项中声明 Response 参数
文档特别强调:Response 参数不仅可以用在路径操作函数中,也可以声明在依赖项(dependencies)里,并在依赖中设置 headers 与 cookies。这是实现"统一为一批接口附加公共头信息(如追踪 ID、公共响应头)"的推荐手段——依赖中写入的头信息同样会被合并进最终响应,因为整条依赖链共享同一个临时 Response 对象(机制详见下文源码分析)。
底层原理:临时 Response 的创建与头部合并
从源码结构可以还原出这一"魔法"的完整调用链,这也印证了文档中"temporary response"的说法:
- 创建临时响应:在 fastapi/dependencies/utils.py 的
solve_dependencies()中,当依赖解析开始时若未传入外部response,会创建一个全新的Response()实例:
if response is None:
response = Response()
del response.headers["content-length"]
response.status_code = None # type: ignore
这个对象一路向下传递给路径操作函数与所有依赖,因此你在函数或依赖里通过 response.headers[...] = ... 写入的内容,最终都会累积在这个临时对象上。
- 合并进最终响应:在处理完你的返回值之后,fastapi/routing.py(及其后针对不同返回分支的多处代码,如 L682、L704、L750)执行了关键的头部拼接:
response.headers.raw.extend(solved_result.response.headers.raw)
solved_result.response.headers.raw 正是临时 Response 上累积的全部 header 原始键值对,extend 把它们原样追加到最终响应上——这就是"文档里设置的 headers 最终出现在 HTTP 响应头中"的直接代码依据。
测试验证
仓库提供了针对上述示例的端到端测试,见 test_tutorial002.py:
def test_path_operation():
response = client.get("/headers-and-object/")
assert response.status_code == 200, response.text
assert response.json() == {"message": "Hello World"}
assert response.headers["X-Cat-Dog"] == "alone in the world"
测试同时断言了响应体({"message": "Hello World"})与自定义头 X-Cat-Dog 都正确返回,完整验证了"既能设置 header、又能正常返回序列化对象"的预期行为。运行该测试可执行:
pytest tests/test_tutorial/test_response_headers/
方式二:直接返回一个携带 headers 的 Response
另一种更直接的做法是:当你本来就要直接返回一个 Response 对象时,把 headers 作为构造参数一并传入。完整示例见 tutorial001_py310.py:
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.get("/headers/")
def get_headers():
content = {"message": "Hello World"}
headers = {"X-Cat-Dog": "alone in the world", "Content-Language": "en-US"}
return JSONResponse(content=content, headers=headers)
这里的关键是:
- 按文档 response-directly.md("直接返回 Response"专题)描述的方式构造响应,再把
headers作为额外参数传入响应类的构造函数; - 示例中一次性传入了两个头:自定义的
X-Cat-Dog以及标准的Content-Language: en-US(用于声明内容语言); - 任意合法的
Response子类(JSONResponse、HTMLResponse、PlainTextResponse等)都支持headers参数。
对应的测试 test_tutorial001.py 断言了响应体及两个 header 均正确返回:
def test_path_operation():
response = client.get("/headers/")
assert response.status_code == 200, response.text
assert response.json() == {"message": "Hello World"}
assert response.headers["X-Cat-Dog"] == "alone in the world"
assert response.headers["Content-Language"] == "en-US"
技术细节:fastapi.responses 与 starlette.responses 的关系
文档中的"Technical Details"提示明确说明了一个易混淆点:
- 你完全可以直接写
from starlette.responses import Response或from starlette.responses import JSONResponse; - FastAPI 之所以额外提供
fastapi.responses,纯粹是为了方便开发者——其中绝大多数 Response 类都直接来自 Starlette(见 fastapi/responses.py 中from starlette.responses import Response as Response的复导出); - 由于
Response常被用于设置 headers 与 cookies,FastAPI 也特意将其暴露在fastapi.Response。
换言之,两种导入路径等价,选择哪种只取决于你的代码风格偏好。本文两个示例恰好分别示范了这两种导入方式:方式一用 fastapi.Response,方式二用 fastapi.responses.JSONResponse。
自定义 Headers 与浏览器可见性(CORS expose_headers)
自定义业务头(Custom Headers)是响应头最常见的应用之一,例如上例中的 X-Cat-Dog。需要了解两条实践规则:
-
命名约定:自定义的专有 Header 习惯上使用
X-前缀命名(这一约定源自 HTTP 头字段的通用实践)。示例中的X-Cat-Dog、X-*系列即为此类。若头名是标准头(如Content-Language、Cache-Control),则直接使用标准名称。 -
浏览器可见性(关键陷阱):如果你设置了自定义 Header,并希望浏览器中的前端 JavaScript(如
fetch或XMLHttpRequest)能够读取到它,仅仅设置 header 是不够的——还必须在 CORS(跨域资源共享) 配置中把该头加入expose_headers参数。否则即便后端确实返回了该头,浏览器也不会把自定义头暴露给页面脚本。FastAPI 中配置方式为使用
CORSMiddleware(由仓库复导出自 Starlette,见 fastapi/middleware/cors.py):
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://example.com"],
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["X-Cat-Dog"], # 关键:把自定义头暴露给浏览器
)
更完整的 CORS 配置(含 allow_origins、allow_credentials 等参数的逐一说明)请参见本仓库的专题文档 CORS 指南(es) 与英文原版 CORS(en)。
快速上手:运行与验证
在本地验证上述两种写法,只需将对应示例保存为 main.py 后用 Uvicorn 启动:
uvicorn main:app --reload
- 访问
http://127.0.0.1:8000/headers/(方式二示例)可看到 JSON 响应体{"message": "Hello World"}; - 使用浏览器开发者工具或
curl -i查看响应头,即可在HTTP/1.1 200 OK段落中看到x-cat-dog: alone in the world(方式二还会附加content-language: en-US)。
两条路径的取舍总结如下:
- 需要"既自定义 header,又保留 FastAPI 自动序列化与
response_model过滤能力"→ 选用方式一(Response参数),它也是可在依赖项中复用、面向"给一批接口统一加头"场景的更优雅方案; - 需要完全掌控整个响应对象(自定义内容类型、状态码、渲染逻辑等)→ 选用方式二(直接返回
Response)。
无论选择哪种,都请牢记自定义 Header 的"最后一公里":若目标客户端是浏览器中的脚本,务必同步配置 CORS 的 expose_headers,否则这些头将无法被页面读取。
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 StartedRust0624
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