大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
HTTP 协议层
这一课你将能做到
- 熟练运用 GET/POST/PUT/DELETE/PATCH,并说清楚各自的语义和幂等性
- 根据业务场景选择正确的 HTTP 状态码,不再只会 200/500
- 配置 CORS 解决跨域问题,理解预检请求(Preflight)机制
为什么要学
HTTP 是 API 的载体。你设计的 URI 再漂亮,如果 HTTP 方法用错、状态码乱返、Header 不会配,调用方照样崩溃。面试必考:POST 和 PUT 区别?301 和 302 区别?CORS 怎么配?
先建立图像
HTTP 请求报文:
┌─────────────────────────────────────────┐
│ POST /api/lessons HTTP/1.1 │ ← 请求行:方法 + URI + 协议版本
│ Host: localhost:8080 │ ← 请求头
│ Content-Type: application/json │
│ Authorization: Bearer xxx │
│ │
│ {"title":"Spring Boot"} │ ← 请求体
└─────────────────────────────────────────┘
HTTP 响应报文:
┌─────────────────────────────────────────┐
│ HTTP/1.1 201 Created │ ← 状态行:协议版本 + 状态码 + 原因短语
│ Content-Type: application/json │ ← 响应头
│ Location: /api/lessons/123 │
│ │
│ {"id":123,"title":"Spring Boot"} │ ← 响应体
└─────────────────────────────────────────┘
跟着做
步骤 1:GET 查询——安全且幂等
# 查询课程列表
curl -s http://localhost:8080/api/lessons
# 查询单个课程
curl -s http://localhost:8080/api/lessons/1
# 带查询参数
curl -s "http://localhost:8080/api/lessons?trackId=java-web&page=0&size=10"
GET 的语义是「读取」,不应该有副作用。浏览器会缓存 GET 请求,刷新页面会重新发。
步骤 2:POST 创建——非幂等
curl -X POST http://localhost:8080/api/lessons \
-H "Content-Type: application/json" \
-d '{"title":"Spring Security","slug":"spring-security","trackId":"java-web"}'
POST 创建资源,服务器生成 id。多次执行会创建多条记录(除非服务端做去重)。
步骤 3:PUT 全量更新 vs PATCH 部分更新
# PUT:全量替换,必须传完整对象
curl -X PUT http://localhost:8080/api/lessons/1 \
-H "Content-Type: application/json" \
-d '{"title":"Spring Boot 进阶","slug":"spring-boot-advanced","trackId":"java-web","content":"..."}'
# PATCH:只改要变的字段
curl -X PATCH http://localhost:8080/api/lessons/1 \
-H "Content-Type: application/json" \
-d '{"title":"Spring Boot 进阶(第2版)"}'
PUT 是幂等的(多次替换结果一样),PATCH 取决于实现(如果 patch 是「追加」操作,多次执行会追加多次)。
步骤 4:DELETE 删除
curl -X DELETE http://localhost:8080/api/lessons/1
DELETE 是幂等的:删一次和删十次,最终结果都是「资源不存在」。但注意:第二次删除应该返回 404 还是 204?建议返回 204(幂等成功)或 404(明确告知已不存在),不要抛 500。
步骤 5:状态码实战
@PostMapping
public ResponseEntity<ApiResponse<Lesson>> create(@RequestBody @Valid LessonRequest request) {
Lesson lesson = lessonService.create(request);
// 201 Created:资源创建成功
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(lesson));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Lesson>> getById(@PathVariable Long id) {
Lesson lesson = lessonService.findById(id);
if (lesson == null) {
// 404 Not Found:资源不存在
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error(404, "课程不存在"));
}
return ResponseEntity.ok(ApiResponse.success(lesson));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
lessonService.delete(id);
// 204 No Content:删除成功,无返回体
return ResponseEntity.noContent().build();
}
步骤 6:CORS 配置
前端跑在 http://localhost:3000,后端在 http://localhost:8080,浏览器会拦截跨域请求。
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
};
}
}
OPTIONS 是预检请求:浏览器先问服务器「我能不能发 POST?」,服务器回答允许的方法和来源,浏览器再发真实请求。
讲清楚要点
- GET 是安全的:安全 = 不修改服务器状态。GET 只读取,不能创建、更新、删除。爬虫、预取、缓存都依赖这个假设。
- POST vs PUT:POST 是「创建子资源」(服务器决定 URI),PUT 是「替换整个资源」(客户端指定 URI)。POST 非幂等,PUT 幂等。
- 状态码是 API 的「表情」:2xx 成功,3xx 重定向,4xx 客户端错误,5xx 服务器错误。别把所有错误都返 200 然后在 body 里写
"code": 500。 - Content-Type 决定解析方式:
application/json是主流,application/x-www-form-urlencoded是表单,multipart/form-data是文件上传。 - CORS 是浏览器行为:服务器之间直接调 HTTP 没有 CORS 限制。CORS 是浏览器保护用户的机制,防止恶意网站偷偷发请求。
你可能会懵的地方
-
PATCH 和 PUT 到底怎么选? PUT 是「全量替换」,传什么就是什么,没传的字段会被清空。PATCH 是「增量修改」,只改传了的字段。大多数业务用 PATCH 更友好,但实现复杂(需要处理字段级别的合并)。
-
为什么 DELETE 有时返回 204,有时返回 200? 204 = 成功且没有返回体;200 = 成功且有返回体(比如返回被删除的资源快照)。推荐 204,简洁。
-
401 和 403 区别? 401 Unauthorized = 未认证(没登录或 token 无效);403 Forbidden = 已认证但无权限(普通用户想删管理员数据)。
-
302 和 307 区别? 302 是「临时重定向」,但历史上浏览器会把 POST 变成 GET;307 严格保持原方法。API 设计里推荐 307/308(永久重定向保持方法)。
-
HTTP/1.1 和 HTTP/2 对 API 设计有影响吗? HTTP/2 多路复用、头部压缩、服务器推送。对 API 设计影响最大的是「不再需要把多个请求合并成一个来减少连接数」,可以更细粒度地拆分资源。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 删除成功返回 200 + 空 body | 浪费带宽,语义不清 | 204 No Content |
| 创建成功返回 200 | 应该明确告知「创建了」 | 201 Created + Location header |
| 所有错误都抛 500 | 调用方无法区分是参数错还是服务器崩 | 参数错 400,未找到 404,权限不足 403 |
| 用 GET 做删除操作 | 浏览器预取、爬虫会误删数据 | DELETE 方法 |
| 在 URL 里传大量查询条件 | URL 长度有限制(约 8KB),且不可读 | 复杂查询用 POST + body,或 GraphQL |
卡点急救
- 405 Method Not Allowed:URI 存在但方法不对,比如用 POST 访问只支持 GET 的接口。检查
@GetMapping是否写成了@PostMapping。 - 预检请求 403:CORS 配置没允许
OPTIONS方法,或allowedOrigins不匹配。检查浏览器 Network 面板里 OPTIONS 请求的响应头。 - 状态码被改成 200:某些框架或网关会拦截错误。确保全局异常处理器正确设置了
HttpStatus。
面试常问
-
HTTP 状态码分类?各举 3 个例子。 1xx 信息(100 Continue),2xx 成功(200 OK、201 Created、204 No Content),3xx 重定向(301 Moved Permanently、302 Found、304 Not Modified),4xx 客户端错误(400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found),5xx 服务器错误(500 Internal Server Error、502 Bad Gateway、503 Service Unavailable)。
-
POST 和 PUT 的核心区别? POST 非幂等,用于创建子资源,服务器生成 URI;PUT 幂等,用于全量替换,客户端指定 URI。PUT 可以重复执行不会产生副作用。
-
CORS 预检请求什么时候触发? 非简单请求(如 PUT、DELETE、Content-Type: application/json、自定义 header)会先发送 OPTIONS 预检。服务器响应
Access-Control-Allow-*后,浏览器才发真实请求。
自测三问
- 更新用户邮箱,应该用什么 HTTP 方法?URI 怎么设计?
- 如果调用方传了错误的 JSON 格式,应该返回什么状态码?
- 为什么 GET 请求不适合传敏感数据?
小结一句话
HTTP 方法定义操作语义,状态码表达执行结果,Header 传递元数据;用对这三者,API 就成功了一半。
延伸
- MDN HTTP 方法
- MDN HTTP 状态码
- 下一课:REST 资源设计——URI 规范、版本策略、分页与幂等性深入。