400 是「请求本身不合法」的总兜底:语法错、缺必备首部、字段格式不对。原样重试一万次也没用。
3XX 说的是「去别处」,4XX 说的是「你错了」。 这一节是 4XX 里最常见的那个,也是排查时最该先看的。
一、什么情况会回 400
| 原因 | 例子 | 谁的问题 |
|---|---|---|
| 缺必备首部 | HTTP/1.1 没带 Host | 客户端/代理 |
| 请求行不合语法 | 方法名里有空格 | 客户端 |
| 首部行不合语法 | 没有冒号 | 客户端 |
| 长度对不上 | Content-Length 与实际不符 | 客户端 |
| URL 有非法字符 | 路径里有裸空格 | 客户端 |
| 共同点:还没走到业务逻辑就被拒了。所以 400 的错误日志里通常看不到任何业务信息,只有原始请求。 | ||
| Demo 里用四条畸形请求实测,每条服务端都能说出具体的拒绝原因——这也是好实践: | ||
| 400 的正文里写清哪里错了,别只说 bad request。 |
二、400 与 422 的分工
400 是「语法层面就不对」;
422 Unprocessable Entity 是「语法没问题,但语义不合法」(比如年龄填了 -5、邮箱格式不对)。
422 是 WebDAV 引入的,现在被 REST API 广泛借用。
很多框架默认把「校验失败」回 400,也不算错,但 422 更精确。
再往下还有 409 Conflict(状态冲突)、412 Precondition Failed(前置条件不满足)、
428 Precondition Required。这几个都属于「请求能被理解但没法执行」,只是原因不同。
三、一个容易被忽略的现实
线上偶尔会有一批来源集中的 400,比如某个运营商网关、某个老版本 App、某个爬虫。 那不是你的 bug,但你要去查——因为它在吃你的带宽和日志配额。 做法通常是:按 UA 或源 IP 聚合,看是不是同一个客户端在刷; 如果是自家老版本 App,就得考虑在网关层兜住,而不是让后端一直报错。 还有一条:网关回 400 和后端回 400 长得一样,但一个是 Nginx 拦的、一个是框架抛的。 看日志要先确认是谁回的——响应里有没有你的应用特有的首部,就是最快的判据。
四、动手 Demo:四条畸形请求,四种拒绝原因
服务端按 RFC 7230 的几条硬规矩校验:请求行必须三段、版本必须是 HTTP/x.y、首部行必须有冒号、 HTTP/1.1 必须有 Host。
def validate(raw):
lines = raw.split(b'\r\n\r\n')[0].decode('latin-1').split('\r\n')
parts = lines[0].split(' ')
if len(parts) != 3:
return u'请求行必须恰好三段,实际 %d 段' % len(parts)
for ln in lines[1:]:
if ln and ':' not in ln:
return u'首部行没有冒号:%s' % ln
if ver == 'HTTP/1.1' and 'host' not in hd:
return u'HTTP/1.1 缺 Host'
实跑输出:
--------------------------------------------------------------------
400 Bad Request:请求本身就不合法
--------------------------------------------------------------------
缺 Host(HTTP/1.1 必备) HTTP/1.1 400 Bad Request
拒绝原因 HTTP/1.1 缺 Host
请求行缺版本 HTTP/1.1 400 Bad Request
拒绝原因 请求行必须恰好三段,实际 2 段
方法名带空格 HTTP/1.1 400 Bad Request
拒绝原因 请求行必须恰好三段,实际 4 段
首部行没有冒号 HTTP/1.1 400 Bad Request
拒绝原因 首部行没有冒号:Host 127.0.0.1
400 的含义 语法错、框架错、字段缺——总之是请求本身有问题
400 该谁修 客户端。原样重试一万次也没用
最常见的真凶 缺 Host、Content-Length 与实际不符、URL 里有非法字符
线上偶尔会看到「某运营商网关发来的畸形请求」,
那种 400 在服务端日志里会有一大批同源 IP——不是你的 bug,但要去查。
四行全是 400 Bad Request,但拒绝原因各不相同:缺 Host、请求行只有 2 段、请求行有 4 段、
首部行没有冒号。
「方法名带空格」那条最容易被忽略:GE T / HTTP/1.1 看起来像「方法写错了」,
实际是请求行被切成了四段。
不同的校验器会给出完全不同的报错,这也是排查 400 时常觉得莫名其妙的原因。
这四条规矩都是 RFC 7230 明文写的。真实服务器会校验更多,比如首部名不能有空格、值不能用裸控制字符。
30 秒实验
- 把
Content-Length: 10但只发 5 字节,看服务端会不会超时后回 400。 - 在路径里塞一个裸空格(
GET /a b HTTP/1.1),看会落到哪条校验上。 - 用 curl 发一个不带 Host 的请求(
curl -H 'Host:'),看真实服务器回什么。
常见误区
- 「400 是服务端 bug」——它是客户端错,方向别搞反。
- 「400 说明业务逻辑有错」——它连业务逻辑都没走到,在解析阶段就被拒了。
- 「所有参数错误都该回 400」——语义错误更该用 422,冲突用 409。
自测三问(点开对答案)
1. 400 通常在哪一步被拒? —— 请求解析阶段,还没进业务逻辑。 2. HTTP/1.1 缺 Host 会怎样? —— 服务端应当回 400,因为 Host 是 HTTP/1.1 的必备首部。 3. 参数格式对但语义不合法该用哪个码? —— 422 Unprocessable Entity。400 是「你的请求写坏了」。下一节看「你没说你是谁」。
- ☐ (本节完,下一节 4.4.2 401 Unauthorized)