Create, Update 和 HTTP 幂等性

本文详细解释了HTTP协议中POST和PUT方法的区别,特别是在创建和更新资源时的应用场景。通过对比幂等性特点,帮助开发者了解如何正确选择HTTP方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

译自原文:https://stormpath.com/blog/put-or-post
原作者:Stormpath 团队

2016/5/12更新:观看 Stormpath Hazlewood 的介绍 REST + JSON API 设计最佳实践.

别忘了, Stormpath 无需代码保护您的 API 的认证

创建、更新和 HTTP 幂等性

对于构建基于 REST 的 api 的开发人员来说,有大量的错误信息和关于何时使用 http 发布和何时使用 http POST 的一些可理解的混淆。有人说 POST 应用于创建资源,PUT 应用于修改资源. 还有人说 PUT 应用于创建资源 POST 应用于修改资源。两者都不太正确。

通常,开发人员认为每个 HTTP 方法都与 CRUD 操作有 1:1 的关系:

CRUDHTTP
CreatePOST
ReadGET
UpdatePUT
DeleteDELETE

这可以是真的,特别是GET和DELETE,但当涉及到哪些 HTTP 方法应该与创建和更新关联时,答案归结为 幂等性

幂等性

幂等性HTTP 规范 中的一个重要概念,无论执行同一请求多少次,幂等 HTTP 请求都将导致服务器的相同状态。 GETHEADPUT,和 DELETE 都具有此属性,但 POST 没有。

为了帮助说明幂等性,我们将使用一个帐户集合(*/accounts),为了简洁起见,我们假定,每个帐户资源有三属性称为givenNamesurname,和 status.

假设您使用 HTTP PUT 方法提交更新请求。在body里,你把 givenName 值定为 “John”,”surname” 值定为 “Smith”。然后,您提交另一个请求 HTTP PUT请求,这次设置 “givenName” 为 “Johnny”。这是幂等吗?不。为什么?因为其他请求 可能 在我们两个请求之间更改了帐户资源的服务器状态。例如,在两个请求之间,”status” 可能被更改为 “blocked”。我们的示例请求无法保证服务器上帐户的状态在重复时是 相同的

请求:

HTTP/1.1 PUT /accounts/abcdef1234
{
      "givenName": "John",
      "surname": "Smith"
}

在我们的两个请求之后可能的帐户状态(由于其他请求的副作用):

{
      "givenName": "John",
      "surname": "Smith",
      "status": "enabled"
}

或者

{
      "givenName": "John",
      "surname": "Smith",
      "status": "disabled"
}

引述 Dino Chiesa,“PUT 意味着放入一个资源 - 将任何在给定的 URL 可用的资源用不同的资源完全替换。” 使用 “PUT” 请求时, 必须将可用的属性 / 值(而不仅仅是要更改的) 全部 发送。如果我们想发送“disabled”的状态, 而不是 “givenName” 和 “surname”, PUT调用将是幂等的,且消除副作用的。幂等性是 HTTP 规范的基本属性, 必须坚持以保证 web 的互通性和规模化。

最后, 我们应该指出, HTTP 幂等性 仅适用于 服务端 状态, 而不是客户端状态。例如, 客户端可以成功地发送服务器幂等请求, 然后再次发送相同的精确服务器幂等请求, 并遇到错误(例如, 可能是由于服务器中的约束冲突), 这完全是 “合法的”。只要 http 请求导致服务器上相同的状态,就能维持 HTTP 幂等性。

HTTP POST vs HTTP PUT

既然 幂等性 是明确的, 那么在执行创建和更新操作时应该使用哪一种方法?以下是适当使用每个方法时的快速参考。

创建

当您不知道资源标识符时, 使用 POST 创建资源。使用 POST 创建后, 最好的做法是返回 “201 Created” 的状态和新创建的资源的位置, 因为在提交时它的位置未知。这允许客户端稍后访问新资源 (如果需要)。

HTTP/1.1 POST /accounts
{
      ...
}

返回:

201 Created
Location: https://api.stormpath.com/accounts/abcdef1234

当允许客户端指定新创建的资源的资源标识时, 请使用 PUT。但是请记住, 既然 PUT 是幂等请求, 你必须发送所有可能的值。

HTTP/1.1 PUT /accounts/abcdef1234
{
      "givenName": "John",
      "surname": "Smith",
      "status": "enabled"
}

更新

您可以使用 POST 来发送所有可用值或仅提供可用值的子集:

HTTP/1.1 POST /accounts/abcdef1234
{
      "status": "disabled"
}
Response 200 OK

如果要使用 PUT 更新资源, 则必须是完整的资源更新;必须在 “提交” 请求中发送所有属性值以保证幂等性。

当需要或需要发送 所有 可用值以遵循幂等性要求时, 请使用 PUT, 例如, 在完全更新资源的情况下。

HTTP/1.1 PUT /accounts/abcdef1234
{
      // 完全更新资源
      "givenName": "J",
      "surname": "Smith",
      "status": "Enabled"
}

您也可以使用 POST 来发送所有值, 服务器状态可能与 PUT 完全相同–它只是不需要由 HTTP 规范来进行。请注意, 幂等性与 HTTP 缓存服务器可缓存的相关性很强, 因此 POST 请求通常不会被缓存。如果您对此缓存副作用感觉OK, 则可以使用 POST 来进行完全更新和部分更新。

POST 是当前唯一的非幂等方法。HTTP 规范是非常通用的, 并且基本上将其声明为 “服务器处理指令”。这意味着在 POST 请求中做任何您想做的事情是 “安全的”。

最后, 我们应该注意到还有另一种方法尚未最后确定的 HTTP 规范, 称为 PATCHPATCH 可替代 POST 进行部分更新。然而, 由于 POST 已经可以简单地处理部分更新, 因此似乎没有足够的理由使用 PATCH,如果 HTTP 规范委员会批准,PATCH 将并入 POST 成为唯一的其他非幂等 HTTP 方法。

<think>好的,用户想了解接口幂等性。首先,我需要回忆接口幂等性的定义核心概念。幂等性指的是无论请求执行多少次,结果都保持一致。这在实际应用中非常重要,尤其是在网络通信可能引发重复请求的情况下。 接下来,应该从为什么需要幂等性入手。比如,用户可能在支付时重复点击提交,如果没有幂等性处理,可能导致多次扣款。这里需要举例说明,让用户更容易理解。 然后,得考虑常见的应用场景。比如HTTP方法的幂等性,GET、PUT、DELETE通常是等的,而POST不是。这部分需要准确,避免错误。同时,实际开发中的场景,如分布式系统、消息队列重复消费等,也是重点。 接下来是解决方案。需要分点说明不同的实现方式,比如唯一标识、状态机、Token机制、数据库约束等。每个方法要简要解释其原理适用情况,比如使用唯一ID可以通过Redis或数据库去重,状态机则适用于有状态流转的业务。 还要注意区分不同方法的优缺点。比如Token机制可能增加一次交互,但安全性较高;数据库约束则依赖唯一索引,可能对分库分表有影响。需要确保信息准确,不会误导用户。 另外,用户可能对如何选择适合的方案有疑问,所以需要给出一些选择的依据,比如根据业务特点、系统复杂度等。同时,提到常见的注意事项,比如全局唯一ID的生成、并发控制,这些是实际开发中容易遇到的问题。 需要检查是否有遗漏的重要点,比如重试机制与幂等性的关系,或者如何测试幂等性。不过用户的问题比较基础,可能不需要深入这些细节,但可以简要提及。 最后,确保整个回答结构清晰,分步骤讲解,使用用户容易理解的语言,避免过于技术化的术语。同时,按照系统指令正确使用LaTeX格式,比如在提到数学概念时用$...$包裹,但根据用户的问题,可能这方面内容不多,但如果有涉及公式的地方需要正确应用。 总结下来,回答的结构应该是:定义、重要性、应用场景、解决方案、注意事项。这样用户能逐步理解,并且得到实用的信息。需要避免错误,比如混淆幂等性一致性,或者解决方案中的技术细节错误。同时,用实际例子帮助用户建立直观认识,比如支付接口的例子。</think>### 接口幂等性详解 #### 1. **定义** 接口幂等性指**无论调用次数如何,操作结果始终一致**。数学上可表示为: $$ f(f(x)) = f(x) $$ 即多次执行与单次执行对系统状态的影响相同。 #### 2. **为什么需要幂等性?** - **网络不确定性**:超时重试、客户端重复提交 - **分布式系统一致性**:服务间调用可能因故障重复触发 - **资金安全**:支付、扣款等场景需避免重复操作 #### 3. **典型应用场景** | 场景类型 | 示例 | |------------------------|-------------------------------| | HTTP方法 | GET(查询)、PUT(覆盖更新) | | 支付系统 | 订单重复提交 | | 消息队列 | Kafka/RocketMQ消息重复消费 | | 微服务调用 | Feign重试机制 | #### 4. **实现方案** ##### 方案1:唯一标识法 - **原理**:通过唯一ID标记请求 - **实现**: ```sql CREATE TABLE idempotent_keys ( request_id VARCHAR(64) PRIMARY KEY, created_time TIMESTAMP ); ``` - **工具**:Redis SETNX、数据库唯一索引 ##### 方案2:状态机控制 - **适用场景**:订单状态流转(如:已创建 → 已支付 → 已完成) - **关键逻辑**: ```java if(currentStatus.canTransitionTo(targetStatus)){ // 执行状态变更 } ``` ##### 方案3:Token令牌机制 1. 客户端预申请令牌 2. 服务端存储令牌(有效期通常5-10分钟) 3. 业务请求携带令牌 4. 校验后立即删除令牌 ##### 方案4:数据库约束 - 使用唯一索引防止重复插入 - 乐观锁实现: ```sql UPDATE account SET balance = balance - 100, version = version + 1 WHERE user_id = 123 AND version = 5 ``` #### 5. **方案选型对比** | 方案 | 优点 | 缺点 | |--------------|-------------------------|---------------------------| | 唯一标识 | 实现简单 | ID存储成本高 | | 状态机 | 符合业务逻辑 | 需完整状态设计 | | Token机制 | 安全性高 | 增加一次交互 | | 数据库约束 | 强一致性保证 | 影响分库分表 | #### 6. **注意事项** 1. **全局唯一ID生成**:推荐雪花算法(Snowflake)、UUID 2. **并发控制**:分布式锁(Redisson)处理高并发场景 3. **时间窗口**:合理设置去重时效(如支付系统一般15分钟) 4. **日志追踪**:通过trace_id实现全链路追踪 #### 7. **测试方法** - 工具:Postman、Jmeter - 测试用例: 1. 正常请求返回200 2. 重复请求返回304/409 3. 并发请求保证数据一致 **示例场景**: 电商支付接口设计: 1. 用户提交订单生成唯一order_no 2. 支付请求必须携带order_no 3. 服务端校验: ```java if(redis.exists(order_no)){ return "请勿重复支付"; } else { redis.setex(order_no, 900, "processing"); // 执行支付逻辑 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值