4. HTTP 状态码:从 1xx 到 5xx 的完整解读
状态码这东西,说白了就是服务器给你回的一句话——「你刚才的请求,我处理得怎么样了?」。我刚开始做 Web 开发时,看到 404 就只知道「页面找不到」,后来踩了不少坑才明白,每个数字背后都藏着协议设计者的深思熟虑。
咱们按类别一个一个过。我会把项目中真正用到的、容易混淆的、以及那些「你以为你懂了但其实没懂」的点,都掰开揉碎讲清楚。
4.1 1xx 信息性状态码——你可能很少见到它们
1xx 状态码在实际业务中确实不常见。为什么?因为它们是「临时」的——服务器收到请求后,先给你回个信儿,告诉你「我还在处理,别急」。浏览器和大部分 HTTP 客户端库会自动处理这些状态码,开发者通常感知不到。
但有一个例外值得关注:100 Continue。
100 Continue 的工作流程:
- 客户端发送
Expect: 100-continue请求头 - 服务器检查请求头,如果愿意接收,返回
100 Continue - 客户端收到后,才发送真正的请求体
- 服务器处理完整请求,返回最终状态码
我曾在做一个大文件上传服务时用过这个机制。客户端先发个头探探路,服务器觉得「嗯,文件大小在允许范围内」,才让客户端继续上传。这样能避免浪费带宽——你想想看,如果客户端直接发一个 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 是个特殊的存在。它不算是「重定向」,而是「缓存协商」的结果。
流程是这样的:
- 客户端请求资源时,带上
If-Modified-Since或If-None-Match头 - 服务器检查资源是否变化
- 如果没变,返回
304 Not Modified,没有响应体 - 客户端知道「缓存还能用」,直接使用本地缓存
这个机制能大幅减少带宽消耗。我在优化一个图片服务时,通过合理设置 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 状态码选择的最佳实践
说了这么多,最后给几个我在项目中总结的经验:
- 不要滥用 200:所有请求都返回 200 然后在 body 里放状态码,会让客户端代码变得复杂。能用 HTTP 状态码表达的语义,就别自己造轮子。
- 区分 401 和 403:这是最常见的错误。记住——401 是「你是谁?」,403 是「你不行」。
- 301 慎用:一旦返回 301,浏览器和搜索引擎都会永久记住这个重定向。如果后续想改回来,会很麻烦。
- 5xx 要记录日志:每次返回 500/502/503,都应该在服务端记录完整的错误上下文。否则线上出了问题,你连从哪查起都不知道。
- 304 是性能利器:合理利用缓存协商,能大幅降低服务器负载。但要注意——ETag 和 Last-Modified 的生成逻辑要高效,否则缓存协商本身会成为性能瓶颈。
状态码是 HTTP 协议中最直观的「沟通语言」。选对了,客户端和服务端就能顺畅对话;选错了,双方都会困惑。嗯,希望这一章能帮你建立起对状态码的系统认知——下次看到 502 时,至少能第一时间判断出「是网关层的问题,不是应用层的问题」。这就够了。