HTTP/1.1协议允许Header字段名含下划线,但aiohttp、httpx等异步客户端默认禁用下划线,因其易与CGI环境变量混淆且反向代理(如Nginx)常过滤或转换,故强制要求转为短横线分隔格式(如X_Api_Key→X-Api-Key)。

Header 字段名含下划线或大小写混用,会导致 aiohttp、httpx 等异步客户端直接抛出 InvalidHeader 或静默丢弃字段——这不是服务端问题,是客户端强制校验所致。
为什么 async HTTP 客户端拒绝下划线 Header?
HTTP/1.1 协议规范(RFC 7230)明确要求 Header 字段名只能包含 token 字符:即 ASCII 字母、数字、! # $ % & ' * + - . ^ _ ` | ~。但 aiohttp(v3.8+)和 httpx 默认启用严格模式,**主动禁用下划线 _**,因其在 CGI 环境中易与环境变量混淆,且多数反向代理(如 Nginx)会将 X-My_Header 自动转为 X-My-Header 或直接过滤。
常见报错:
InvalidHeader: Invalid header name 'X-Api_Key'
或请求发出后服务端收不到该 Header。
立即学习“Python免费学习笔记(深入)”;
修复方式:统一转为短横线分隔 + 首字母大写
把 X_Api_Key → X-Api-Key,Content_Type → Content-Type,这是唯一被所有主流异步客户端接受的格式。
-
aiohttp的headers参数只接受dict或CIMultiDict,键必须是合法 token;传入含下划线的字符串会立即 raise -
httpx允许下划线但会自动转换为短横线(例如X_Api_Key变成X-Api-Key),但行为不可控,不建议依赖 - 若从配置文件或用户输入读取 Header 名,务必在构造请求前做标准化处理:
def normalize_header_key(key: str) -> str:
return key.replace('_', '-').title()
注意:.title() 对连字符后字母生效,"x_api_key".title() → "X_Api_Key"(仍有下划线),所以必须先 replace 再 title。
特殊 Header 如 Authorization 和 Cookie 怎么处理?
它们本身不含下划线,但容易因拼写或空格出错:
-
Authorization拼成authorization或Authroization:客户端不报错但服务端拒收 -
Cookie值里若含未编码的空格或分号,httpx会截断,aiohttp可能直接抛InvalidHeaderValue -
User-Agent中若含换行符(\n/\r),某些版本aiohttp会静默丢弃整个 Header
建议对敏感 Header 值做基础清理:
headers = {
"Authorization": f"Bearer {token.strip()}",
"Cookie": cookie_str.replace('\n', '').replace('\r', ''),
"User-Agent": user_agent.replace('\n', ' ').replace('\r', ' ')
}
调试时如何确认 Header 是否真正发出?
不要只看代码里的 headers=... 字典——它可能被中间件修改或被客户端标准化覆盖。
- 用
aiohttp时,在ClientSession中启用日志:logging.getLogger("aiohttp.client").setLevel(logging.DEBUG),会打印实际发出的 raw headers - 用
httpx时,加httpx.HTTPTransport(verify=False)并捕获request.headers实例(它是Headers类型,支持遍历) - 最可靠的是抓包:
tcpdump -i lo port 8000或用mitmproxy查看原始 HTTP 请求流,确认 wire 上的 Header 名是否含下划线或大小写异常
Header 标准化不是“写对就行”,而是客户端强制执行的协议层约束;漏掉一个下划线或空格,请求就可能无声失败——尤其在微服务间调用时,错误常被归因为下游超时而非 Header 本身。


















