406 是什么
406 Not Acceptable 表示目标资源确实存在,但服务端根据请求里的内容协商头(Accept、Accept-Language、Accept-Encoding 等)挑不出一种自己能够产出的表示,于是拒绝返回。规范定义在 RFC 9110 第 15.5.7 节。它既不是权限问题也不是路径问题,排查方向应当放在协商头与服务端支持格式的比对上。
基本信息
| 状态码 | 406 Not Acceptable |
|---|---|
| 语义 | 资源存在,但没有满足协商条件的表示 |
| 规范 | RFC 9110 §15.5.7 |
| 触发头 | Accept / Accept-Language / Accept-Encoding |
| 与缓存关系 | 响应是否能缓存取决于 Vary 声明的分歧维度 |
| 重试有无用 | 通常无效,必须换协商头或换请求格式 |
协商头与失败原因
| 请求头 | 作用 | 典型失败写法 |
|---|---|---|
| Accept | 声明可接受的媒体类型 | 只写自定义类型,服务端当前只支持标准 JSON |
| Accept-Language | 声明可接受的自然语言 | 只写 zh-CN,站点当前只提供英文 |
| Accept-Encoding | 声明可接受的压缩算法 | 只写 br,中间代理与源站只支持 gzip |
| Accept-Charset | 历史头,现代多被忽略 | 服务端仍在严格校验该头时会突然报错 |
| Vary | 响应头,声明按哪些维度区分缓存 | 缺失时不同协商结果互相覆盖,表现为时好时坏 |
典型使用场景
- 改版后接口报错:客户端照抄了旧文档里的自定义媒体类型,而服务端已改回标准 JSON
- 多语言站点只提供部分语种,客户端把未支持的语种硬编码进请求头
- 压缩协商异常:客户端把 br 当作唯一可接受编码,链路上却有一层只支持 gzip 的老设备
- 老框架自带严格内容协商逻辑,新增前端路由或更换调用方后开始命中
- 压测脚本与爬虫伪造了奇怪的 Accept 值,被严格实现直接拒绝
复现与对照排查
复现与对照排查
# 复现:声明一个服务端不支持的媒体类型
curl -si https://api.example.com/report -H 'Accept: application/vnd.super+json'
# HTTP/1.1 406 Not Acceptable
# 对照一:不带 Accept(等价于接受任意类型)
curl -si https://api.example.com/report
# 对照二:只改压缩协商再试
curl -si --compressed -H 'Accept-Encoding: gzip' https://www.example.com/服务端完全可以忽略 Accept 直接返回默认格式,此时不会出现 406;出现 406 说明这一层确实做了严格校验。定位时同时对比「不带该头」和「带常见值」两次请求,差异就是答案。若经网关访问正常、直连后端才报错,说明是网关做了格式兜底。
别把 406 与 415 混为一谈:406 是「我产不出你要的格式」,415 Unsupported Media Type 是「你发来的请求体格式我不认」。把 406 当成可重试错误会让客户端在重试里反复失败。