两个被说烂了但常被搞混的词
翻 RFC 9110 或者任意一份 HTTP 方法对照表,都会看到「幂等」「安全」两列。很多人扫一眼就过去了,直到某天写重试逻辑、配缓存策略或者做接口评审时才发现:这两个属性直接决定了一个接口能不能被自动重试、能不能被缓存、能不能被爬虫预取。
它们的定义其实非常干净,只是名字容易误导。
安全:不改变服务器状态
安全(Safe) 指这个方法不会修改服务器上的资源状态。反复调用,服务器上的数据不会因此改变。
被规范明确列为安全方法的是 GET、HEAD、OPTIONS、TRACE。注意「安全」描述的是方法本身的语义,不代表实现一定守规矩——用 GET 请求去删除数据在技术上做得到,但那是违反规范的实现,会带来一连串问题,最常见的就是爬虫或浏览器预取把数据删了。
幂等:执行多次与执行一次效果相同
幂等(Idempotent) 指这个方法执行一次和执行多次,对服务器状态的影响相同。它不要求「不修改」,只要求「修改的结果一致」。
这个区别是关键。DELETE 会修改状态,但它是幂等的:第一次调用把资源删掉,再调一次,资源还是不存在,最终状态没变。PUT 也是幂等的:无论调用几次,资源都被替换成请求体里的那份内容。
POST 不是幂等的:每调用一次就创建一份新资源,调用三次就有三份。
一张对照表
| 方法 | 安全 | 幂等 | 说明 |
|---|---|---|---|
| GET | 是 | 是 | 只读取,不修改 |
| HEAD | 是 | 是 | 只取响应头,无响应体 |
| OPTIONS | 是 | 是 | 查询目标资源支持的通信选项 |
| PUT | 否 | 是 | 整体替换,重复执行结果一致 |
| DELETE | 否 | 是 | 删除后资源不存在,再删仍是这个结果 |
| POST | 否 | 否 | 重复提交会创建多份资源 |
| PATCH | 否 | 否 | 补丁语义由实现决定,规范不保证幂等 |
有一条推论值得记住:安全方法一定是幂等的,幂等方法不一定安全。 因为「不修改」天然满足「修改结果一致」。
为什么这个区分有实际意义
它决定能不能自动重试。 网络超时、连接被重置、网关返回 502,这些情况下客户端不知道请求有没有到达服务端。幂等方法可以放心重试,非幂等方法不能——重复提交可能造成重复下单、重复扣款、重复发短信。这也是为什么支付类接口宁愿设计成 PUT /orders/{id}/pay 这种幂等形式,也不愿用 POST /pay。
它决定能不能缓存。 浏览器和 CDN 只会缓存 GET(以及 HEAD)的响应。反向代理配置里那条「只缓存 GET」的规则,来源就是这个安全属性。
它决定爬虫敢不敢抓。 搜索引擎爬虫只对安全方法发起请求。一个用 GET 做删除操作的链接被爬虫扫到,数据就没了——这也是历史上最经典的一类事故。
它影响中间件的重试行为。 负载均衡器、服务网格、HTTP 客户端库普遍内置了「幂等请求自动重试」的策略。理解这两个属性,才能预判自己的接口会被怎样对待。
几个容易混淆的边界情况
PATCH 到底幂不幂等? 规范上不保证,取决于补丁内容。{"op":"replace","path":"/name","value":"x"} 是幂等的,而 {"op":"add","path":"/tags/-","value":"new"} 每次都会追加一个新元素,不幂等。设计补丁接口时要留意这一点。
POST 能不能做成幂等? 可以,但那是业务层的幂等,不是方法本身的属性。常见做法是让客户端带上一个唯一的幂等键(Idempotency-Key 请求头或业务单号),服务端对同一个键只处理一次,后续请求直接返回首次结果。
GET 带查询参数做筛选,还算安全吗? 算。安全的判断依据是「是否修改状态」,参数多少不影响。但要避免把写操作伪装成 GET,那属于违反语义。
HEAD 有什么用? 只取响应头,常用于检查资源是否存在、是否被修改、文件大小与类型,能在不传输响应体的前提下拿到元信息,省带宽也省时间。
设计接口时的检查清单
- 写操作是否都能用幂等方法表达(用资源 ID 定位,而非提交动作)
- 无法幂等化的操作是否提供了幂等键机制
- 是否有任何修改状态的逻辑挂在 GET 上
- 缓存策略是否只作用于安全方法
- 客户端重试策略是否按方法的幂等性做了区分