FastAPI Header 参数完全指南:声明、下划线自动转换与重复请求头的处理
Header(请求头/报头)参数用于从 HTTP 请求中提取元数据,例如 User-Agent、认证令牌 X-Token 或自定义扩展头。本指南以 docs/es/docs/tutorial/header-params.md(及对应的英文原版 docs/en/docs/tutorial/header-params.md)为核心脉络,结合 FastAPI 源码仓库中的真实实现与示例,系统讲解如何用 Header 声明请求头参数、FastAPI 如何自动把 Python 变量名的下划线转换为 HTTP 标准的连字符,以及如何接收出现多次的重复请求头。读完后,你将能在自己的 FastAPI 接口中正确、优雅地处理各类 HTTP 请求头,并理解其背后的运行机制。
Header 与 Query、Path、Cookie 的"家族关系"
FastAPI 允许你用与定义 Query、Path、Cookie 参数完全相同的方式定义 Header 参数。三者都用于从 HTTP 请求的不同位置取值:
Path:从 URL 路径中取值;Query:从查询字符串(?key=value)中取值;Cookie:从请求的 Cookie 中取值;Header:从 HTTP 请求头中取值。
从源码可以印证这一"同族"关系:在 fastapi/params.py 中,Header 是 Param 的子类,与 Path、Query、Cookie 等共享同一个基类,并通过 in_ = ParamTypes.header 指明其数据来源是 HTTP 请求头。
需要特别记住的一点是:当你 from fastapi import Header 后所拿到的 Header,实际上是一个函数而非类本身。在 fastapi/param_functions.py 中可以看到,Header() 等名字是返回特殊类实例的工厂函数。这也是为什么文档与源码中会出现 Header 这种"首字母大写、写法却像函数调用"的风格——日常使用中的 Header()、Query()、Path() 都是函数调用,它们在背后替你构造对应的参数声明对象。
提示:HTTP 请求头默认不会被当作
Query参数解析。如果不使用Header来声明,FastAPI 会把普通函数参数误判为查询参数(从源码看,位于 fastapi/dependencies/utils.py 的默认逻辑是:当参数属于Param基类且未明确指定来源in_时,一律归入ParamTypes.query)。因此,声明请求头必须显式使用Header。
第一步:导入 Header
按照 FastAPI 官方教程(对应英文原版 Header Parameters),首先导入 Header:
from typing import Annotated
from fastapi import FastAPI, Header
app = FastAPI()
第二步:声明 Header 参数
声明 header 参数的结构与 Path、Query、Cookie 完全一致:你可以设置默认值,也可以叠加所有额外的校验(gt、ge、min_length、pattern 等)与标注参数(title、description、deprecated、alias 等)。
仓库中的官方示例 docs_src/header_params/tutorial001_an_py310.py 演示了用 Annotated 语法声明一个可选的 user_agent 请求头:
from typing import Annotated
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(user_agent: Annotated[str | None, Header()] = None):
return {"User-Agent": user_agent}
发送请求后,返回体中即可看到收到的 User-Agent 值:
$ curl http://127.0.0.1:8000/items/ -H "User-Agent: my-test-agent"
{"User-Agent":"my-test-agent"}
对于不使用 Annotated 的老式写法,FastAPI 也提供了兼容路径——同目录下的 docs_src/header_params/tutorial001_py310.py 展示了通过 Header(default=None) 直接指定默认值的方式:
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(user_agent: str | None = Header(default=None)):
return {"User-Agent": user_agent}
两种写法在功能上等价;Annotated 是当前 FastAPI 官方文档推荐的首选方式,因为它把"类型"与"参数元数据"绑定在同一声明中,语义更清晰,也便于后续在类型层面做静态检查。
从实现层面看,fastapi/param_functions.py 中 Header 函数提供的 convert_underscores 等关键字参数,本质上都会传递给 fastapi/params.py 的 Header 类构造器,再经由统一的 Param 基类完成默认值、校验规则、别名等信息的组装,最终在依赖注入阶段被解析成真正的请求字段。
自动转换:下划线 _ 如何变成连字符 -
Header 相比 Path、Query、Cookie 多了一项独特能力——下划线到连字符的自动转换,这正是处理 HTTP 请求头时最容易踩坑也最实用的一环。
背景知识是这样的:绝大多数标准 HTTP 请求头都以连字符(hyphen,即减号 -)分隔单词,例如 User-Agent、X-Token、Accept-Language。但在 Python 中,user-agent 这样的变量名是非法标识符——你无法写出 def read(user-agent: str) 这样的代码。
于是 FastAPI 默认提供了转换规则:把 Python 参数名中的下划线 _ 替换为连字符 -,再用于匹配真实的 HTTP 请求头。反过来,因为 HTTP 请求头本身不区分大小写,你可以放心用 Python 惯用的 snake_case(如 user_agent)声明参数,而不必刻意写成 User_Agent 或 User-Agent 这类大写形式。
也就是说,上面示例中的参数 user_agent 会被自动解释为请求头 User-Agent。
转换发生在哪里?
这一逻辑落在请求解析与 OpenAPI 文档生成两个层面:
-
请求解析阶段:在 fastapi/dependencies/utils.py 中可以看到核心逻辑——只要该参数属于 Header 且没有显式指定
alias,并且convert_underscores为真,FastAPI 就会执行:alias = param_name.replace("_", "-") field_info.alias = alias即用"下划线替换成连字符"的结果作为真正从请求头中取值的别名。
-
OpenAPI 文档生成阶段:在 fastapi/openapi/utils.py 附近,生成 API 文档时同样会遵循
convert_underscores标志,用转换后的连字符名称记录请求头,保证 Swagger UI / ReDoc 中展示的 header 名与真实 HTTP 协议一致。
因此,"在 Python 代码里写 user_agent,在 HTTP 请求里发 User-Agent"这一双向心智模型是完全成立的,不需要你手动做任何大小写或连字符换算。
需要时关闭转换:convert_underscores=False
如果出于某些原因你必须原样保留下划线(例如某个非标准的自定义请求头本身就叫 strange_header,带下划线),可以把 Header 的 convert_underscores 参数设为 False。
官方示例 docs_src/header_params/tutorial002_an_py310.py 演示了这一用法:
from typing import Annotated
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(
strange_header: Annotated[str | None, Header(convert_underscores=False)] = None,
):
return {"strange_header": strange_header}
此时只有真正发送名为 strange_header(含下划线)的请求头才能取到值:
$ curl http://127.0.0.1:8000/items/ -H "strange_header: hello"
{"strange_header":"hello"}
如果关闭了转换却仍发送 Strange-Header(连字符形式),该参数将取不到内容。
关闭转换前必读的警告
FastAPI 官方文档特别提醒:在将 convert_underscores 设为 False 之前要三思。因为部分 HTTP 代理和服务器会拒绝或丢弃名称中带下划线的请求头——下划线头并非 HTTP 规范中的标准实践,一些中间件(如老旧的 WSGI 服务器、部分负载均衡器)会对它们做特殊处理甚至直接拦截。如果线上环境由这类代理/服务器把关,使用带下划线的请求头可能导致请求行为与本地开发不符。因此默认的连字符转换不仅是为了代码合法性,更是一种贴近真实 Web 部署环境的务实设计。
另外补充一个细节:仓库测试 tests/test_query_cookie_header_model_extra_params.py 中专门覆盖了"开启转换时优先匹配连字符头"与"关闭转换时拒绝下划线头"的行为,验证了 convert_underscores 在 Header 模型层面同样生效(含 Header(convert_underscores=False) 的用法)。这与教程中把该选项用在单参数声明上的方式是同一套机制在不同层级的体现。
处理重复的请求头:使用 list 类型
HTTP 协议允许同一个请求头出现多次,例如客户端连续发送两条 X-Token。FastAPI 支持这种"重复头(duplicate headers)"场景:只要把参数类型声明为 list,FastAPI 就会把重复头的所有值收集成一个 Python 列表。
官方示例 docs_src/header_params/tutorial003_an_py310.py 演示了如何声明一个可能出现多次的 X-Token:
from typing import Annotated
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(x_token: Annotated[list[str] | None, Header()] = None):
return {"X-Token values": x_token}
向该接口同时发送两条 X-Token 请求头:
X-Token: foo
X-Token: bar
FastAPI 会返回(注意教程示例中列表内元素顺序是 "bar"、"foo",与请求发送顺序相反,这正是 HTTP 库/服务器底层合并重复头时的常见表现):
{
"X-Token values": [
"bar",
"foo"
]
}
几点实战提示:
- 若请求中只出现一次该头,你会得到只含一个元素的列表;若未发送该头且声明了默认值
None,则取值为None; - 类型标注可结合元素类型做进一步约束,例如
list[int]会对每个值分别做类型转换与校验; - 如果重复头的顺序对业务逻辑敏感(例如多个签名令牌需要按序验证),建议在客户端按固定顺序发送,并在测试中验证框架行为是否符合预期,因为不同 ASGI 服务器在合并重复头时未必保持原始顺序。
与 Query / Path 参数相同的能力可以复用
由于 Header 与 Query、Path、Cookie 共享 Param 基类,凡是你在查询参数、路径参数中用过的声明能力,Header 参数几乎都能无缝使用,包括但不限于:
- 默认值与可选性:
Header(default=None)或str | None表示可选头; - 数值/长度校验:
gt、ge、lt、le、min_length、max_length、pattern; - 文档元数据:
title、description、deprecated; - 显式别名:当你想要的名字无法在
Header自动转换下得到时,可以直接传alias(一旦提供显式alias,FastAPI 会优先使用它,不再执行下划线转换); - 在 OpenAPI / Swagger UI 中自动呈现:声明过的 header 会出现在接口文档的请求参数列表中,方便前端联调。
小结
- 声明 Header 参数要显式使用
Header,否则参数会被当作Query处理; - 写法上可选用
Annotated[str | None, Header()](推荐)或str | None = Header(default=None); - 默认情况下 FastAPI 会把参数名中的下划线
_自动转换为 HTTP 请求头的连字符-,因此写user_agent即对应User-Agent,且无需担心大小写; - 确需保留下划线时,设置
Header(convert_underscores=False),但要警惕部分代理/服务器对下划线请求头的限制; - 需要接收出现多次的请求头时,把参数类型声明为
list[...],FastAPI 会以 Python 列表形式返回全部值。
无论你的接口要读取 User-Agent 做客户端识别、校验 X-Token 做鉴权,还是解析各类自定义扩展头,掌握了 Header 参数与自动转换规则之后,都可以用最贴近 Python 习惯的代码把它们稳妥地接入 FastAPI 路径操作函数中。要深入了解 Header 之外的其他参数类型,可继续阅读 Query 参数教程 与 Path 参数教程(同一教程系列的相邻章节),它们共享本指南介绍的这套声明体系与校验模型。
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