4. HTTP 状态码:从 1xx 到 5xx 的完整解读

状态码这东西,说白了就是服务器给你回的一句话——「你刚才的请求,我处理得怎么样了?」。我刚开始做 Web 开发时,看到 404 就只知道「页面找不到」,后来踩了不少坑才明白,每个数字背后都藏着协议设计者的深思熟虑。

咱们按类别一个一个过。我会把项目中真正用到的、容易混淆的、以及那些「你以为你懂了但其实没懂」的点,都掰开揉碎讲清楚。

HTTP 状态码分类全景 1xx 信息性 100 Continue 2xx 成功 200 OK 3xx 重定向 301/302/304 4xx 客户端错误 400/401/403/404 5xx 服务器错误 500/502/503 核心原则:状态码的第一位数字定义了响应的类别,后两位提供更细粒度的语义 临时响应,请求已接收 继续处理中 请求成功,服务器已处理 返回期望数据 需要进一步操作 通常用于 URL 跳转 请求包含错误 客户端需修改请求 服务器处理失败 请稍后重试

4.1 1xx 信息性状态码——你可能很少见到它们

1xx 状态码在实际业务中确实不常见。为什么?因为它们是「临时」的——服务器收到请求后,先给你回个信儿,告诉你「我还在处理,别急」。浏览器和大部分 HTTP 客户端库会自动处理这些状态码,开发者通常感知不到。

但有一个例外值得关注:100 Continue。

100 Continue 的工作流程:

  1. 客户端发送 Expect: 100-continue 请求头
  2. 服务器检查请求头,如果愿意接收,返回 100 Continue
  3. 客户端收到后,才发送真正的请求体
  4. 服务器处理完整请求,返回最终状态码

我曾在做一个大文件上传服务时用过这个机制。客户端先发个头探探路,服务器觉得「嗯,文件大小在允许范围内」,才让客户端继续上传。这样能避免浪费带宽——你想想看,如果客户端直接发一个 2GB 的文件,服务器一看「超出限制」直接拒绝,那这 2GB 流量就白费了。

小技巧:如果你在写 HTTP 客户端,可以主动设置 Expect: 100-continue 来优化大请求的发送策略。但注意,大部分浏览器默认不发送这个头。

4.2 2xx 成功——最让人安心的数字

2xx 意味着「一切正常」。但同样是成功,不同场景下含义差别很大。

200 OK:最通用的成功

这是最常见的状态码。GET 请求成功返回数据、POST 请求成功创建资源、PUT 请求成功更新……只要服务器处理了请求并返回了期望的结果,基本都是 200。

不过,我个人习惯在 RESTful API 设计中区分使用:

  • GET /users/123 → 200 + 用户数据
  • POST /users → 201 Created + 新用户数据
  • DELETE /users/123 → 204 No Content(没有响应体)

为什么要区分?因为客户端可以根据状态码直接判断下一步操作。收到 201 就知道「资源创建成功,可以跳转到详情页了」;收到 204 就知道「删除成功,刷新列表就行」。这样写出来的代码更清晰,也更容易维护。

注意:有些团队习惯所有成功请求都返回 200,然后在响应体里放一个 code 字段表示业务状态。这种做法虽然能用,但违背了 HTTP 语义。我建议尽量遵循协议设计——状态码是给机器读的,业务码是给人读的,各司其职。

4.3 3xx 重定向——浏览器帮你「绕路」

3xx 状态码的核心含义是:「你要的资源不在这里,去别处找找看」。浏览器收到 3xx 后,会自动跳转到 Location 响应头指定的新 URL。

这里面有几个容易混淆的点,我一个个说。

301 vs 302:永久 vs 临时

特性 301 Moved Permanently 302 Found
语义 资源已永久迁移 资源临时在别处
浏览器缓存 会缓存重定向结果,下次直接访问新 URL 不会缓存,下次仍请求原 URL
搜索引擎 将权重转移到新 URL 保留原 URL 的权重
典型场景 网站更换域名、HTTP→HTTPS 升级 临时维护页面、A/B 测试

我记得有一次帮客户迁移网站,从旧域名换到新域名。我用了 301 重定向,结果客户发现旧域名的 SEO 排名慢慢掉下去了——这正是 301 的预期行为,搜索引擎把权重转移到了新域名。如果当时用了 302,那旧域名的排名会一直保留,但用户每次访问都会被重定向一次,体验不好。

304 Not Modified:缓存的好帮手

304 是个特殊的存在。它不算是「重定向」,而是「缓存协商」的结果。

流程是这样的:

  1. 客户端请求资源时,带上 If-Modified-Since 或 If-None-Match 头
  2. 服务器检查资源是否变化
  3. 如果没变,返回 304 Not Modified,没有响应体
  4. 客户端知道「缓存还能用」,直接使用本地缓存

这个机制能大幅减少带宽消耗。我在优化一个图片服务时,通过合理设置 ETag 和 Last-Modified,让 80% 的重复请求都返回了 304,服务器负载直接降了一半。

避坑指南:304 响应不能有消息体。如果你在服务端框架里手动返回 304 时还带了 body,客户端可能会解析失败。我曾经踩过这个坑——Nginx 配置里不小心给 304 加了内容,结果某些浏览器直接白屏。

4.4 4xx 客户端错误——问题出在你这边

4xx 意味着「服务器收到了你的请求,但你的请求有问题」。注意,这不是服务器出错了,是客户端(浏览器、App、脚本)发来的请求不符合规范。

400 Bad Request:最通用的「你错了」

400 是个兜底状态码。请求语法错误、参数格式不对、请求体 JSON 解析失败……只要服务器觉得「这请求我看不懂」,就可以返回 400。

我个人习惯在 API 设计里,把 400 留给「格式错误」,比如:

  • 缺少必填字段 → 400 + 错误信息「缺少字段:username」
  • 字段类型不对 → 400 + 错误信息「age 字段必须是数字」
  • JSON 解析失败 → 400 + 错误信息「请求体不是合法的 JSON」

401 vs 403:没登录 vs 没权限

这两个经常被搞混。我面试时喜欢问这个问题——能答清楚的人,说明对 HTTP 认证机制有深入理解。

状态码 含义 典型场景 解决方案
401 Unauthorized 未认证,服务器不知道你是谁 没带 Token、Token 过期 重新登录获取 Token
403 Forbidden 已认证,但你没有权限 普通用户访问管理员接口 联系管理员提升权限

举个例子:你登录了知乎,能看自己的主页(200),但你想看别人的私信——服务器知道你是谁(已认证),但你没有权限看别人的私信,所以返回 403。

如果你连登录都没做,直接请求私信接口,服务器会返回 401,并带上 WWW-Authenticate 响应头,告诉你怎么认证。

404 Not Found:最出名的「找不到」

404 可能是普通人最熟悉的 HTTP 状态码。它的含义很简单:服务器上没有你请求的资源。

但要注意,有些网站为了安全,会把「资源存在但没权限」也伪装成 404。比如你访问 /admin/config,如果服务器直接返回 404,攻击者就不知道这个路径到底是不存在还是存在但被保护了。这是一种安全策略。

安全建议:在生产环境中,对于敏感路径,建议统一返回 404 而不是 403。这样攻击者无法通过状态码差异来探测你的目录结构。

4.5 5xx 服务器错误——出问题的是我这边

5xx 意味着「服务器在处理请求时出了内部错误」。这时候客户端可以重试,但大概率会再次失败——除非服务器自己恢复了。

500 Internal Server Error:最通用的「服务器炸了」

500 是 5xx 里的「兜底王」。代码抛异常、数据库连不上、第三方服务超时……只要服务器没处理好,就返回 500。

我建议在开发阶段,让服务器返回详细的错误信息(堆栈、错误码),方便调试。但生产环境一定要把这些信息隐藏掉,只返回一个通用的「500 Internal Server Error」,避免泄露敏感信息。

502 Bad Gateway vs 503 Service Unavailable

这两个在微服务架构和网关场景下非常常见。

状态码 含义 典型场景
502 Bad Gateway 网关/代理从上游服务器收到了无效响应 Nginx 代理的后端服务挂了,返回了非 HTTP 响应
503 Service Unavailable 服务器暂时无法处理请求(通常是过载或维护) 服务器正在重启、流量突增导致限流

我记得有一次线上事故:凌晨三点,数据库主库挂了,从库还没切过来。所有 API 请求都返回 502——因为应用服务器连不上数据库,返回了空响应,Nginx 觉得「这响应不对」,就给了 502。

而 503 通常伴随着 Retry-After 响应头,告诉客户端「你过 30 秒再试」。我在做限流中间件时,就用了这个机制——当请求量超过阈值时,返回 503 + Retry-After: 60,客户端看到后会等待 60 秒再重试。

核心区别总结:

  • 502:网关/代理层的问题,上游服务没给正确响应
  • 503:服务本身的问题,过载或维护中
  • 504:网关/代理层超时,上游服务响应太慢

4.6 状态码选择的最佳实践

说了这么多,最后给几个我在项目中总结的经验:

  1. 不要滥用 200:所有请求都返回 200 然后在 body 里放状态码,会让客户端代码变得复杂。能用 HTTP 状态码表达的语义,就别自己造轮子。
  2. 区分 401 和 403:这是最常见的错误。记住——401 是「你是谁?」,403 是「你不行」。
  3. 301 慎用:一旦返回 301,浏览器和搜索引擎都会永久记住这个重定向。如果后续想改回来,会很麻烦。
  4. 5xx 要记录日志:每次返回 500/502/503,都应该在服务端记录完整的错误上下文。否则线上出了问题,你连从哪查起都不知道。
  5. 304 是性能利器:合理利用缓存协商,能大幅降低服务器负载。但要注意——ETag 和 Last-Modified 的生成逻辑要高效,否则缓存协商本身会成为性能瓶颈。

状态码是 HTTP 协议中最直观的「沟通语言」。选对了,客户端和服务端就能顺畅对话;选错了,双方都会困惑。嗯,希望这一章能帮你建立起对状态码的系统认知——下次看到 502 时,至少能第一时间判断出「是网关层的问题,不是应用层的问题」。这就够了。