FastAPI Header 请求头参数详解:声明、自动下划线转换与重复请求头处理
HTTP 请求头(Headers)广泛用于传递认证令牌、客户端信息、追踪标识等元数据。FastAPI 提供与 Query、Path、Cookie 完全一致的 Header 参数机制,让你能用 Python 惯用的 snake_case 命名安全地提取像 User-Agent 这样的标准请求头,并支持对重复请求头的自动列表化处理。阅读本文后,你将掌握如何在 FastAPI 中导入并声明请求头参数、理解其将下划线自动转换为连字符(-)的工作原理、按需关闭该转换,以及接收同一请求头多次出现时的全部值。
本文主体对应仓库中的法语文档 docs/fr/docs/tutorial/header-params.md,示例代码均可直接运行,源码与测试目录中能找到完整的配套实现与验证用例。
什么是请求头参数,为什么需要 Header
HTTP 协议中,请求头(header)是请求行之后以 键: 值 形式携带的元数据,例如浏览器每次请求都会自动带上 User-Agent 与 Accept 等标准请求头,业务上还常用 X-Token、Authorization 等自定义头传递上下文。
在 FastAPI 中,声明请求头参数的方式与声明 Query、Path、Cookie 参数完全相同:把它作为路径操作函数的一个形参,并用 Header() 包裹类型注解即可。区别仅在于数据来源——Query 从 URL 查询字符串取值,Path 从 URL 路径模板取值,而 Header 从 HTTP 请求头中取值。
导入 Header
第一步是从 fastapi 中导入 Header,代码位于 docs_src/header_params/tutorial001_an_py310.py:
from typing import Annotated
from fastapi import FastAPI, Header
app = FastAPI()
如果你使用的是旧式写法(不依赖 Annotated),语法同样受支持,仓库中同时保留了 tutorial001_py310.py 等纯类型注解版本的等价示例。
声明 Header 参数
接下来按照与 Path、Query 和 Cookie 相同的结构声明请求头参数。你可以设置默认值,也可以附加校验与元数据参数。例如读取客户端发送的 User-Agent 请求头:
@app.get("/items/")
async def read_items(user_agent: Annotated[str | None, Header()] = None):
return {"User-Agent": user_agent}
这里声明了一个名为 user_agent、可选的 str 请求头参数:缺省时值为 None,传入时则回显其内容。
技术细节:
Header是Path、Query、Cookie的"姊妹"类,都继承自同一个公共基类Param。从源码看,这一继承关系定义在 fastapi/params.py#L303:class Header(Param),并通过in_ = ParamTypes.header标记参数属于 header 位置。同时要注意,当你从fastapi导入Query、Path、Header时,得到的实际上是返回特殊类的函数——真正的类定义见 fastapi/params.py,而 fastapi/param_functions.py#L701 中的Header()函数负责装配默认值、校验规则等参数并实例化返回。这种双层结构让你既能像调用函数一样书写参数,又能获得完整的类型检查支持。重要提醒:声明请求头参数时必须显式使用
Header(),否则 FastAPI 会把该形参当作普通的查询参数(query parameter)处理——因为在 fastapi/dependencies/utils.py 的默认逻辑中,未指定in_的Param会被归入 query 位置。这会造成数据永远无法从请求头中读到,接口行为与预期完全不符。
下划线到连字符的自动转换
Header 相比 Path、Query、Cookie 多提供了一项便利功能。绝大多数标准 HTTP 请求头的名称是用连字符(-)分隔的,如 User-Agent、X-Forwarded-For;但 Python 中变量名里带 - 是非法的,写不出 user-agent 这样的形参名。
因此,默认情况下 Header 会把形参名中的下划线 _ 自动转换为连字符 -,再据此去提取并记录(document)对应的请求头。同时,HTTP 请求头本身不区分大小写,所以你可以放心使用 Python 标准命名风格(即 snake_case)。例如声明形参 user_agent,实际匹配的是 User-Agent 请求头——这正是上一节示例能够工作的原因,无需把代码写成 User_Agent 这样生硬的名字。
这一转换并不只发生在提取阶段。从 fastapi/dependencies/utils.py#L524 附近的实现可以看到,当参数使用了 Header 且未显式指定 alias、convert_underscores 为真时,框架会执行 alias = param_name.replace("_", "-"),把转换后的名字作为别名用于解析与 OpenAPI schema 生成;在请求头来自 Headers 对象(如使用 Pydantic 模型聚合请求头)的场景中,fastapi/dependencies/utils.py#L793-L828 也实现了同样的 replace("_", "-") 逻辑并保持默认开启。
关闭自动转换:convert_underscores=False
如果由于某种原因你需要禁用下划线转连字符的自动转换,只需将 Header 的 convert_underscores 参数设为 False。示例见 docs_src/header_params/tutorial002_an_py310.py:
@app.get("/items/")
async def read_items(
strange_header: Annotated[str | None, Header(convert_underscores=False)] = None,
):
return {"strange_header": strange_header}
关闭后,名为 strange_header 的形参将原样匹配字面的 strange_header 请求头,而不会去找 strange-header。源码层面该选项的默认值定义在 Header() 函数签名(fastapi/param_functions.py#L761)与 Header 类构造器(fastapi/params.py#L316)中,均为 True,并被保存为字段属性供依赖解析阶段读取。
警告:在把
convert_underscores设为False之前请三思——某些代理服务器与 HTTP 服务器会禁止使用含下划线_的请求头,将其视为非法字段。此时若你仍希望用带下划线的请求头名,客户端发起的请求可能根本到不了你的应用。因此实践中更推荐保持默认的自动转换行为,让客户端发送连字符形式的请求头。
注意事项小结
- 默认启用转换:
user_agent→ 匹配User-Agent; convert_underscores=False:user_agent→ 匹配字面的user_agent;- 请求头大小写不敏感,但接收方通常以首字母大写的连字符形式发送(如
User-Agent); - 生产环境中与反代、网关等中间层协作时,优先使用连字符风格,规避下划线被拦截的风险。
处理重复的请求头
HTTP 允许同一个请求头多次出现——即同名请求头携带多个值。这在 X-Token、Accept-* 等场景很常见。FastAPI 通过在类型注解中使用列表来接收重复请求头的全部值:声明后,框架会把该请求头的所有值合并成一个 Python list 返回给你。
例如,声明一个可能出现多次的 X-Token 请求头,完整示例见 docs_src/header_params/tutorial003_an_py310.py:
@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,且因注解为 list[str],重复值会被收集为列表。若客户端同时发送两条同名请求头:
X-Token: foo
X-Token: bar
接口返回的结果会是:
{
"X-Token values": [
"bar",
"foo"
]
}
注意到列表中 bar 排在了 foo 前面,这与请求头的实际接收与解析顺序有关;作为调用方,不应依赖重复请求头的相对顺序。仓库配套测试 tests/test_tutorial/test_header_params/test_tutorial003.py 对上述行为做了逐项验证:不传时返回 None、传单个值时返回单元素列表、传两个值时返回两个元素的列表。
此外,你也可以用 list 表示法搭配可选语义(None 缺省),这样请求头完全缺失时返回 None,出现一次及以上时返回对应数量的列表,语义清晰且不抛错。
运行与验证
你可以把上述任一示例保存为本地文件并用 uvicorn 运行:
uvicorn main:app --reload
然后通过 curl 或直接在 http://127.0.0.1:8000/docs(Swagger UI)中测试请求头参数的效果。仓库中为每个示例都提供了对应的自动化测试,例如 tests/test_tutorial/test_header_params/test_tutorial001.py 会用 TestClient 发送 User-Agent 请求头,并断言返回体与 OpenAPI schema 中该参数以 "name": "User-Agent"、"in": "header" 的方式呈现。
总结
在 FastAPI 中声明请求头参数的完整套路与 Query、Path、Cookie 如出一辙,核心要点只有四条:
- 从
fastapi导入Header; - 在路径操作函数形参上用
Header()标记,并可用Annotated组合类型与默认值; - 无需为形参名中的下划线烦恼——默认情况下 FastAPI 会自动将其转换为连字符以匹配标准 HTTP 请求头,需要严格匹配字面名称时可设
convert_underscores=False(但要注意代理层对下划线请求头的限制); - 将类型注解声明为
list[...],即可接收同一请求头重复出现的多个值。
请求头参数本质上仍是 FastAPI"以类型注解驱动参数解析"设计哲学的一部分。想进一步了解请求头相关扩展能力,可继续阅读仓库中的查询参数(docs/en/docs/tutorial/query-params.md)、路径参数(docs/en/docs/tutorial/path-params.md)与 Cookie 参数(docs/en/docs/tutorial/cookie-params.md)等姊妹章节,理解它们如何共享同一套 Param 基类与校验体系。
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