HTTP方法的深度探源与RESTful API的设计思想

即使写程序很多年的老鸟,能真正说清楚道明白HTTP方法(HTTP Verbs / Methods)的也不多,多数都只会使用而不知道所以然。每个人都知道使用GET、POST方法,使用也很熟练,但是底层设计思想并不一定了解。这里,来认真梳理一下HTTP方法背后设计的逻辑。

HTTP Verbs,从词的本意来说,HTTP方法本质上是一组动作,它们是语义化(Semantics )的行为约束。

一. HTTP方法10种

HTTP 标准里常见的方法是这10 个:

方法主要作用
GET获取资源
POST提交数据 / 创建资源
PUT完整替换资源
PATCH部分修改资源
DELETE删除资源
HEAD获取响应头,不返回正文
OPTIONS查询服务器支持哪些方法/能力
CONNECT建立隧道
TRACE请求回显,用于诊断
QUERY发出搜索请求

二.HTTP方法两个核心设计理念:安全(Safe) 与 幂等(Idempotent)

  1. 安全(Safe):
    指调用该方法不会修改服务器上的数据(只读操作),直观理解就是不管调用多少次,数据库里的数据都不会被新增、修改或删除。
    Safe 方法的语义本质上是只读的,客户端不请求、也不期望服务器因为这个请求而发生状态变化,Safe ≠ Security ,更贴切的中文可以理解成无副作用请求,是安全方法。
  2. 幂等(Idempotent):
    指使用相同的参数执行一次与执行多次,对服务器产生的最终状态/结果是完全相同的。直观理解: f(x) = f(f(x))。比如把某人的余额直接“设置为 100 元”,执行 1 次还是 10 次,余额都是 100 元(幂等);但如果是“把余额加上 100 元”,执行 1 次和 10 次结果截然不同(非幂等)。
HTTP 方法安全性 (Safe)幂等性 (Idempotent)
GET
HEAD
OPTIONS
QUERY
TRACE
PUT
DELETE
POST
PATCH否*(⚠️ 不一定)

可以看出,DELETE是不安全但是是幂等的。幂等的核心含义在于对同一个请求执行一次,与执行多次,其“预期的服务器效果”相同,不是说每次 HTTP Response 必须完全一样。

第一次可能:

204 No Content

第二次可能:

404 Not Found

但它依然可以是幂等的,因为:

服务器资源最终状态
第一次删除 → 不存在
多次删除 → 仍然不存在

RFC 7231 明确指出,幂等性针对的是请求所要求的“预期效果”,响应本身可以不同。同时,但是 Safe ≠ 完全没有任何副作用,GET /article/123 ,服务器收到后可能:

读取文章

记录访问日志

统计访问次数

写日志数据库

所以服务器实际上发生了变化,但它仍然是 Safe,因为这些不是客户端通过 GET 请求所要求的业务状态变化。因此判断 Safe 时,关注的是:请求的“定义语义”是不是要求修改服务器资源,而不是“服务器内部是不是绝对什么东西都没改变。”

安全性和幂等性规范带来的好处:

—— 遵守安全与幂等约定,才能让 HTTP 缓存(浏览器缓存、Edge/CDN 节点)、反向代理(Nginx 重试机制)发挥最大效能。如果用 GET 去做写操作,预加载引擎或搜索引擎爬虫就可能误删数据。
—— 网络故障下的容错重试策略。

在分布式系统中,如果超时未收到响应:

—— 对于 GET / PUT / DELETE(幂等):客户端或网关可以安全地自动发起重试
—— 对于 POST(非幂等):不能盲目重试,必须依赖业务层引入防重 Token幂等键(Idempotency Key)

—— API 接口的契约可读性。遵循标准的语义,可以让 API 见名知意,极大地降低前后端协作与外部对接的沟通成本。

三. 10种HTTP方法的用法和特点

1. GET:获取资源

  // 给我用户 123 的数据
 GET /users/123   
 
  // 服务器返回
 {
  "id": 123,
  "name": "Andrew"
}
// 带查询条件获取列表
GET /users?page=2&keyword=tom

GET 的特点是: GET = 读取理论上不应该产生修改操作。

2. POST:提交数据,创建资源。

这个用的最多,也最容易理解。

大部分人容易陷入误区(如“GET 参数在 URL,POST 在 Body”或“POST 比 GET 更安全”),但这些只是浏览器实现或应用层的表现,并非 HTTP 规范的本质。

GET 的底层逻辑

—— 只读契约:客户端表明“我只是想拿数据,不会改变任何东西”。
—— 架构收益:因为安全且幂等,浏览器、CDN、代理服务器可以放心地缓存 GET 请求。这是整个互联网能够承受巨量流量的基础。

POST 的底层逻辑

—— 非幂等与状态变更:客户端表明“我要提交数据,请服务器按照自己的逻辑去处理”。
——架构收益:不强制要求针对特定 URI,处理逻辑高度灵活。由于非幂等,浏览器在刷新的 POST 页面时会弹窗警告(防止重复提交/重复扣款)。

3. PUT:完整更新

假设原来的用户:

{
   "id": 123,
   "name": "Andrew",
   "age": 30,
   "email": "a@test.com"
 }

发送:

 PUT /users/123
 
 {
   "name": "Tom",
   "age": 25,
   "email": "tom@test.com"
 }

语义是:

用新的完整数据替换原来的资源。

 旧用户
    ↓
 完全替换
    ↓
 新用户

4. PATCH:局部更新

例如,只改用户名:

PATCH /users/123
 {
   "name": "Tom"
 }

意思:

只修改 name,其他字段保持不变。

 PUT   → 全量更新
 PATCH → 部分更新

5. DELETE:删除

//删除用户 123
DELETE /users/123

服务器:

 204 No Content

或者:

 {
   "code": 200,
   "message": "Deleted successfully"
 }

6. HEAD 方法的本质就是:“不要主体(Body)的 GET 请求”

客户端向服务器发送 HEAD 请求时,服务器返回的响应头(Headers)与 GET 请求完全一致,但绝对不能包含任何响应体(Body)

HEAD 方法的核心价值在于零带宽成本地获取资源的元数据(Metadata)。在真实工程中,它有几个典型应用场景:

预先获取大文件信息

 HEAD /file.zip

可以先判断:文件存在吗? 多大? 什么类型? 有没有更新?

在下载几 GB 的大文件(如系统镜像、游戏包)之前,下载工具会先发一个 HEAD 请求:

校验文件大小: 检查 Content-Length,提前判断本地磁盘空间是否足够。

校验文件修改: 检查 ETagLast-Modified,对比本地已下载的文件片段,判断服务器上的文件是否被更新过。

测试链接的有效性(死链检测)

搜索引擎爬虫或网站链接检查器在校验成千上万个外链时,如果用 GET 请求,会把网页的 HTML、图片全下载下来,极度浪费流量和 CPU。使用 HEAD 请求,只需几百字节的 Header 就能确认目标页面是 200404 还是 301/302 重定向。

检查服务器对断点续传(Range Request)的支持

客户端想并发分多段下载一个文件前,会先发 HEAD 请求查看响应头中是否包含:

 Accept-Ranges: bytes

如果包含,说明服务器支持分段读取,客户端才可以放心开多线程并行下载。

高频轻量级健康检查(Health Check)

负载均衡器(如 Nginx、L4/L7 网关)或 Kubernetes Kubelet 需要每几秒钟探测一次后端服务是否活着。如果探测使用 GET,后端会不断渲染页面或查询数据库并返回响应体,造成无意义的性能损耗;而 HEAD 请求极轻,能将健康探测对业务系统的干扰降到最低。

7.OPTIONS

 OPTIONS /users

意思是:你这个资源支持什么操作?

服务器可能返回:

 Allow: GET, POST, OPTIONS

它还有一个非常重要的用途:

CORS 预检请求

例如:https://mosang.net,调用 API:https://api.xiaobage.com/,浏览器可能先自动发送:

 OPTIONS /users

询问服务器:“这个网站是否允许我跨域访问?”

所以后端日志里有时会看到:

 OPTIONS /api/users

这通常不是用户直接操作,而是浏览器自动发出的。

8. CONNECT

这个一般普通 Web 开发很少主动使用。

例如访问:HTTPS 网站,代理服务器需要建立隧道:

 浏览器
    │
 CONNECT
    ↓
 代理服务器
    │
 建立隧道
    ↓
 目标服务器

通常与:HTTPS Proxy 相关。

9. TRACE:会让服务器把请求“反射”回来。

例如:

 TRACE /users

服务器把:请求方法、请求头等信息返回。主要用于诊断、调试 HTTP 链路,但因为安全原因,很多服务器直接禁用了它。

10. QUERY

GET 只能把参数放在 URL(Query String)里。但现在的商业系统查询条件极度复杂(如复杂的 JSON 过滤树、长文本特征对比、大数组 ID 查询)。URL 长度是有限制的(通常浏览器/网关限制在 2KB ~ 8KB),超出就会抛出 414 URI Too Long 错误。为了解决 URL 长度限制,大家不得不改用 POST 把复杂查询条件放在 Body 里。但这一改就打破了规矩:

无法被 CDN 缓存: 默认情况下,几乎所有 CDN 和浏览器都拒绝缓存 POST 请求。

破坏安全性与幂等性: 监控系统和中间件会把这个查询误认为是“数据写操作”,引发误判。

为了解决这个“想用 Body 传复杂查询参数,但又想保持安全与可缓存”的僵局,IETF 提出了全新的 QUERY 方法(最早叫 SEARCH)。

QUERY 方法的官方定义是:Safe(安全的) + Idempotent(幂等的) + 支持 Request Body(有请求体)

 QUERY /products/search HTTP/1.1
 Host: api.example.com
 Content-Type: application/json
 ​
 {
   "category": "electronics",
   "price_range": {"min": 100, "max": 500},
   "tags": ["wireless", "gaming", "noise-canceling"],
   "sort_by": "rating_desc"
 }

它的核心意义在于:

语义精准: 明确告诉所有中间件“我只是在查数据(安全、幂等),请不要害怕,也不会修改服务端状态”。
容量无界: 查询条件可以任意复杂、任意大,完全放在 Request Body 中,再也不用担心 URL 超长。
重新解锁缓存(关键突破): 因为 QUERY 明确宣告了自己是安全的,CDN 和网关可以基于 URL + Request Body 的 Hash 值来重新实现自动化缓存。

四. HTTP方法语义化的本质

这些方法本质是在语义化描述:

这个请求是在读取、创建、替换、修改、删除,还是在查询通信能力。

HTTP Method 的语义可以帮助:

浏览器
HTTP 客户端
代理服务器
缓存系统
开发者

这能让整个互联网生态更好衔接,例如让服务器优化缓存、让中间件(CDN/代理/网关)能够盲操作,让爬虫与搜索引擎有确切的安全边界,同时标准化语义让接口设计变成了“统一语言”:

HTTP 方法不是简单的“不同名字”,它会影响:

缓存
重试
代理
安全策略
浏览器行为
CORS
服务器路由

统一的语义大幅降低了多端协同(前端、移动端、后端、第三方开放 API)的沟通与对接成本。

五. 语义化升级版:RESTful规范