Skip to content

HTTP 方法语义

本章导读:HTTP 方法是 REST 的"动词表"。RFC 9110 第 9 章对每个方法的语义有精确规定——哪些方法安全、哪些幂等、body 意味着什么、成功该返回哪个状态码。这一章是面试与实战的双重高频区,值得逐条精读。

1. 方法总表(RFC 9110 §9)

方法语义安全幂等请求体成功典型状态码
GET读取资源的表示200 / 206 / 304
HEAD同 GET 但无响应体(取元数据)200 / 304
POST对目标资源执行"处理"(创建子资源/触发流程)201 / 202 / 200
PUT用请求体整体替换目标资源的表示200 / 204 / 201
PATCH对资源施加部分修改✘(通常)200 / 204
DELETE删除目标资源的当前表示✘(少见)200 / 202 / 204 / 410
OPTIONS查询目标资源支持的接口(CORS 预检)200 / 204
TRACE回显请求(环回测试,生产应禁用)200
CONNECT为隧道建立代理连接(HTTPS 穿透)2xx

安全(safe)= 只读不改状态;幂等(idempotent)= 执行 N 次与执行 1 次对资源状态的影响相同。详细辨析见安全与幂等一章。

2. GET:读取的艺术

http
GET /articles/42?fields=title,status HTTP/1.1
Accept: application/json

RFC 9110 对 GET 的关键规定:

  • GET 不应具有副作用(§9.3.1):业务上不得用 GET 触发扣款、发货——"浏览器预取/爬虫/监控拨测"随时可能重放你的 GET。
  • GET 响应默认可缓存(RFC 9111),URL + 请求头决定缓存键。
  • GET 无请求体:虽然语法上未禁止,但语义与互操作性都不支持(代理/服务器普遍不转发 GET body,永远不要设计带 body 的 GET)。
  • 同一 URL 的 GET 参数顺序、重复参数语义要团队统一定义(RFC 9110 指出服务器可对查询串组合自行定义含义)。

大坑演示: GET /jobs/7/trigger 触发报表任务 → 监控系统每 10 秒拨测一次 → 任务被执行上千次。正确姿势:POST /jobs/7/executions

3. POST:最不" REST "却最必要的方法

RFC 9110 §9.3.3:POST 的语义是"服务器按照目标资源自身定义的语义处理请求表示"——它是一个扩展点。三大用途:

3.1 创建子资源(最常见)

http
POST /articles HTTP/1.1
Content-Type: application/json

{ "title": "新文章", "authorId": 7 }
http
HTTP/1.1 201 Created
Location: /articles/43

{ "id": 43, "title": "新文章", "status": "draft" }
  • ID 由服务器生成 → POST 到集合;ID 由客户端决定 → 应用 PUT(PUT /articles/43,见 §4)。
  • 201 响应应当Location 头指向新资源(RFC 9110 使用 SHOULD;Location-PermaLink 头已被废弃,实践中统一用 Location)。

3.2 触发流程/动作(无法名词化时)

http
POST /articles/42/publish      → 状态迁移:draft → published
POST /orders/7/cancel
POST /auth/login               → 或规范化为 POST /sessions
POST /graphql                  → 查询语言本身也走 POST

3.3 提交无法用其它方法表达的数据

如超长查询、文件分片(POST /uploads/{id}/parts)。

POST 重试问题: POST 非幂等,网络超时后客户端"敢不敢重发"是分布式经典问题。解法:幂等键(Idempotency-Key)——见下一章与进阶篇。

4. PUT:整体替换

RFC 9110 §9.3.6 规定 PUT 的语义:请求体是目标资源的新表示,服务器存储它,此后 GET 应返回它。

http
PUT /articles/42 HTTP/1.1
Content-Type: application/json

{ "title": "改过的标题", "content": "…", "authorId": 7 }

关键规则

  1. PUT 是替换不是更新:body 未提供的字段将被清除/置默认,不是保留旧值。所以 PUT 请求应携带客户端从 GET 拿到的全字段(不含只读字段)。
  2. PUT 幂等:连续发送同一 body 十次,资源终态与一次相同。注意"幂等"指终态而非计数——每次请求的日志、updatedAt 可以变化(RFC 9110 明确允许服务器保留重复检测信息)。
  3. PUT 创建(upsert):对不存在的 URI PUT 合法,可创建资源 → 返回 201 Created;更新成功返回 200(带更新后的表示)或 204 No Content
  4. PUT 到集合资源语义未定义——需要"用一批数据整体替换集合"时,显式设计资源(如 PUT /users/7/tags)。
  5. 条件 PUTIf-Match: "etag" 实现乐观锁,版本不符返回 412 Precondition Failed——修改类接口应默认要求版本校验,避免"最后写赢"覆盖他人编辑。

5. PATCH:局部修改

RFC 5789 定义 PATCH:"应用对资源的部分修改"。两种标准媒体类型:

5.1 JSON Merge Patch(RFC 7386)——朴素直观

http
PATCH /articles/42 HTTP/1.1
Content-Type: application/merge-patch+json

{ "title": "只改标题", "summary": null }   ← null = 删除该字段

语义:逐字段合并;null 表示删除;嵌套对象递归合并。无法表达"把数组替换为…"与"删除数组第 2 项"的区分——这是它的短板。

5.2 JSON Patch(RFC 6902)——精确指令

http
PATCH /articles/42 HTTP/1.1
Content-Type: application/json-patch+json

[
  { "op": "replace", "path": "/title", "value": "新标题" },
  { "op": "add", "path": "/tags/-", "value": "REST" },
  { "op": "test", "path": "/version", "value": 3 }    内置乐观锁
]

5.3 直接用裸 JSON 当补丁(多数团队的现状)

http
PATCH /articles/42
Content-Type: application/json

{ "title": "新标题" }

可行,但媒体类型没有表达"这是补丁"的语义,null 的含义(删除 vs 置空)需文档声明。规范推荐优先选 merge-patch 或 json-patch。

PATCH 不要求幂等(RFC 5789):如 {"count": "+1"} 式补丁就不幂等——若你的 PATCH 是"字段赋值"型,它事实上幂等,但重试安全性仍建议配 Idempotency-Key。

6. DELETE

http
DELETE /articles/42

HTTP/1.1 204 No Content     ← 最常见
HTTP/1.1 200 OK             ← 需要返回"删除结果/软删除后的资源"时
HTTP/1.1 410 Gone           ← 资源曾存在、已永久删除,后续访问者收到 410
HTTP/1.1 404 Not Found      ← 资源不存在

RFC 9110 §9.3.5:DELETE 的语义是"服务器对目标资源的当前表示执行不可逆删除"(实现可软删除)。幂等:第二次 DELETE 对资源状态的"影响"应为零——若返回 404,是否破坏幂等?RFC 的判据是"最终状态相同(该资源不存在)即可视为幂等"。工程惯例:重复删除返回 404204 均可接受,团队二选一写进规范;Google AIP 建议幂等返回 404 并明确文档化。

7. 方法支持度查询:OPTIONS

OPTIONS 回答"这个 URI 支持哪些方法":

http
OPTIONS /articles/42
HTTP/1.1 204 No Content
Allow: GET, PUT, PATCH, DELETE, OPTIONS
  • 浏览器的 CORS 预检(preflight)就是 OPTIONS——只要方法用得规范(简单请求 GET/HEAD/POST),大多数跨域请求可跳过预检。
  • 405 响应必须带 Allow 头(见状态码一章)。

8. 方法选择决策树

只是读取?                      → GET(确认无副作用!)
按 ID 创建,ID 客户端已知?       → PUT(upsert)
新建子资源,ID 服务器生成?        → POST → 201 + Location
修改部分字段?                   → PATCH
全量替换/重置资源?               → PUT
移除资源?                       → DELETE
状态迁移(发布/取消/退款)?       → 首选建模为状态字段 PATCH/PUT;
                                实在不行用 POST /{id}/动作 子资源

9. 本章小结

  • 九种方法中 API 设计常用的就是 GET/POST/PUT/PATCH/DELETE(+OPTIONS 服务于 CORS)。
  • 语义红线:GET 无副作用;PUT 是整体替换;POST 是服务器定义的扩展处理;DELETE 幂等靠"终态一致"理解。
  • 写操作的安全网:条件请求(If-Match)防并发覆盖,幂等键防重试重复。

10. 下一步

把"安全/幂等"两个词抠到定义级别 → 安全与幂等操作