你以为HTTP状态码只是个数字?让我告诉你为什么你的API在设计层面就已经烂透了
写API五年了,看过的烂代码比你吃过的盐还多。今天不整虚的,直接撕开一个被所有人忽视的伤口——HTTP状态码。
别以为这是什么基础知识。百分之九十的所谓RESTful API,连HTTP协议的基本语义都没搞清楚,就在那边吹自己接口设计得多优雅。我今天就把这些遮羞布一把扯下来。
先问你一个问题:你的接口为什么返回200?
别急着回答。先想清楚:200代表什么?
不是操作成功了,不是数据查到了,不是服务器没崩。200的原始语义是——请求的语义被成功接收并理解,服务器没有发现任何错误。
这两个东西差别大了去了。你用GET查一个不存在的资源,返回200加个null JSON;你POST创建一条记录因为唯一键冲突失败了,返回200加个error字段。这些都是语义污染。
HTTP状态码体系不是随机生成的数字,它是一套完整的协议层语义描述系统。每个码的设计都有它的理由,你不用,要么是因为懒,要么是因为根本不懂。
404不只是没找到,它是客户端的错
这条得罪人。我见过太多这样的代码:
if (resourceNotFound) {
return ResponseEntity.status(404).body("资源不存在");
}
然后用户拿到这个响应会怎么想?哦,后端没找到这个资源。错。实际上,当你的API正确返回404的时候,问题出在客户端——请求的URL本身就是错的,或者路径参数有问题。
你想表达的数据查不到应该是什么?200,空结果集。或者更讲究一点,用204(无内容)来表示操作执行了,但没什么好返回的。
还有个更常见的反面教材:POST请求创建资源,成功后返回201 Created,但你知道201应该包含什么头吗?Location,指向新创建资源的URI。这不是可选的,这是标准。
400 Bad Request?你根本不知道自己在用
400大概是所有API里被滥用得最彻底的状态码。什么错误都往里面塞:参数校验失败、数据库连接超时、业务规则不满足、JSON格式错误……全TM是400。
来,记住这条规则:400的意思是这个请求的语法或语义有问题,客户端没法改改重试。
参数校验失败?有时候是400(格式完全不对),有时候是422(格式对了但语义不对,比如负数年龄)。数据库崩了?503。并发冲突?409。限流了?429。这些你都分清楚了吗?
我见过最离谱的是一个API把所有错误都返回400,然后在body里塞一个code字段来区分具体原因。这不叫REST,这叫用HTTP协议包装了一个RPC调用。
429不只是你太快了,它有深意
限流返回429。有几个人认真看过这个状态码的定义?
429的全称是Too Many Requests,RFC 6585给它加了一个配套头:Retry-After。你知道这个头是干嘛的吗?告诉客户端多久之后可以重试。
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
Content-Type: application/json
{"message": "请求过于频繁,请稍后再试", "retry_after": 3600}
有多少人的限流接口返回了Retry-After?没有。你让客户端怎么办?傻等30秒再试?暴力重试?这些都是低效的。正确实现限流,客户端应该能根据Retry-After精确等待,而不是靠猜测。
5xx不是你的免责金牌
很多人觉得5xx嘛,服务器的问题,不怪我。这话对了一半。
500 Internal Server Error,internal是什么意思?意思是服务器在执行请求的时候遇到了自己意料之外的错误。这通常意味着代码bug、依赖服务崩了、或者系统资源耗尽。
但503 Service Unavailable不一样。503的意思是服务器现在能响应,但暂时不可用——比如维护中、负载过高。它是临时性的,客户端应该重试。而500通常是不可恢复的,客户端重试没有意义(除非是幂等操作)。
你连这个都分不清,说明你对HTTP的理解还停留在200是成功、500是服务器崩了这个层面。
说个实战例子
我之前接手过一个接口,登录失败返回的是这样的:
HTTP/1.1 200 OK
{
"success": false,
"error": "密码错误",
"code": "PASSWORD_MISMATCH"
}
我当时差点把咖啡喷在屏幕上。用HTTP语义来描述,这应该是:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
{
"error": "invalid_credentials",
"message": "用户名或密码错误"
}
401表示需要认证或者认证失败,WWW-Authenticate头告诉客户端这是什么类型的认证。客户端程序看到这个状态码,就知道该清token、跳转登录页。这才是协议该有的样子。
为什么这套体系重要?
因为HTTP状态码不只是给人看的,更是给机器读的。CDN会缓存301/302但不会缓存200。网关会根据4xx做请求重试还是直接返回给用户。监控体系会根据5xx的出现频率判断服务健康状态。
你用200包装所有响应,就是在告诉所有这些基础设施:你自己判断。然后你又会奇怪为什么监控不准、为什么缓存失效、为什么重试策略不生效。
这不是吹毛求疵,这是代价。你现在省的那点事,将来都是债。
一个原则
送你一条原则,记住它能让你少走五年弯路:
状态码描述的是请求-响应这个交互的结果,不是你业务操作的结果。
业务上失败了不代表HTTP状态码是4xx,业务上成功了也不代表一定是200。资源没找到、用户没权限、请求格式不对、服务器过载——每一种情况都有它对应的标准状态码,不要发明轮子,不要用200+业务错误码这种四不像。
先把HTTP协议本身用对,再谈什么API设计优雅不优雅。连地基都没打牢,就别吹什么架构了。
行了,写完了。有收获的给我点个在看,没收获的……那就忍着吧。