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