这个系列写到这里,已经到第四篇了。前几篇我们把 HTTP 协议里最基础的部分过了一遍:1xx 的临时应答、2xx 的成功语义、3xx 的重定向,以及上一篇里 4xx 家族中几个最“常见”的状态码,像 400、401、403、404、405 这类,都拆开聊了聊。今天这篇,接着把 4xx 客户端错误状态码剩下的部分讲完,重点是我在实际开发、联调、排查日志时真正遇到过、并且最能折磨人的那些。
如果你是个后端开发、运维、测试,或者是在做爬虫、对接第三方 API 的工程师,这一篇应该会对你有用。因为 4xx 这个区间太特殊了:它不像 5xx 那样是服务端炸了,也不像 2xx 那样皆大欢喜,它说的是“你的请求有问题”,但很多时候问题并不像状态码字面上写的那么直白。比如同一个 400,有时候是 JSON 格式错了,有时候是 Content-Type 没对上,有时候甚至是网关层主动拦的。你如果只盯着状态码数字猜,永远猜不中真正原因。
这一篇的定位是延续系列的深入解读,所以不打算再把每个状态码都背一遍定义。我更想把它当成一份“排障手记”来写:哪些状态码看起来像、实际完全不一样,哪些状态码虽然冷门但一出现就是大事,以及当你真的看到了它们,下一步应该怎么查。
1. 先别急着查状态码,看看4xx家族的整体逻辑
1.1 系列前情:这一篇到底继续讲什么
上一篇把 4xx 里最“出圈”的几个讲掉了,比如 400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found、405 Method Not Allowed。但 4xx 区间从 400 到 499 有几十个状态码,上一篇只是开了个头。这一篇要补上的,是剩下的、在真实项目里出现频率同样不低,但很多人一看到就懵的状态码。
我先把这一篇要覆盖的列表摆出来:408 Request Timeout、409 Conflict、410 Gone、411 Length Required、412 Precondition Failed、413 Payload Too Large、414 URI Too Long、415 Unsupported Media Type、416 Range Not Satisfiable、417 Expectation Failed、418 I'm a teapot、421 Misdirected Request、422 Unprocessable Entity、423 Locked、428 Precondition Required、429 Too Many Requests、431 Request Header Fields Too Large、451 Unavailable For Legal Reasons。
看到这个列表别慌,并不是每个都需要你背下来。但有几个是“高频踩坑位”,比如 413、414、415、429 这些,在对接文件上传、URL 参数、限流策略时几乎绕不开。而像 421、422 这类,在 HTTP/2 和 RESTful API 设计里也越来越常见。剩下的属于“认识一下、遇到不慌”的冷门状态码,知道它代表什么语义就够了。
1.2 一张表看清本篇要讲的状态码
在逐个拆解之前,我先用一个表格把这一篇涉及的状态码、标准定义、以及最常见的触发场景列出来。这张表你可以直接存起来,排查问题的时候对着看:
| 状态码 | 标准定义 | 最常见触发场景 |
|---|---|---|
| 408 | Request Timeout | 客户端迟迟没把请求体发完,服务端等不及主动断开 |
| 409 | Conflict | 资源当前状态与请求冲突,常见于并发编辑、版本号不一致 |
| 410 | Gone | 资源曾存在但已被永久删除,和 404 有语义差异 |
| 411 | Length Required | 请求没带 Content-Length,但服务端必须要这个头 |
| 412 | Precondition Failed | If-Match / If-None-Match 等条件头校验失败 |
| 413 | Payload Too Large | 请求体超过服务端限制,文件上传最常见 |
| 414 | URI Too Long | URL 太长,尤其是 GET 请求拼接大量参数时 |
| 415 | Unsupported Media Type | Content-Type 不是服务端期望的格式 |
| 416 | Range Not Satisfiable | Range 请求的范围不对,断点续传经常碰到 |
| 417 | Expectation Failed | Expect 请求头里的预期无法满足 |
| 418 | I'm a teapot | 愚人节彩蛋,但协议里合法存在 |
| 421 | Misdirected Request | HTTP/2 下请求发到了错误的虚拟主机 |
| 422 | Unprocessable Entity | 请求格式正确,但业务语义校验失败 |
| 423 | Locked | 资源被锁定,常见于 WebDAV 场景 |
| 428 | Precondition Required | 服务端要求客户端必须带条件请求头 |
| 429 | Too Many Requests | 请求太频繁,触发限流 |
| 431 | Request Header Fields Too Large | 请求头太大,尤其 Cookie 过大时 |
| 451 | Unavailable For Legal Reasons | 因法律原因不可用 |
这张表只是“地图”,真正干活的时候,要结合响应头、响应体、服务端日志一起看。下面我把这里面最容易出问题的几组状态码,一个个拆开讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 高频疑难4xx逐个拆解
2.1 400 Bad Request:你以为“语法错”,其实是“你没按套路出牌”
400 是上一篇可能已经讲过的状态码,但我在这一篇里还愿意再花一段强调,是因为它太特殊了。特殊在哪?它可能是 4xx 里最“万能”的状态码——后端开发者拿不准该返回什么的时候,常常随手就丢一个 400。所以当你真的收到 400,第一反应不应该是“我语法错了”,而是“我没按服务端期望的格式出牌”。
我实际遇到过的 400,原因五花八门:请求体 JSON 里多了个逗号、Content-Type 写的 application/x-www-form-urlencoded 但 body 其实是 JSON、header 里带了个服务端不认识的非法字符、multipart 请求少了 boundary、文件上传时 filename 里有未转义的特殊字符,甚至还有一次是客户端对响应做过 gzip 解压,结果把原始请求也错误地 gzip 了,服务端一解压直接炸了。
排查 400 的思路,核心就一句话:不要猜,重放请求。用 curl 把原请求的 method、URL、header、body 完整复制出来,一条条裁剪字段,看删掉哪个字段之后状态码变了,元凶就找到了。这里有个细节我特意说一下:复制请求体的时候,要特别小心那些“不可见字符”,比如首尾空格、BOM 头、Windows 换行符 \r\n。很多 JSON 解析器碰
