HTTP 状态码实战:真正常用的那些码,以及每个码背后的决策

状态码不用背全表:记住分类口诀和十来个高频码,再看每个码背后的设计决策——401 还是 403、400 还是 422、301/302/307/308 怎么选、为什么「全部返回 200」是反模式。

约 7 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证

先一个口诀分堆:1xx 信息、2xx 成功、3xx 重定向、4xx 你的锅、5xx 我的锅

状态码的第一位就是分类。排障时先看首位判断「该查谁」,再用具体数字定位环节——这一条口诀能省掉大半的来回猜测。客户端收到 4xx 先检查自己的请求参数和凭证,收到 5xx 才去找服务端日志。

高频状态码速查表(按出现频率排)

状态码什么时候用最常踩的坑
200 OKGET 成功返回错误也返回 200(见下文反模式)
201 CreatedPOST 建资源成功忘了带 Location 指向新资源
204 No Content成功但没有返回体DELETE/PUT 后还想返回数据——那该用 200
301 / 302永久 / 临时重定向把临时跳转写成 301,会被浏览器和搜索引擎长期缓存
304 Not Modified协商缓存命中响应里又带了 body,白省
400 Bad Request请求语法本身坏掉把业务校验失败也塞进 400(该用 422)
401 / 403没登录 / 没权限两者混用,客户端分不清该重新登录还是该换账号
404 / 410资源不存在 / 已永久删除接口下线返回 404 而不是 410,爬虫会一直重试
429 Too Many Requests触发限流忘了带 Retry-After,客户端只能瞎重试
500 / 502 / 503 / 504服务端异常 / 网关错误 / 暂不可用 / 网关超时把可恢复的 503 当致命的 500 处理,错过重试时机

401 还是 403:身份没验 vs 验了没权限

401 的语义是「我不知道你是谁」——没带凭证、凭证过期或无效,响应应带 WWW-Authenticate 头提示如何认证。403 的语义是「我知道你是谁,但你不能进」——身份合法但权限不足,重试没有意义。判断方法:换一个有权限的账号能解决的是 403,重新登录能解决的是 401。混用的代价是客户端不知道该跳登录页还是该提示无权限。

400 还是 422:语法坏了 vs 语义不成立

400 留给「请求本身就坏」:JSON 括号不配对、必填头缺失、编码错误——服务器读都读不懂。422 留给「读得懂但业务上不成立」:年龄传了负数、邮箱格式对但未注册、库存不足。这条界线在 REST 语义里很清楚,但实践中大量框架默认全报 400——如果团队约定统一用 400,就把错误明细放进响应体,别让客户端只能猜。

301 / 302 / 307 / 308:重定向的四张牌

记法:3 开头里「七、八」保方法,「一、二」不保。给 API 做迁移,用 308 或 307;给页面做 SEO 迁移,用 301。302 的历史包袱最重——HTTP/1.0 时代几乎所有客户端都把它的 POST 降级成 GET,307 正是为了修正这个行为才补进标准。

状态码永久性请求方法典型场景
301永久可能从 POST 降级为 GET域名迁移、URL 结构调整(搜索引擎转移权重)
302临时可能从 POST 降级为 GET临时活动页、登录后的跳转
307临时原样保留(POST 仍是 POST)临时跳转且不能丢请求体(如表单提交)
308永久原样保留永久迁移且必须保留方法(如 API 端点换地址)

502 / 503 / 504:网关三兄弟,排查顺序完全不同

  1. 502 Bad Gateway——上游挂了或回了非法响应:查上游进程是否存活、是否崩溃重启、响应是否格式错误。
  2. 503 Service Unavailable——服务暂时不可用但可恢复:维护窗口、过载保护。带 Retry-After 头告诉客户端多久后重试;客户端收到 503 应退避重试,而不是立刻把错误抛给用户。
  3. 504 Gateway Timeout——上游活着但太慢:查慢查询、锁等待、超时配置;网关的超时通常比应用超时短,先把两边的参数对齐。

304 与协商缓存:一次往返省下的不只是带宽

客户端带 If-None-Match(配 ETag)或 If-Modified-Since(配 Last-Modified)发请求,资源没变时服务器只回 304 和几个头,不回 body——浏览器转而使用本地缓存。对静态资源和变更不频繁的 API 列表,这是零成本的提速手段。注意两点:304 响应别带 body;ETag 变了哪怕内容只差一个字节,也会老老实实回 200 全量。

为什么「全部返回 200」是反模式

有些 API 图省事:无论成败一律 200,把业务成败塞进响应体的 code 字段。代价在基础设施层:监控和告警按状态码统计,全 200 的服务故障时流量图一片绿;中间件的重试、熔断、限流也都依赖状态码判断,全 200 让这些机制集体失明。状态码表达传输层结果、body 表达业务结果,两层各司其职,系统才可观测。

✗ 反模式:HTTP 层永远成功
HTTP/1.1 200 OK
{"code": 5001, "msg": "database unavailable"}

→ 网关看到 200,不熔断、不重试、不告警
✓ 规范做法
HTTP/1.1 503 Service Unavailable
Retry-After: 30

{"error": {"code": "DB_UNAVAILABLE", "message": "...", "trace_id": "..."}}

常见问题

401 和 403 到底怎么区分?

401 是「没验明身份」:没带凭证、凭证过期,重新登录就能解决;403 是「验明了但权限不够」,换再多次登录也没用。给客户端的提示也不同:401 跳登录页,403 提示无权限。可以用 HTTP 状态码速查把整组 4xx 的语义一次看全。

接口偶尔返回 502,但服务没重启,查什么?

502 意味着网关拿到了「非法响应」或连不上上游。常见原因:上游进程被 OOM 杀掉后被守护拉起(日志里表现为偶发中断)、响应体超网关 buffer 上限、上游主动断开 keep-alive 连接的竞态。按「网关错误日志 → 上游进程内存 → 响应大小」的顺序查。

重定向后 POST 变成了 GET,怎么办?

你用的是 301/302,多数浏览器和客户端会把 POST 降级成 GET。想让方法原样保留,改用 307(临时)或 308(永久)。这是 HTTP 标准行为,不是 bug——降级语义写进了 301/302 的规范。

全部用 200 再靠 body 里的 code 区分成败,为什么不行?

HTTP 中间层(网关、负载均衡、监控、重试与熔断组件)只认状态码。全 200 会让故障在流量图上不可见、让重试机制失效、让熔断器永远不打开。状态码表达传输层结果、body 表达业务结果,两层各司其职才可观测。

📂

松开以自动格式化

支持 .json / .sql / .csv / .html / .css / .xml / .yaml 文件