Appearance
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/jsonRFC 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 → 查询语言本身也走 POST3.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 }关键规则
- PUT 是替换不是更新:body 未提供的字段将被清除/置默认,不是保留旧值。所以 PUT 请求应携带客户端从 GET 拿到的全字段(不含只读字段)。
- PUT 幂等:连续发送同一 body 十次,资源终态与一次相同。注意"幂等"指终态而非计数——每次请求的日志、
updatedAt可以变化(RFC 9110 明确允许服务器保留重复检测信息)。 - PUT 创建(upsert):对不存在的 URI PUT 合法,可创建资源 → 返回
201 Created;更新成功返回200(带更新后的表示)或204 No Content。 - PUT 到集合资源语义未定义——需要"用一批数据整体替换集合"时,显式设计资源(如
PUT /users/7/tags)。 - 条件 PUT:
If-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 的判据是"最终状态相同(该资源不存在)即可视为幂等"。工程惯例:重复删除返回 404 或 204 均可接受,团队二选一写进规范;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. 下一步
把"安全/幂等"两个词抠到定义级别 → 安全与幂等操作