REST API 状态码最佳实践:2xx/4xx/5xx 怎么用才对
状态码用错,联调就变成了猜谜
接口设计里最被低估的一环,就是状态码。很多后端开发者把精力花在业务逻辑上,状态码却随手一写——成功就 200,失败就 500,参数不对也 200(把错误塞进响应体里)。结果前端拿到响应,得先扒开 body 猜”这到底是成了还是败了”。
状态码是 HTTP 协议给接口的第一层语义,用对了,前端不用猜、监控告警有据可依、排查方向一眼能定。这篇讲清楚 REST API 里 2xx / 3xx / 4xx / 5xx 的正确用法、常见误用与排查思路,并给一张状态码选用对照表。
查任何状态码的标准短语、含义与排查建议,可以用星点 HTTP 状态码查询,输入数字即查。
先立好框架:首位数字定责任
| 首位 | 分类 | 谁的问题 | 接口设计该怎么做 |
|---|---|---|---|
| 2xx | 成功 | 无 | 正常返回数据 |
| 3xx | 重定向 | 客户端配合 | 慎用,明确语义 |
| 4xx | 客户端错误 | 请求方 | 给出可操作的错误信息 |
| 5xx | 服务器错误 | 服务方 | 记录日志、告警 |
设计接口时心里要有这张表:4xx 是”你发错了”,5xx 是”我挂了”。很多接口的混乱,根源就是没守住这条边界。
2xx:成功状态码的正确用法
2xx 不是只有 200 一个选择。用对细分状态码,前端能少很多 if-else。
| 状态码 | 短语 | 什么时候用 | 典型场景 |
|---|---|---|---|
| 200 | OK | 通用成功,返回资源或结果 | 查询、更新成功 |
| 201 | Created | 创建了新资源 | POST 创建成功,最好带 Location 头指向新资源 |
| 202 | Accepted | 已接受,异步处理中 | 任务排队、异步作业 |
| 204 | No Content | 成功但无返回体 | 删除成功、无内容可回 |
200 vs 201:创建该返回哪个
一个常见的”半对半错”:创建资源一律返回 200。语义上,创建成功用 201 更准确,并配合 Location 头让客户端知道新资源在哪。虽然很多团队图省事全用 200 也能跑通,但状态码越准确,前后端契约越清晰——前端看到 201 + Location,就能直接跳转到新资源,不用再从 body 里猜 ID。
200 vs 204:删除该返回哪个
删除成功,返回 200 + 一个空 body,还是返回 204?规范上 204 No Content 更准确——它明确告诉客户端”成功了,但没有任何内容可返回”。前端看到 204 就知道不用解析 body。当然,如果删除成功后还想返回一些元信息(比如删除时间),那用 200 带 body 也没问题。关键是别 204 却还带了个 body,自相矛盾。
202:异步任务的正确姿势
如果接口是异步的——请求提交后,任务要排队慢慢执行——返回 202 Accepted 是最诚实的做法。它告诉客户端”我收下了,但还没做完”。配合后续的轮询或回调机制,客户端就知道该去查任务状态,而不是傻等。
200 不代表业务成功
另一个关键提醒:200 不代表业务成功。很多接口业务失败也返回 200,只在 body 里放 {"code": 500, "msg": "..."}。这种做法让 HTTP 层的监控、缓存、重试机制全部失效。如果一定要用”业务码”体系,也应当让 HTTP 状态码与业务结果保持一致——业务失败就返回对应的 4xx/5xx,把业务码放进 body 作为补充。
3xx:重定向,REST 里要慎用
3xx 在 REST API 里用得少,但一旦用错也麻烦。关键区分:
| 状态码 | 短语 | 语义 | 是否改变方法 |
|---|---|---|---|
| 301 | Moved Permanently | 永久重定向 | 可能把 POST 变 GET |
| 302 | Found | 临时重定向 | 可能把 POST 变 GET |
| 303 | See Other | 用 GET 查看另一个资源 | 明确改为 GET |
| 307 | Temporary Redirect | 临时重定向,保持方法 | 不改变 |
| 308 | Permanent Redirect | 永久重定向,保持方法 | 不改变 |
REST API 里最该记住的一点:301/302 可能被客户端把 POST 改成 GET,导致请求体和语义丢失。 如果重定向后必须保持原方法(比如一个 POST 被重定向到另一个 URL 继续处理),用 307/308。如果只是”创建成功,去用 GET 看结果”,用 303 更规范。
SEO 场景里 301 常用来做域名跳转、HTTP 转 HTTPS、旧 URL 迁移,这个在 HTTP 状态码查询 里也有联动说明(301/302/307/308 互相关联跳转)。
4xx:客户端错误,最容易用错的一类
4xx 表示”请求本身有问题”。它细分很多,用错了会让前端误导排查方向。
| 状态码 | 短语 | 正确用法 | 常见误用 |
|---|---|---|---|
| 400 | Bad Request | 请求格式错误、JSON 解析失败 | 什么都往 400 里塞 |
| 401 | Unauthorized | 未认证:凭证缺失或无效 | 误当成”无权限” |
| 403 | Forbidden | 已认证但无权限 | 误当成”未登录” |
| 404 | Not Found | 资源不存在 | 用 404 隐藏”无权限” |
| 405 | Method Not Allowed | 方法不被允许 | 用 404 或 500 代替 |
| 409 | Conflict | 资源冲突(如重复创建) | 用 400 代替 |
| 422 | Unprocessable Entity | 参数校验失败 | 用 400 代替 |
| 429 | Too Many Requests | 触发限流 | 用 500 代替 |
401 vs 403:最经典的一对混淆
- 401 Unauthorized:字面意思是”未授权”,但标准语义其实是**“未认证”**——你是谁都不知道(没带凭证、凭证无效或过期)。客户端看到 401,应该引导用户重新登录。
- 403 Forbidden:“已认证但无权限”——知道你是谁,但你不允许做这件事。客户端看到 403,重新登录也没用,是权限配置问题。
一句话:401 是”请先证明你是谁”,403 是”证明了你也不行”。 很多系统把”登录过期”和”权限不足”都写成 401 或都写成 403,导致前端无法区分该跳登录还是提示无权限。
400 vs 422:参数问题到底用哪个
- 400 Bad Request:偏”请求本身格式不对”——JSON 解析失败、协议层错误。
- 422 Unprocessable Entity:偏”格式对但业务校验不过”——字段格式合法,但值不满足业务规则(如年龄为负)。
现代 API 框架(尤其 Rails 及受其影响的生态)倾向于用 422 表示参数校验失败。二者没有绝对的谁对谁错,但团队内部要统一:要么都用 400,要么都用 422,别一半一半。
404 的一个反模式
有些后端故意用 404 隐藏资源的存在性(比如无权限访问别人的资源时也返回 404,避免暴露”这资源存在”)。这在隐私敏感场景有合理性,但会让前端误以为资源真的不存在。更规范的做法是:公开资源用 403 明确”无权限”,只有确实需要隐藏存在性时才用 404。
5xx:服务器错误,别把客户端错误往这里塞
5xx 表示”服务器处理出错”,责任在服务方。设计上它应该少而准。
| 状态码 | 短语 | 正确用法 | 常见误用 |
|---|---|---|---|
| 500 | Internal Server Error | 未捕获异常、无法分类的错误 | 把参数错误、限流也当 500 |
| 501 | Not Implemented | 服务器不支持该功能 | 极少用到,常被误用 |
| 502 | Bad Gateway | 网关从上游收到无效响应 | 与 504 混淆 |
| 503 | Service Unavailable | 服务不可用(过载/维护/降级) | 用 500 代替 |
| 504 | Gateway Timeout | 网关等待上游超时 | 与 502 混淆 |
几个设计要点:
- 参数错误、权限问题绝不该是 5xx。客户端发了个坏请求,你返回 500,等于把锅甩给自己,还污染了服务器错误率的告警。
- 503 用于主动降级和限流兜底。服务过载时主动返回 503(可带
Retry-After头),比把请求压到超时强得多。 - 网关场景分清 502 和 504:502 是”上游回了句听不懂的话”(无效响应),504 是”上游一直没吭声”(超时)。
错误响应体:状态码之外的第二层语义
状态码说清了”是哪类错”,body 还要说清”具体错在哪、怎么改”。好的错误响应体有这几个要素:
| 字段 | 作用 | 举例 |
|---|---|---|
| 错误码/类型 | 程序可识别的错误类型 | VALIDATION_ERROR、RATE_LIMITED |
| 消息 | 给人看的错误描述 | ”email 字段不能为空” |
| 字段级错误 | 具体哪个字段错了 | {"email": "格式不正确"} |
| 请求 ID | 便于查日志定位 | req_abc123 |
设计原则:状态码负责”大类”,body 负责”细节”,两者别打架。 状态码是 422,body 就别写”服务器内部错误”。一个请求 ID 尤其重要——生产环境排查时,用户报一个错,有请求 ID 就能在日志里秒定位。
幂等性与状态码的关系
状态码设计还和”幂等性”有关——同一个请求重复发,结果是否一致:
| 方法 | 幂等吗 | 相关状态码 |
|---|---|---|
| GET | 是 | 200 |
| PUT | 是 | 200 / 204 |
| DELETE | 是 | 204 / 404 |
| POST | 否(重复会创建多个) | 201 / 409 |
POST 不幂等,所以重复提交容易产生重复资源。这正好解释了 409 Conflict 的价值:如果客户端带了唯一标识(如幂等键)重复创建,服务端可以返回 409 而不是又创建一个。理解幂等性,能帮你决定”什么时候该返回 409”。
状态码选用对照表:把操作映射到状态码
这是可以直接抄进团队规范的一张表:
| 操作 | 成功 | 失败场景与对应状态码 |
|---|---|---|
| 查询资源 | 200 | 不存在 → 404 |
| 创建资源 | 201 | 参数校验不过 → 422/400;重复 → 409 |
| 更新资源 | 200 | 不存在 → 404;无权限 → 403;参数错误 → 422/400 |
| 删除资源 | 204 | 不存在 → 404;无权限 → 403 |
| 未认证 | — | 凭证缺失/无效 → 401 |
| 无权限 | — | 已认证但无权限 → 403 |
| 方法不支持 | — | 405(配合 Allow 头) |
| 限流 | — | 429(配合 Retry-After 头) |
| 服务器内部错误 | — | 500 |
| 上游异常(网关) | — | 502 / 504 |
把这张表贴进接口文档,前后端的沟通成本会大幅下降。
常见误用案例复盘:看看这些问题你是不是也踩过
把真实项目里高频出现的状态码错误摆出来复盘,比抽象讲规则更有用:
| 误用做法 | 为什么错 | 应该怎么做 |
|---|---|---|
| 登录过期返回 403 | 前端无法区分”要重新登录”还是”没权限” | 未认证用 401,无权限才用 403 |
| 参数校验失败返回 500 | 客户端错误被算成服务器错误,污染告警 | 用 400 或 422 |
资源不存在返回 200 + {code:404} | HTTP 层监控、缓存全部失效 | 直接用 404 |
| 创建成功返回 200 | 丢掉了 201 和 Location 的语义 | 用 201 + Location 头 |
| 限流返回 500 | 客户端被误判为服务器故障 | 用 429 + Retry-After |
| 删除成功返回 200 + 空 body | 语义模糊,前端不知要不要解析 body | 用 204 No Content |
| 方法不支持返回 404 | 让客户端误以为资源不存在 | 用 405 + Allow 头 |
这些错误有个共同根因:把”业务结果”和”HTTP 语义”混在一起,或者干脆图省事全用一个码。 状态码用对了,前端不用猜、告警有据可依、排查方向一眼能定,这笔投入是值得的。
状态码与 RESTful 资源设计的配合
状态码不是孤立的,它和 RESTful 的资源设计配合使用才完整:
- 资源路径对应 404 的边界:
/users/123里的123不存在时返回 404,是 REST 的经典语义。如果 404 被滥用去表达”权限不足”,资源的语义就被污染了。 - 幂等操作对应 2xx 的细分:PUT(幂等更新)返回 200 或 204,DELETE(幂等删除)返回 204,POST(非幂等创建)返回 201。状态码在这里承载了”这个方法是否幂等”的信息。
- 子资源与 404:
/users/123/orders/456中,用户存在但订单不存在,返回 404 比返回 400 更准确——它指的是”这个子资源”找不到,而不是请求格式错。
一句话:状态码是资源语义的一部分,不是随手填的数字。 设计 REST 接口时,把”每个资源在每种操作下成功/失败返回什么码”当成接口契约的一部分来设计,而不是写完逻辑再补。
排查思路:拿到状态码后怎么走
查状态码时,按这个顺序走,定位会快很多:
- 看首位:4xx 先怀疑自己发的请求(URL、参数、凭证、权限),5xx 先怀疑服务端。
- 看具体码:401 检查 Token 是否缺失/过期;403 检查权限配置;404 检查 URL 与路由;422 检查参数校验规则。
- 动手复现:用 HTTP 请求测试器 原样重发,排除前端拼接问题,并抓取完整响应头。
- 看响应体与响应头:错误信息、
Retry-After、Location等头往往藏着关键线索。
星点 HTTP 状态码查询 内置了每个状态码的含义、常见原因与排查建议,未收录的状态码按首位归类给通用说明,输入即查,配合上面这套思路可以快速下结论。
常见问题(FAQ)
401 和 403 有什么区别?
401 是"未认证"(你是谁都不知道,凭证缺失或无效);403 是"已认证但无权限"(知道你是谁,但不允许你做这件事)。400 和 422 该怎么选?
400 偏"请求格式不对"(JSON 解析失败等);422 偏"格式对但业务校验不过"(字段值不合法)。团队内部统一用一个即可,别混用。业务失败可以都返回 200 吗?
技术上能跑通,但不推荐。200 包业务错误会让 HTTP 层的监控、缓存、重试全部失效。建议让 HTTP 状态码与业务结果一致,业务码作为 body 补充。404 和 403 有什么区别?
404 表示服务器找不到资源(URL 错误、资源不存在);403 表示服务器理解请求但拒绝执行(权限不足)。502 和 504 的区别?
502 Bad Gateway 表示网关从上游收到无效响应;504 Gateway Timeout 表示网关等待上游响应超时。429 和 503 在限流场景怎么选?
429 表示"你请求太频繁"(客户端被限流),503 表示"我暂时扛不住"(服务端主动降级/过载)。都建议带 Retry-After 头告诉客户端何时重试。301 和 308 有什么区别?
301 是永久重定向但可能把 POST 改成 GET;308 也是永久重定向但严格保持方法不变。REST API 里要保持方法就用 308。错误响应体应该包含什么?
建议包含程序可识别的错误码、给人看的消息、字段级错误(校验场景)和请求 ID,便于定位与排查。未收录的状态码能查吗?
能。工具按状态码首位分类给出通用说明,例如 499 归为 4xx 客户端错误。499、599 这些非标准码是什么?
这些是部分服务器(如 Nginx)自定的非标准码:499 常见于 Nginx,表示"客户端在服务器响应前主动断开连接"。工具按首位归类为 4xx 并给出通用说明。状态码和 REST 资源路径怎么配合?
路径里的资源标识不存在时用 404,子资源不存在也用 404;权限不足用 403;请求格式/校验问题用 400/422。状态码是资源语义的一部分,应随资源设计一起定。405 应该返回什么信息?
405 Method Not Allowed 应配合 Allow 响应头,告诉客户端哪些方法被允许(如 Allow: GET, POST),而不是只返回一个冷冰冰的数字。状态码是越多越细越好吗?
不是。关键是"准"而不是"多":常用几十个码已经够覆盖绝大多数场景。过度细分(自造一堆非标准码)反而增加前后端的认知成本。联调时状态码对不上怎么办?
用 HTTP 请求测试器原样重发,确认是前端拼装问题还是后端返回问题;再用状态码查询工具对照每个码的标准语义,判断后端用法是否规范。一分钟速查清单
设计接口返回状态码时,按这个清单自查:
- 成功细分了吗(查询 200、创建 201、删除 204、异步 202);
- 未认证用 401、无权限用 403,别混;
- 参数校验用 400 或 422,团队内统一;
- 客户端错误没被塞进 5xx;
- 错误响应体里有错误码、消息、请求 ID;
- 限流用 429 + Retry-After,降级用 503。
总结
状态码是接口的第一层语义契约。核心记住三条:2xx 用准细分码、3xx 慎用且分清是否保持方法、4xx 分清 401/403 与 400/422、5xx 别把客户端错误塞进去。 设计时对照选用表,排查时先看首位定方向,再用 HTTP 请求测试器 复现验证。
立即体验:访问 HTTP 状态码查询
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!