POST不是幂等的后果(RESTful API)

时间:2012-12-21 14:26:16

标签: http rest post put idempotent

我想知道我目前的方法是否有意义,或者是否有更好的方法。

我有多种情况需要创建新对象并让服务器为这些对象分配ID。发送POST请求似乎是最合适的方法。 但是,由于POST不是幂等的,因此请求可能会丢失并再次发送它可能会创建第二个对象。此外,丢失请求可能非常常见,因为API通常通过移动网络访问。

结果我决定将整个过程分成两个步骤。 首先发送POST请求以创建新对象,该对象返回Location头中新对象的URI。其次,对提供的位置执行幂等PUT请求,以使用数据填充新对象。如果未在24小时内填充新对象,则服务器可能会通过某种批处理作业将其删除。

听起来合理还是有更好的方法?

由于

6 个答案:

答案 0 :(得分:33)

POST创建优于PUT创建的唯一优势是服务器生成ID。 我不认为缺乏幂等性(然后需要删除重复项或空对象)。

相反,我会在URL中使用带有UUID的PUT。由于UUID生成器,您nearly sure表示客户端生成的ID将是服务器端唯一的。

答案 1 :(得分:14)

一切都取决于,首先,您应该多谈谈 URI,资源和表示,而不要关注对象。

POST方法适用于非幂等请求或具有副作用的请求,但can be used for idempotent requests

将表单数据POST到/ some_collection /

normalize the natural key of your data (Eg. "lowercase" the Title field for a blog post)
calculate a suitable hash value (Eg. simplest case is your normalized field value)
lookup resource by hash value
if none then
    generate a server identity, create resource
        Respond =>  "201 Created", "Location": "/some_collection/<new_id>" 
if found but no updates should be carried out due to app logic
        Respond => 302 Found/Moved Temporarily or 303 See Other 
        (client will need to GET that resource which might include fields required for updates, like version_numbers)
if found but updates may occur
   Respond => 307 Moved Temporarily, Location: /some_collection/<id> 
   (like a 302, but the client should use original http method and might do automatically) 

合适的散列函数可能与某些连接字段一样简单,或者对于大字段或值,可以使用截断的md5函数。有关详细信息,请参阅[哈希函数] 2

我假设你:

  • 需要与哈希值不同的身份值
  • 使用的数据字段 身份无法改变

答案 2 :(得分:4)

您在服务器上,在应用程序中,在专用的请求 - 响应中生成ID的方法是非常好的方法!唯一性是非常重要的,但客户,如追求者,将继续重复请求,直到他们成功,或直到他们失败,他们愿意接受(不太可能)。所以你需要从某个地方获得独特性,而你只有两种选择。客户端,具有Aurélien建议的GUID,或者服务器,如您所建议的那样。我碰巧喜欢服务器选项。关系数据库中的种子列是一种易于获得的唯一性来源,没有碰撞风险。第二轮,我读了一篇提倡这个解决方案的文章,称之为“简单可靠的消息传递与HTTP”,所以这是一个确定的解决实际问题的方法。

阅读REST的内容,你可以原谅一群青少年刚刚继承了Elvis的豪宅。他们兴奋地讨论如何重新安排家具,他们对于他们可能需要从家里带些东西的想法感到歇斯底里。建议使用POST ,因为它存在,而不会解决非幂等请求的问题。

在实践中,您可能want to make sure all unsafe requests to your api are idempotent,除了身份生成请求之外,您指出无关紧要。生成身份很便宜,很容易丢弃未使用的身份。作为对REST的认可,请记住通过POST获取新身份,因此不会在整个地方缓存和重复。

关于the sterile debate about what idempotent means,我说它需要成为一切。连续请求不应产生额外的影响,并且应该收到与第一个处理的请求相同的响应。要实现这一点,您需要存储所有服务器响应,以便可以重放它们,并且您的ID将识别操作,而不仅仅是资源。你将被赶出Elvis的豪宅,但你会有一个防弹api。

答案 3 :(得分:3)

但是现在你有两个可以丢失的请求? POST仍然可以重复,创建另一个资源实例。不要过度思考。让批处理过程寻找欺骗。可能会对您的资源进行一些“访问”计数统计,以查看哪些候选人是废弃帖子的结果。

另一种方法:根据某些日志屏蔽传入的POST以查看它是否重复。应该很容易找到:如果请求的正文内容与x时间之前的请求相同,请将其视为重复。您可以检查额外的参数,如原始IP,相同的身份验证,......

答案 4 :(得分:3)

无论您使用何种HTTP方法,理论上都不可能在不生成唯一标识符客户端,临时(作为某些请求检查系统的一部分)或永久服务器ID的情况下发出幂等请求。丢失的HTTP请求不会产生重复,但担心请求可能成功到达服务器,但响应不会将其返回给客户端。

如果最终客户端可以轻松删除重复项并且它们不会导致固有数据冲突,那么开发临时复制防止系统可能不是一件足够大的事情。对请求使用POST,并在HTTP标头中将客户端返回201状态,并在响应正文中返回服务器生成的唯一ID。如果您有数据显示重复是经常发生或任何重复导致重大问题,我会使用PUT并创建唯一的ID客户端。使用客户端创建的id作为数据库ID - 在服务器上创建其他唯一ID没有任何好处。

答案 5 :(得分:2)

我认为您还可以将创建和更新请求折叠为仅一个请求(upsert)。为了创建新资源,客户端POST一个“工厂”资源,例如位于/ factory-url-name。然后服务器返回新资源的URI。