避免使用REST重复POST

时间:2013-03-01 13:46:36

标签: rest post atomicity

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

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

我有2个解决方案:

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

B)向客户端上创建的所有对象添加GUID,并让服务器强制执行UNIQUE -

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

编辑:

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

7 个答案:

答案 0 :(得分:8)

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

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

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

我解决这个问题的另一个想法是有条件的POST,我描述了它并要求对here提供反馈。

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

答案 1 :(得分:7)

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

答案 2 :(得分:5)

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

其他anwsers中对标识符的所有讨论都是为了响应重复请求而给出错误,但这通常只会诱使客户端获取或生成新ID并再次尝试。

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

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

如果有人关心的话,我会有一些谷歌文档here

答案 3 :(得分:2)

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

如果客户端在第一次请求后断开连接,则他们将不会拥有该令牌,并且该对象将无限期地“暂存”或直到您使用其他进程将其删除。

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

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

答案 4 :(得分:2)

服务器发布的标识符

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

每个客户都应该能够以某种方式获得他们在阶段状态下的资源列表(可能与其他资源混合)并且应该能够删除他们创建的资源(如果他们仍然刚刚上演)。您还可以定期删除已暂停一段时间的暂存资源。

您无需向任何其他客户端显示一个客户的暂存资源;只有在确认步骤之后,它们才需要在全球范围内存在。

客户发布的标识符

另一种方法是让客户发出标识符。这对于像文件存储这样的建模非常有用,因为文件名通常对用户代码很重要。在这种情况下,您可以使用PUT来创建资源,因为您可以完全无意义地完成。

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

答案 5 :(得分:0)

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

答案 6 :(得分:0)

设计

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

算法[解决方案1]

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

算法[解决方案2]

  1. REST随UUID一起到达
  2. 将UUID保存在“内存缓存”表中(有效期30天)
  3. Web服务器检查UUID是否在“内存缓存黑名单”表中[返回HTTP 409]
  4. 服务器将请求写入数据库[返回HTTP 200]

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

在解决方案1中,将永远不会有重复项,因为在写入之前,我们始终只对磁盘进行一次检入,如果已复制,则下一次往返将由内存缓存处理。对于大查询而言,此解决方案更好,因为请求中没有幂等,但优化程度也较低。

HTTP response code for POST when resource already exists