我一直在REST API中使用POST来创建对象。每隔一段时间,服务器就会创建一个对象,但客户端会在收到201 Created
响应之前断开连接。客户端只能看到失败的POST请求,稍后再次尝试,服务器会愉快地创建一个重复的对象......
其他人一定有这个问题吧?但我谷歌周围,每个人似乎都忽略了它。
我有2个解决方案:
A)改为使用PUT,并在客户端上创建(GU)ID。
B)向客户端上创建的所有对象添加GUID,并让服务器强制执行其UNIQUE
-ness。
A与现有框架不匹配,B感觉像是黑客。在现实世界中,其他人如何解决这个问题?
编辑:
使用Backbone.js,您可以在客户端上创建对象时将GUID设置为id。保存后,Backbone将执行PUT请求。让你的REST后端处理PUT到不存在的id,然后你就设置了。
由于服务器端的任何问题,我总是使用B - 检测重复。
另一个为此提出的解决方案是POST Once Exactly (POE),其中服务器生成一次性POST URI,当多次使用时,将导致服务器返回405响应。
缺点是1)允许POE草案在标准化没有任何进一步进展的情况下到期,因此2)实现它需要改变客户端以利用新的POE头,以及服务器实现POE语义的额外工作。
通过谷歌搜索,您可以找到一些正在使用它的API。
我解决这个问题的另一个想法是条件POST,我描述并询问有关here的反馈。
在唯一的URI生成无法在客户端上进行PUT并因此需要POST的情况下,似乎没有就防止重复资源创建的最佳方法达成共识。
检测重复是一个问题,并且可能变得非常复杂。真正不同但相似的请求可以同时到达,可能是因为网络连接已恢复。如果网络连接中断,重复请求可以分开数小时或数天。
所有关于其他anwsers中的标识符的讨论都是为了响应重复请求而给出错误,但这通常只会诱使客户端获取或生成新ID并再次尝试。
解决此问题的简单而强大的模式如下:服务器应用程序应存储对不安全请求的所有响应,然后,如果它们看到重复请求,则可以重复先前的响应而不执行任何其他操作。为所有不安全的请求执行此操作,您将解决一系列棘手的问题。 “Duplicate”由应用程序级别id确定,可以是客户端生成的GUID,也可以是服务器生成的序列号。在第二种情况下,请求响应应专用于交换id。我喜欢这个解决方案,因为专门的步骤让客户认为他们正在获得他们需要照顾的宝贵东西。如果他们可以生成自己的标识符,他们更有可能将此行放在循环中,并且每个血腥请求都会有一个新的id。
使用此方案,所有POST都为空,POST仅用于检索操作标识符。所有PUT和DELETE都是完全幂等的:连续请求获得相同(存储和重放)响应并且不会导致任何进一步发生。这种模式最好的是它的功夫(熊猫)品质。它需要一个弱点:客户在获得意外响应时重复请求的倾向,并将其转化为力量:-)
我有一个小的谷歌文档here,如果任何人关心。
你可以尝试两步法。您请求创建一个对象,该对象返回一个令牌。然后在第二个请求中,使用令牌请求状态。在使用令牌请求状态之前,您将其保持“暂存”状态。
如果客户端在第一个请求之后断开连接,则它们将不具有令牌,并且对象将无限期地保持“暂存”状态,或者直到您使用其他进程将其删除。
如果第一个请求成功,则您拥有一个有效的令牌,您可以根据需要多次获取创建的对象,而无需重新创建任何内容。
令牌没有理由不能成为数据存储中对象的ID。您可以在第一个请求期间创建对象。第二个请求实际上只是更新了“暂存”字段。
如果您正在处理发出标识符的服务器的情况,请以临时暂存状态创建对象。 (这是一个固有的非幂等操作,所以应该用POST来完成。)然后客户端必须对它进行进一步的操作,将它从分阶段状态转移到活动/保留状态(这可能是一个PUT资源的属性,或对资源的合适POST。
每个客户都应该能够以某种方式获得他们在阶段状态下的资源列表(可能与其他资源混合),并且应该能够删除他们创建的资源(如果他们仍然只是暂存)。您还可以定期删除已处于非活动状态一段时间的暂存资源。
您不需要向任何其他客户端显示一个客户端的暂存资源;只有在确认步骤之后,它们才需要全球存在。
另一种方法是让客户发出标识符。这对于像文件存储这样的建模非常有用,因为文件名通常对用户代码很重要。在这种情况下,您可以使用PUT来创建资源,因为您可以完全无意义地完成所有这些操作。
这方面的缺点是客户端能够创建ID,因此您无法控制他们使用的ID。
这个问题还有另一种变化。让客户生成唯一ID表示我们要求客户为我们解决此问题。考虑一个我们拥有公开API并且有100个客户端与这些API集成的环境。实际上,我们无法控制客户端代码以及他实现唯一性的正确性。因此,如果请求是重复的,那么了解情报可能会更好。这里的一个简单方法是基于来自用户输入的属性计算和存储每个请求的校验和,定义一些时间阈值(x分钟)并将来自同一客户端的每个新请求与过去x分钟中接收的请求进行比较。如果校验和匹配,则可能是重复请求,并为客户端添加一些质询机制来解决此问题。如果客户端在x分钟内使用相同的参数发出两个不同的请求,那么即使它具有唯一的请求ID,也可能值得确保这是有意的。这种方法可能不适用于每个用例,但是,我认为这对于执行第二次调用的业务影响很大并且可能使客户付出代价的情况会很有用。考虑支付处理引擎的情况,其中中间层在重试失败的请求时结束,或者客户双击,导致客户层提交两个请求。
在解决方案2中,创建内存缓存黑名单的阈值仅在内存中创建,因此永远不会检查数据库是否有重复项。 “复制”的定义是“任何进入一段时间的请求”。我们还复制磁盘上的Memory Cache表,因此我们在启动服务器之前填写它。
在解决方案1中,永远不会有重复,因为我们总是在写入之前仅检查磁盘一次,如果它是重复的,则下一个往返将由内存缓存处理。这个解决方案对于Big Query更好,因为请求不是imdepotents,但它也不那么优化。