← 中文资源

简体中文版本

用 curl 请求头实验排查 406 错误

对 Accept 头做逐项二分实验,找出服务器拒绝的具体媒体类型,再决定该改客户端还是改服务端。

阅读英文原文

目标

406 表示服务器理解了请求,但拒绝以你提供的任何媒体类型返回表示。复合 Accept 头里混着多个类型和参数时,没人知道到底是哪一项触怒了服务器。本食谱把 Accept 头逐项拆开做二分实验,让每个媒体类型单独投票,把猜测变成一行明确的结论,并据此判断该修客户端还是修服务端。

步骤要点

  1. 先用最简单的 Accept: application/json 建立基线,返回 200 即排除路由与鉴权问题,把范围收窄到协商逻辑。
  2. 把复合头拆成多个候选媒体类型(含带参数与不带参数的变体),逐个请求并打印状态码,通常恰好一项返回 406。
  3. 用 HEAD 请求查看服务器真实提供的 Content-Type 与是否有 Vary: Accept,区分“有意的协商”与“中间件误伤”。
  4. 根据契约归属修复:客户端不该要的就删掉该头;服务端承诺的就正确注册解析器(对比 MIME 类型时先比类型再比参数),修复后用原始复合头复测确认 200。

排错提示

每种类型单独都 200 但复合头 406,说明解析器处理不了列表参数,先拆请求或修解析器;只有过网关才 406 是网关改写或按更严格白名单校验 Accept,用旁路请求对比源站实际收到的头再对齐白名单;返回 405 而非 406 说明是方法而非媒体类型问题,服务器先校验方法再协商,先改动词。

适用边界

curl 只能证明今天这台服务器的行为,RFC 对协商留有实现自由度,网关升级后需跨版本复验;很多服务器忽略 q 质量值的排序,不要依赖它做按优先级的协商。

代码示例

for mt in "application/json" \
         "application/json; charset=utf-8" \
         "text/html" \
         "application/vnd.api+json"; do
  printf "%-40s -> " "$mt"
  curl -s -o /dev/null -w "%{http_code}\n" \
    "https://staging.example.com/api/report" -H "Accept: $mt"
done
# application/json                        -> 200
# application/json; charset=utf-8         -> 406   <-- parameter mishandled
# text/html                                -> 200
# application/vnd.api+json                 -> 406   <-- type never registered