48

我一直在 REST API 中使用 POST 来创建对象。每隔一段时间,服务器会创建对象,但客户端会在收到201 Created响应之前断开连接。客户端只看到一个失败的 POST 请求,稍后再试一次,服务器愉快地创建了一个重复的对象......

其他人一定有这个问题,对吧?但我在谷歌周围搜索,每个人似乎都忽略了它。

我有 2 个解决方案:

A) 改用 PUT,并在客户端上创建 (GU)ID。

B) 为客户端上创建的所有对象添加一个 GUID,并让服务器强制执行它们的UNIQUE-ness。

A 与现有框架不太匹配,而 B 感觉就像一个 hack。在现实世界中,其他人如何解决这个问题?

编辑:

使用 Backbone.js,您可以在客户端上创建对象时将 GUID 设置为 id。保存后,Backbone 将执行 PUT 请求。让您的 REST 后端处理 PUT 到不存在的 id,然后您就设置好了。

4

7 回答 7

12

另一个为此提出的解决方案是POST Once Exactly (POE),其中服务器生成一次性使用的 POST URI,当多次使用时,将导致服务器返回 405 响应。

缺点是 1) POE 草案被允许在没有任何标准化进一步进展的情况下过期,因此 2) 实施它需要更改客户端以使用新的 POE 标头,并且服务器需要额外的工作来实现 POE 语义。

通过谷歌搜索,您可以找到一些正在使用它的 API。

我解决这个问题的另一个想法是条件 POST,我在此处描述并要求提供反馈。

在唯一 URI 生成无法在客户端上放置并因此需要 POST 的情况下,对于防止重复资源创建的最佳方法似乎没有达成共识。

于 2013-04-11T23:09:18.693 回答
9

我总是使用 B - 由于服务器端的任何问题而检测重复。

于 2013-03-01T13:53:48.160 回答
7

重复检测是一个杂项,并且会变得非常复杂。真正不同但相似的请求可以同时到达,可能是因为网络连接已恢复。如果网络连接中断,重复请求可能会相隔数小时或数天。

其他回答者中对标识符的所有讨论都是为了在响应重复请求时给出错误,但这通常只会促使客户端获取或生成新的 id 并重试。

解决这个问题的一个简单而健壮的模式如下:服务器应用程序应该存储对不安全请求的所有响应,然后,如果他们看到重复的请求,他们可以重复之前的响应而不做其他任何事情对所有不安全的请求执行此操作,您将解决一堆棘手的问题。“重复”由应用程序级别的 id 确定,客户端生成的 GUID 或服务器生成的序列号。在第二种情况下,请求-响应应该专门用于交换 id。我喜欢这个解决方案,因为专门的步骤让客户认为他们得到了他们需要照顾的宝贵东西。如果他们可以生成自己的标识符,他们更有可能将此行放入循环中,并且每个该死的请求都会有一个新的 id。

使用此方案,所有 POST 都是空的,并且 POST 仅用于检索操作标识符。所有的 PUT 和 DELETE 都是完全幂等的:连续的请求得到相同的(存储和重放)响应并且不会导致进一步的事情发生。这种图案最好的地方是它的功夫(熊猫)品质。它有一个弱点:客户倾向于在收到意外响应时重复请求,并将其转化为力量 :-)

如果有人关心,我在这里有一个小谷歌文档。

于 2016-02-17T09:45:35.133 回答
5

您可以尝试两步法。您请求创建一个对象,该对象返回一个令牌。然后在第二个请求中,使用令牌请求状态。在使用令牌请求状态之前,您将其保持在“暂存”状态。

如果客户端在第一次请求后断开连接,他们将没有令牌,并且对象无限期地保持“暂存”状态,或者直到您使用另一个进程将其删除。

如果第一个请求成功,则您有一个有效的令牌,您可以根据需要多次获取创建的对象,而无需重新创建任何内容。

令牌没有理由不能是数据存储中对象的 ID。您可以在第一次请求期间创建对象。第二个请求实际上只是更新了“暂存”字段。

于 2013-03-01T14:05:37.023 回答
3

服务器发布的标识符

如果您要处理的是服务器发出标识符的情况,请在临时的分阶段状态下创建对象。(这是一个固有的非幂等操作,因此应该使用 POST 来完成。)然后客户端必须对其进行进一步的操作,以将其从暂存状态转移到活动/保留状态(这可能是资源的属性,或对资源的合适 POST)。

每个客户端都应该能够以某种方式获取处于暂存状态的资源列表(可能与其他资源混合),并且应该能够删除他们创建的资源(如果它们仍然只是暂存)。您还可以定期删除已停用一段时间的暂存资源。

您不需要向任何其他客户透露一个客户的暂存资源;只有在确认步骤之后,它们才需要全局存在。

客户发布的标识符

另一种方法是由客户端发出标识符。这主要在您对文件存储之类的东西进行建模时很有用,因为文件的名称通常对用户代码很重要。在这种情况下,您可以使用 PUT 来创建资源,因为您可以做到这一切都是幂等的。

这样做的缺点是客户端能够创建 ID,因此您完全无法控制他们使用的 ID。

于 2013-03-01T15:11:06.340 回答
2

这个问题还有另一种变体。让客户生成唯一的 id 表明我们正在要求客户为我们解决这个问题。考虑一个环境,我们有一个公开的 API,并且有 100 个客户端与这些 API 集成。实际上,我们无法控制客户端代码及其唯一性实现的正确性。因此,如果请求是重复的,最好有智能来理解。这里的一种简单方法是根据用户输入的属性计算和存储每个请求的校验和,定义一些时间阈值(x 分钟)并将来自同一客户端的每个新请求与过去 x 分钟内收到的请求进行比较。如果校验和匹配,则可能是重复请求,并为客户端添加一些质询机制来解决此问题。如果客户端在 x 分钟内使用相同的参数发出两个不同的请求,即使它带有唯一的请求 ID,也可能值得确保这是有意的。这种方法可能不适用于每个用例,但是,我认为这对于执行第二次调用的业务影响很大并且可能使客户付出代价的情况很有用。考虑支付处理引擎的情况,其中中间层最终重试失败的请求或客户双击导致客户端层提交两个请求。我认为这对于执行第二次呼叫的业务影响很大并且可能使客户付出代价的情况很有用。考虑支付处理引擎的情况,其中中间层最终重试失败的请求或客户双击导致客户端层提交两个请求。我认为这对于执行第二次呼叫的业务影响很大并且可能使客户付出代价的情况很有用。考虑支付处理引擎的情况,其中中间层最终重试失败的请求或客户双击导致客户端层提交两个请求。

于 2018-04-19T17:51:48.470 回答
1

设计

  • 自动(无需维护手动黑名单)
  • 内存优化
  • 磁盘优化

算法[解决方案1]

  1. REST 带有 UUID
  2. Web 服务器检查 UUID 是否在内存缓存黑名单表中(如果是,则回答 409)
  3. 服务器将请求写入数据库(如果没有被 ETS 过滤)
  4. DB 在写入之前检查 UUID 是否重复
  5. 如果是,则为服务器回答 409,并将内存缓存和磁盘列入黑名单
  6. 如果不重复写入 DB 并回答 200

算法[解决方案2]

  1. REST 带有 UUID
  2. 将 UUID 保存在 Memory Cache 表中(有效期为 30 天)
  3. Web 服务器检查 UUID 是否在内存缓存黑名单表中 [返回 HTTP 409]
  4. 服务器将请求写入 DB [返回 HTTP 200]

在方案2中,创建内存缓存黑名单的阈值仅在内存中创建,因此永远不会检查数据库是否有重复项。“重复”的定义是“在一段时间内出现的任何请求”。我们还将内存缓存表复制到磁盘上,因此我们在启动服务器之前填充它。

在解决方案 1 中,永远不会有重复,因为我们总是在写入之前只检查一次磁盘,如果它是重复的,则下一次往返将由内存缓存处理。这个解决方案更适合 Big Query,因为请求不是无能的,但它也不太优化。

资源已存在时 POST 的 HTTP 响应代码

于 2019-03-06T15:10:43.127 回答