200 只说明「请求被理解、被正常处理」。 它不保证业务成功——业务失败也回 200 是最大的接口设计坑。
上一节分了家族。这一节看最常见的那个码,以及它被滥用的方式。
一、200 的边界
200 说的只是协议层的事:我收到了、我看懂了、我处理了,没出错。
它不保证内容是最新的(可能是缓存),也不保证业务成功。
最常见的滥用是「业务失败也回 200」:200 {"code": 500, "msg": "余额不足"}。
这在协议上没错,但在工程上把整条链路搞坏了。
二、滥用之后坏在哪
监控全瞎:看 5XX 比例永远是 0,故障发现不了。 重试逻辑失效:客户端不知道哪些该重试。 缓存乱套:200 可以被缓存,失败的响应被缓存下来更难查。 网关误判:负载均衡、CDN 都靠状态码决定怎么处理。 正确做法:失败就用 4XX/5XX,正文放业务码和说明。 如果非要在正文里带业务码,那 HTTP 状态码也必须跟着变。
三、200 与 201/204 的分工
201 表示「新建了资源」,必须带 Location 指向它。
204 表示「成功了但没内容」——它和 200 空正文的区别是语义:204 是「本来就没有」,
200 空是「有但空」。
这三个码别混用。客户端对 204 不会去解析正文,对 200 会;
某些框架对 200 空正文会抛解析异常,对 204 不会。
四、动手 Demo:发一个最普通的 200
服务端回 200 加一段 UTF-8 中文,把状态行、正文、Content-Type 逐项打出来。
def resp(raw, st):
return (u'HTTP/1.1 200 OK',
[(u'Content-Type', u'text/plain; charset=utf-8')],
u'正常内容'.encode())
实跑输出:
--------------------------------------------------------------------
200 OK:最常见的那一个
--------------------------------------------------------------------
状态行 HTTP/1.1 200 OK
正文 正常内容(12 字节)
Content-Type text/plain; charset=utf-8
整段响应 111 字节
200 只说明什么 请求被理解、被处理了,且结果正常
200 不说明什么 不保证内容是最新的、不保证业务逻辑成功
最常见的坑:业务失败也回 200。接口返回 `200 {"code": 500, "msg": "失败"}`——
这是把业务状态塞进了正文,HTTP 层的监控、缓存、重试全部失效。
正确做法是用 4XX/5XX 表达失败,正文只放细节。
正文 正常内容(12 字节)——四个汉字在 UTF-8 里是 12 字节,Content-Length 也必须是 12。
编码与长度必须一致,写成 4 客户端就只拿到一个汉字。
Content-Type ... charset=utf-8 里的 charset 不能省。省了浏览器会按猜测来解中文,猜错就是乱码。
整段响应 111 字节,正文只占 12——短响应的固定开销占了九成。
30 秒实验
- 把正文换成 4 个英文单词,重跑,看 Content-Length 变成多少。
- 故意把 Content-Length 写成 4,看客户端收到什么。
- 把 charset 去掉,用浏览器打开同一段字节,看会不会乱码。
常见误区
- 「200 表示业务成功」——它只表示协议层没出错。
- 「业务码写在正文就够了」——HTTP 状态码也得跟着变,否则监控和网关全瞎。
- 「200 空正文和 204 一样」——语义不同,客户端行为也不同。
自测三问(点开对答案)
1. 200 到底保证了什么? —— 请求被理解、被正常处理,没有协议层错误。业务是否成功它不管。 2. 「业务失败回 200」最大的危害是什么? —— 监控看不到错误率,重试和缓存也全都失效。 3. 新建资源该回哪个码? —— 201 Created,并且要带 `Location` 指向新资源。200 只表示有内容。下一节看「成功了但没内容」的那个码。
- ☐ (本节完,下一节 4.2.2 204 No Content)