CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
HTTP 协议与缓存/课时/状态码
RawBlame
课时目录12 节

HTTP 协议与缓存

  1. 01HTTP 是什么
  2. 02方法与语义
  3. 03状态码
  4. 04请求与响应头
  5. 05HTTPS 与 TLS
  6. 06HTTP 缓存
  7. 07Cookie 与会话
  8. 08CORS
  9. 09REST 与资源设计
  10. 10HTTP/2 与 HTTP/3
  11. 11应用层缓存对照
  12. 12综合练习

状态码

2xx/3xx/4xx/5xx 常见码与 REST API。

30 分钟

状态码

这一课你将能做到

  1. 按 2xx / 3xx / 4xx / 5xx 四类解释「成功、重定向/协商、客户端错、服务器错」,各举出至少两个常用码
  2. 把 200、201、204、301、302、304、400、401、403、404、405、429、500、502 与「本站接口、Nginx 反代、登录鉴权」的真实场景对上号
  3. 用 curl -w "%{http_code}" 与 DevTools Status 列排错,先读状态码再读 body,并为自己的 API 写一张「状态码合同」

为什么要学

接口挂了先看 Network 红字:是你传错参数(4xx),还是上游服务炸了(5xx)?方向判断错了会在前端白改半天。REST 设计里状态码是机器可读的结果摘要——前端拦截器按它分支、监控告警按它统计、缓存按 304 决定复用、限流按 429 触发退避。

面试常问「301 和 302 的区别」「401 和 403 的区别」「502 和 500 谁的锅」,本质都在考你是否真的理解这套分类。本课建立「常用码词典」,不对着 RFC 背几百个。

先建立图像

2xx  成功:我按你的意图做完了(或已接受)
3xx  换地方 / 用本地缓存:去别的 URL(301/302),或「你本地那份还能用」(304)
4xx  客户端问题:改请求再来(没登录、路径错、参数不合法)
5xx  服务器问题:请求可能没错,服务端或网关炸了
码人话真实场景
200OK,body 里有东西GET /api/tracks 返回课表
201已创建POST /admin/tracks 成功,常带 Location
204成功但无 bodyDELETE 成功的标配
301永久搬家HTTP→HTTPS 强制跳转、旧链接迁移
302临时去别处短链跳转、登录后回跳
304未修改,用你缓存协商缓存命中(缓存课细讲)
400请求不合法JSON 校验失败、参数类型错
401我不认识你没登录 / Token 过期
403认识你但不许普通用户调管理员接口
404没这个资源错误 slug、路由没匹配
405资源在,方法错GET-only 接口收到 POST(方法课验证过)
429请求太频繁网关限流,常带 Retry-After
500服务器内部错未捕获异常、NPE
502网关收到无效/无响应Nginx 上游 Spring 挂了
503
暂不可用
过载、维护中

跟着做

步骤 1:只打印状态码,练「先看码」的肌肉

# 成功:期望 200
curl -o /dev/null -s -w "%{http_code}\n" http://localhost:8080/api/notes

# 不存在:期望 404
curl -o /dev/null -s -w "%{http_code}\n" http://localhost:8080/api/notes/99999

-o /dev/null(Windows 用 -o NUL)丢弃 body,-w 自定义输出。日常巡检、监控脚本都这么写——只关心码不关心体。

步骤 2:让 Spring Boot 的状态码变「对」

给上一课的 NoteController 补上「业务失败要选对家族」的实践。加一个带校验的创建接口:

@PostMapping("/valid")
public ResponseEntity<Map<String, String>> createValid(@RequestBody Map<String, String> body) {
    String title = body.get("title");
    if (title == null || title.isBlank()) {
        // 参数不合法 → 400,而不是 200 + {ok:false}
        return ResponseEntity.badRequest().body(Map.of("error", "title 不能为空"));
    }
    return ResponseEntity.status(201)          // 创建成功 → 201
            .header("Location", "/api/notes/new")  // 告诉客户端新资源在哪
            .body(Map.of("title", title));
}

验证:

# 期望 400 + 错误 JSON
curl -i -X POST http://localhost:8080/api/notes/valid \
  -H "Content-Type: application/json" -d '{"title":""}'

# 期望 201 + Location 头
curl -i -X POST http://localhost:8080/api/notes/valid \
  -H "Content-Type: application/json" -d '{"title":"HTTP 状态码"}'

步骤 3:统一异常处理,别让 500 吞掉 4xx

真实项目用 @RestControllerAdvice 统一兜底,避免「业务异常全变 500」:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity<Map<String, String>> notFound(EntityNotFoundException e) {
        return ResponseEntity.status(404).body(Map.of("error", e.getMessage()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> badRequest(MethodArgumentNotValidException e) {
        return ResponseEntity.badRequest().body(Map.of("error", "参数校验失败"));
    }
    // 兜底的 500 交给 Spring 默认行为即可,别把 4xx 伪装成 500
}

原则:找得到资源但业务上没有 → 404;参数错 → 400;真·程序异常 → 500。

步骤 4:401 vs 403 情景剧

用两个场景固定记忆:

场景 A:没带 Token 调 /admin/tracks
        → 401 Unauthorized(你是谁?先去登录)

场景 B:带了普通用户的 Token 调管理员删除
        → 403 Forbidden(我认得你,但你没这个权限)

前端据此分支:收到 401 跳登录页并清本地凭证;收到 403 留在当前页提示「无权限」。Spring Security 里对应「未认证」与「已认证但鉴权失败」两个阶段。

步骤 5:DevTools 按状态码筛选

  1. 打开站点任意页 → Network 面板
  2. 在筛选框输入 status-code:404(Chrome 支持),或直接点红色失败请求
  3. 故意访问一个错误 slug 的课程页,观察 document 与 API 各自的失败形状

4xx 在面板里标红,点开 Preview 看前端是否把 message 展示给了用户。

步骤 6:304 预习——不是错误

# 对线上或本站接口带条件头发请求(ETag 值替换为步骤中真实拿到的)
curl -i -H 'If-None-Match: "abc123"' https://coderoadmap.cn/api/tracks

若内容没变会返回 304 且无 body——这是缓存正常工作,监控里别把 304 算进错误率(缓存课展开)。

步骤 7:502 心智实验——谁的锅?

浏览器 → Nginx →(上游 Spring 进程挂了/端口不通)→ 502 Bad Gateway

模拟:把 Spring Boot 停掉再 curl 一个走 Nginx 反代的路径(或想象这一步),Nginx 自己生成 502 响应。判别口诀:看响应头里的 Server 字段——nginx 开头的 5xx 是网关层报的,查 upstream;由 Spring 生成的错误 JSON 是应用层报的,查应用日志。502 别先怪前端。

步骤 8:写一张「本 API 状态码合同」

为课表 API 在笔记里定三行合同(可直接贴进接口文档):

200  成功,body = JSON 数组或对象
404  slug 不存在,body = 统一错误形状 {"error":"..."}
5xx  留给未处理异常;业务「找不到」不许伪装成 500

管理写接口再补一行:201 创建、204 删除、401 未登录、403 无权限、400 校验失败。合同越短越好记,团队才用得起来。

讲清楚要点

  1. 因为状态码是契约的一部分,所以业务「失败」也要选对家族——参数错用 400,别一律 200 + {ok:false}(除非团队明确 RPC 风格并全员文档化),否则前端拦截器、监控、网关全都失效。
  2. 因为 401/403 语义不同,所以前端处理分支不同——401 通常是「凭证缺失/过期」,处理动作是去登录;403 是「凭证有效但权限不足」,处理动作是提示与引导。
  3. 因为 204 承诺无 body,所以客户端不要对 204 调 response.json()——DELETE 成功后的经典报错点。
  4. 因为 301 是永久语义,浏览器和搜索引擎会缓存它,所以别把可能反复变化的跳转设成 301——临时跳转用 302/307。
  5. 因为 5xx 多数是环境/上游问题,所以排错顺序是:应用进程 → 网关日志 → 数据库/依赖,而不是先改前端——502/503 尤其如此。
  6. 因为 304 表示协商成功,所以它省的是 body 传输而不是那次往返——热点 API 想减往返,要靠 CDN/Nginx/Redis 分层缓存(缓存课与应用缓存课)。

你可能会懵的地方

  1. 为什么有些公司「永远返回 200」? 一些老的 JSON-RPC 风格或网关习惯,把业务码塞进 body。代价是 HTTP 生态(缓存、监控、重试、浏览器)全部失明。本站主路径按标准状态码教学;若维护遗留系统,至少在文档里写清映射表。

  2. 404 到底是「路径写错」还是「业务数据不存在」? 对客户端来说一样:资源不可用。服务端内部可以区分「路由没匹配」和「数据库没查到」,对外都可以 404;关键是别把「查无此人」报成 500。

  3. 307/308 又是什么?和 301/302 什么关系? 307/308 是「不改变方法」的重定向:POST 跳转后仍是 POST。301/302 允许客户端把 POST 改成 GET(历史遗留)。改方法可能改变语义,涉及写操作的重定向优先 307/308。

  4. 429 该怎么处理? 它仍是 4xx:客户端(或该 Token/IP)请求过频。响应常带 Retry-After 头,前端应指数退避重试,而不是立刻再打一轮把限流坐实。自己实现限流时在网关层(Nginx limit_req)或应用层做,并返回 429 而不是 403。

  5. 500 和 502 怎么快速分? 看响应是谁生成的:有 Spring 的错误 JSON / 应用堆栈特征 → 应用 500,查应用日志;Server: nginx + Nginx 默认错误页 → 网关连不上上游或上游响应非法,查 Nginx error.log 和 upstream 进程状态。

错法对照

错法问题正解
成功失败都返回 200,body 里写 code监控告警、浏览器缓存、通用拦截器全部失灵HTTP 状态码表达大类,body 可补充业务码
把 401 当 403,提示「没有权限」未登录用户被引导到「联系管理员」,体验灾难未认证 401 → 引导登录;已认证无权限 403 → 提示无权
DELETE 成功却返回一大段 JSON 并标 204204 承诺无 body,客户端解析必炸要回执就 200 + JSON;纯删除就 204 无体
把 304 统计进错误率告警半夜被「缓存正常工作」叫起来缓存指标单独建面板
一见 5xx 就改前端重试逻辑上游挂着重试只会火上浇油先查应用日志与 upstream 健康,再谈重试

卡点急救

  1. 前端拿到的全是 500,后端日志却没有业务异常:多半是请求根本没到业务代码——查 Spring Security 过滤器、参数解析失败(HttpMessageNotReadableException);在 GlobalExceptionHandler 里临时把所有异常打出堆栈定位。
  2. 接口明明存在却 404:检查 @RequestMapping 拼接、server.servlet.context-path 是否多了一层前缀、包是否在组件扫描范围内;用 curl -i 看 404 的 body 是 Spring 默认的还是 Nginx 的,先分清是谁回的。
  3. 线上偶发 502:Nginx error.log 找 connect() failed / upstream prematurely closed;前者查 Spring 进程和端口,后者多是上游处理超时或被 kill(OOM)——结合 dmesg、堆内存监控定位,不是重启了事。

面试常问

  • 301 和 302 的区别?对 SEO 有什么影响? 301 永久重定向,浏览器会缓存、搜索引擎把权重迁移到新地址;302 临时重定向,每次都问源站、权重不迁移。网站换域名/路径重构用 301;登录回跳、A/B 短链用 302。

  • 401 和 403 的区别?前端分别怎么处理? 401 未认证:凭证缺失或无效,前端清凭证并跳登录页。403 已认证但无权限:凭证有效,前端提示无权限、不跳转。服务端对应 Spring Security 的认证与授权两个阶段。

  • 线上用户反馈「网站打不开」,你如何用状态码快速分层定位? ① 浏览器报 ERR_CONNECTION_REFUSED / 超时 → 网络或进程层,查服务器与端口;② 5xx:看是谁生成的响应——Nginx 502/503 查 upstream,应用 500 查异常日志;③ 4xx:404 查路由与发布版本,401/403 查登录态;④ 都正常但页面空 → 查前端资源加载与 JS 报错。

自测三问

  1. 写出 2xx/3xx/4xx/5xx 各一句话定义,并各举两个常用码。
  2. GET 不存在的 slug、无 Token 调管理接口、Nginx 上游挂掉,分别最可能落在哪个码?
  3. 为什么「先读状态码再读 body」能加快排错?举一个 body 会误导你的例子。

小结一句话

状态码是机器可读的结果摘要:2 成功、3 换路或用缓存、4 改请求、5 查服务端——REST 语义准,监控与客户端才省心。

延伸

  • MDN:HTTP 响应状态码
  • MDN:常见 404 条目示例
  • 下一课:请求与响应头——Content-Type、Authorization、Cache-Control、ETag,头是 HTTP 的便签,会读头等于会读半条链路。
加载本节练习…
← 上一课方法与语义下一课 →请求与响应头