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

HTTP认知溯源

HTTP方法本质上就是一个普通的 ASCII 编码字符串,我们可以自定义HTTP方法。从这一点看每个http方法没有什么区别。但是,在应用层,考虑到缓存与网络效能、容错与重试机制、安全隔离与攻击防御等,浏览器、路由器、防火墙、CDN节点、NGINX等应用对各方法做了区别对待。

HTTP方法是在安全和幂等的理念下设计的,语义化的命名有助于互联网生态的协作,在缓存、爬虫、搜索、CDN分发、前后端数据交互等方面能更低成本协作。而REST则是一种设计API的理念,即使用 URI 来标识资源,使用标准的 HTTP 方法 来表示操作。RESTful则是符合这种理念的API。

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

一. HTTP方法常见10种

HTTP 标准里常见的10 个:

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

(实际上HTTP方法数量多达几十种,这里只是列举常见的方法,详见文末)

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


注意:以上的标准是2014年颁布的RFC 7231,QUETY标准出自2026颁布的RFC 10008

  1. 安全(Safe):
    指调用该方法不会修改服务器上的数据(只读操作),直观理解就是不管调用多少次,数据库里的数据都不会被新增、修改或删除。
    Safe 方法的语义本质上是只读的,客户端不请求、也不期望服务器因为这个请求而发生状态变化,Safe ≠ Security ,更贴切的中文可以理解成无副作用请求,是安全方法。
  2. 幂等(Idempotent):
    指使用相同的参数执行一次与执行多次,对服务器产生的最终状态/结果是完全相同的。直观理解: f(x) = f(f(x))。比如把某人的余额直接“设置为 100 元”,执行 1 次还是 10 次,余额都是 100 元(幂等);但如果是“把余额加上 100 元”,执行 1 次和 10 次结果截然不同(非幂等)。
    “幂等”最重要的实际价值:允许安全重试,这是 RFC 定义幂等性的真正工程意义。
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)的沟通与对接成本。

在现实的工程实践中,像 Laravel(PHP 领域代表)、Spring Boot(Java 领域代表)以及 Ruby on RailsExpress/NestJS(Node.js)等主流 Web 框架,从框架设计层面,是完全且深度遵循并拥抱这种语义化 HTTP 方法。

Spring Boot (Java):

@RestController
@RequestMapping("/users")
public class UserController {
    // 严格匹配 HTTP DELETE /users/123
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteById(id);
        return ResponseEntity.noContent().build(); // 返回 204 No Content
    }
}

Laravel (PHP):

// routes/api.php 严格匹配 HTTP DELETE /api/users/123
Route::delete('/users/{id}', [UserController::class, 'destroy']);

五. 语义化的落地执行:RESTful规范

REST 到底是什么?

REST的全称是Representational State Transfer,中文对应词是“表现层状态转移”。REST 是“理论/架构风格”(概念),而 RESTful 是“符合这种理论的具体实现/设计”(属性/修饰词)。

话说REST这个名字实在是个很差劲的表达,晦涩难懂。甚至连许多搞了多年 RESTful API 架构的人,第一次看到 Representational State Transfer 以及中文翻译“表现层状态转移”时,也是一头雾水。

这个词是HTTP/1.1 的主要架构师之一Roy Thomas Fielding,在他的博士论文中提出的,是一个学术味浓、表达追求极度严谨的词,是一个典型的“学术抽象合成词”。

1. Representation(表征 / 表现层)

字面意思:某样东西的“呈现形式”或“代名符”。
大白话数据格式(如 JSON、XML、HTML、图片等)。
底层逻辑:你在服务器数据库里存储的某条用户记录(比如包含各种外键、原始字节的二进制数据),并不是直接传给客户端的。服务器会把它包装成某种“表征形式”(比如一段 JSON 字符串 {"name": "張三", "age": 20})发给客户端。客户端看到的永远是资源的“表征”,而不是资源本身。

2. State(状态)

字面意思:当前所处的情况或数据。

大白话应用/数据的当前页面或数据快照
底层逻辑:这里包含两种状态:
资源状态(Resource State):服务器端存储的数据当前长什么样(比如订单是“已支付”还是“待发货”)。
应用状态(Application State):客户端当前处于什么流程或页面(比如你在浏览商品列表页,还是购物车结算页)。

3. Transfer(转移 / 传输)

字面意思:移动、传递。
大白话在网络上搬运/传递
底层逻辑:客户端和服务器通过 HTTP 协议把这种“表征”在网络上来回传递,从而让客户端从一个状态“跳转/迁移”到另一个状态。

如果抛弃那些晦涩的学术词汇,用更符合人类直觉的语言,REST 其实可以翻译为:“基于资源表征的状态切换”“通过数据快照驱动应用跳转”

REST 最核心的思想:把一切东西看成 Resource

假设系统里面有:

用户
订单
商品
文章
文件
评论

REST 会把这些抽象成:

Resources(资源)

例如:

用户:
/users/100

订单:
/orders/12345

商品:
/products/888

文章:
/articles/50

于是 REST 的思维发生了一个非常重要的变化:

传统 API 很容易这样设计:

/getUser
/createUser
/updateUser
/deleteUser

/getOrder
/createOrder
/updateOrder
/deleteOrder

REST 则倾向于:

/users/100
/orders/12345

API设计成:

GET    /users/100
POST   /users
PUT    /users/100
DELETE /users/100

动作由 HTTP Method 表达,资源由 URL 表达,这其实就是 REST 最核心表达,RESTful就是符合这种风格的API设计。

REST 不是协议,REST 更像一套设计 Web 系统的原则,其中对 API 设计影响最大的是:Uniform Interface(统一接口),这也是 REST 最精髓的地方之一。

HTTP方法非常广泛:远不止以上举例的10个

在文初列出的这 10 个(基础 9 个 + 新增的 QUERY)是 HTTP 核心/通用标准 中最主流的方法。但实际上,远远止于这 10 个。HTTP 协议在设计之初就具备极强的扩展性,标准规范(IANA 注册表)中记载的官方 HTTP 方法以及各种扩展协议(如 WebDAV)中的方法多达几十个

一、 WebDAV 扩展方法(分布式创作与版本控制)

WebDAV(RFC 4918) 是 HTTP 最著名的扩展协议之一(用于网盘、网关、在线文档协作、日历同步等)。它新增了一整套处理文件和目录的管理方法:

方法主要作用
PROPFIND获取资源的属性(如文件大小、创建时间、修改作者等)
PROPPATCH修改或删除资源的属性
MKCOL创建集合/文件夹(Make Collection)
COPY复制文件或目录到指定位置
MOVE移动或重命名文件/目录
LOCK锁定资源,防止多人协同编辑时发生冲突
UNLOCK解除资源的锁定状态

二、 CalDAV / CardDAV 扩展方法(日历与通讯录同步)

苹果 iOS、Google Calendar 和 Outlook 在同步日历与联系人时,底层走的也是 HTTP 扩展方法(基于 RFC 4791):

方法主要作用
REPORT用于根据特定条件(如时间段)检索日历事件或通讯录条目

三、 Delta Encoding 扩展(增量传输)

用于优化网络传输效率的 HTTP 扩展规范(RFC 3229):

方法主要作用
A-IM(Accept-Instance-Manipulation) 用于请求资源的增量差异(只获取改动的部分,无需下载整个新文件)

四、 安全与授权领域方法

方法主要作用
ACL用于修改资源的访问控制列表(Access Control List),定义谁有权限读写该资源

我们可以自定义HTTP方法

从 HTTP 协议规范的角度来看,HTTP 方法本质上就是一个 ASCII 字符串(区分大小写,由英文字母构成)。

只要客户端和服务端协商一致,你完全可以自己发明一个全新的方法,比如定义一个 PURGE 请求用来清空 CDN 缓存:

PURGE /static/main.js HTTP/1.1
Host: cdn.example.com

例如:著名的 HTTP 缓存代理服务 VarnishNginx (带有 ngx_cache_purge 模块),在现实工程中就大量使用自制的 PURGE 方法来精准清除缓存。

我们在前端也可以使用自定义方法来与后端数据交互:

fetch('https://api.xiaobage.cc/cache', {
  method: 'PURGE', // 自定义 HTTP 方法
  headers: {
    'Content-Type': 'application/json'
  }
})
.then(response => response.json())
.then(data => console.log(data));

当你运行这段代码时,浏览器会毫无保留地将这个请求封装好并发送出去。在浏览器的 F12 开发者工具(Network 面板)以及 WireShark 抓包中,可以清楚地看到请求行第一行就是:

PURGE /cache HTTP/1.1 Host: api.example.com

虽然浏览器会帮你发出去,但会触发以下两个重要机制:

1. 跨域时,必然触发 OPTIONS 预检请求(CORS)。因为自定义方法(如 PURGE)不属于 CORS 规范中的“简单方法”(GET、POST、HEAD),所以如果发起跨域请求,浏览器会在发送 PURGE 之前,强制先发一个 OPTIONS 预检请求
2. HTML 原生标签不支持。 无法通过原生 HTML 元素(如 <form method="PURGE">)来发送自定义方法。如果强行写入,浏览器解析 HTML 时会自动将其退化/降级为默认的 GET 请求。

另外,Nginx / 代理服务器默认情况下,Nginx 等 Web 服务器能够接收并转发任何标准的或自定义的 HTTP 方法。但如果在配置中写了严格的匹配规则(例如 if ($request_method !~ ^(GET|POST)$ ) { return 405; }),自定义方法就会被直接拦截并返回 405 Method Not Allowed。WAF / 防火墙 / CDN为了防范未知攻击,会默认将非标准 HTTP 方法直接阻断。

现实情况:绝对的“RESTful”很少,几乎绝迹

例1.

假设我们在开发一个电商系统,要获取订单详情并完成支付。

GET /api/v1/orders/8888 HTTP/1.1
Host: api.example.com

服务器响应:

{
  "order_id": 8888,
  "status": "UNPAID",
  "total_amount": 199.00,
  "items": ["MacBook Pro Cover"]
}

下一步支付: 前端开发者必须查阅 API 文档,得知“支付订单要发 POST 到 /api/v1/orders/8888/pay”,然后在代码里写死这个逻辑:

// 前端代码硬编码了 URI 路径和业务判断逻辑
if (order.status === "UNPAID") {
  axios.post(`/api/v1/orders/${order.order_id}/pay`);
}

这显然支付不符合RESTful标准。

在 Roy Fielding 理想的绝对 RESTful 系统中,客户端不需要预先看文档,也不需要硬编码任何业务 URL。API 像浏览器刷网页一样,通过响应中返回的链接(Links)驱动客户端做出下一步动作。

同样是获取订单详情:

请求:

GET /api/v1/orders/8888 HTTP/1.1
Host: api.example.com

响应:

{
  "order_id": 8888,
  "status": "UNPAID",
  "total_amount": 199.00,
  "_links": {
    "self": { "href": "/api/v1/orders/8888", "method": "GET" },
    "cancel": { "href": "/api/v1/orders/8888/cancel", "method": "DELETE" },
    "pay": { 
      "href": "/api/v1/payments", 
      "method": "POST",
      "title": "Pay this order",
      "schema": { "payment_method": "credit_card|alipay" }
    }
  }
}

下一步支付: 前端完全不关心支付的 URL 长什么样。它只需要寻找响应里的 _links.pay 属性,直接发起调用:

// 前端不需要拼接 URL,完全由响应中的超链接动态驱动
if (response._links.pay) {
  axios({
    method: response._links.pay.method,
    url: response._links.pay.href,
    data: { payment_method: "alipay" }
  });
}

如果订单状态变成了“已支付”,后端返回的 JSON 中就会自动抹去 paycancel 链接,替换为 ship(发货)或 refund(退款)链接。

这种绝对的RESTful(Level 3:HATEOAS),看起来非常优雅(后端改变 URL 不会破坏前端,解耦到了极致),但在实际开发中,它存在不可忽视的硬伤:

客户端往往是专用 UI:网页和 APP 的界面按钮是固定的,即使后端动态给出了一个 refund 链接,前端 UI 如果没有提前画好“退款”按钮和交互弹窗,依然无法处理。

前后端开发成本飙升:后端需要为每个 API 动态计算和拼接庞大的 _links 元数据,数据传输体积变大;前端逻辑也变得高度抽象,调试极其痛苦。

前后端协作模式变化:现实中前后端通常由不同团队并行开发,双方通过 Swagger / OpenAPI 等静态文档来约定 API 格式,而不是靠运行时的动态发现。

因此,GitHub APIPayPal API 等少数企业曾尝试引入 HAL/HATEOAS 规范,但在全行业范围内,99% 的 API 都选择停留在了“用 URI 表示资源 + 用 HTTP 动词表示操作 + 返回 JSON 数据”的 Level 2 阶段。这就是为什么绝对的 RESTful 极其罕见的原因。

例2 最典型的:一个动作根本不是 CRUD

例如电商:

POST /orders/123/cancel

这其实就不是严格意义上的:

DELETE /orders/123

因为:

取消订单 ≠ 删除订单

订单仍然存在,只是状态从:

PENDING

CANCELLED

所以你可能会设计:

POST /orders/123/cancel

甚至:

POST /orders/123/pay
POST /orders/123/ship
POST /orders/123/confirm
POST /orders/123/refund

这些明显带有 RPC(Remote Procedure Call) 的味道:

“请服务器执行这个动作。”

而不是纯粹:

“我正在操作一个资源。”

这在现实中非常普遍。

例3 “执行任务”特别难 REST 化

例如 PDF 服务:

POST /pdf/generate

或者:

POST /documents/123/render

服务器可能需要:

读取模板

加载字体

生成 PDF

合成图片

上传 OSS

返回文件

这实际上是:

Command / Job

而不是简单的 Resource CRUD。

真正的大型 API 往往长这样:

                API

        ┌─────────┴─────────┐
        │                   │
   Resource API        Command API
        │                   │
    RESTful             RPC-like
        │                   │
   /users/123          /login
   /orders/123         /orders/123/pay
   /products/123       /orders/123/cancel
   /comments/123       /products/search
                       /reports/export
                       /pdf/generate

这才是最常见的。

我们需要知道的是,REST 不是一种技术,而是一种“用标准格式(如 JSON/HTML)在客户端和服务器之间传递资源快照,从而驱动业务流程演进”的设计风格。