34

我想知道我目前的方法是否有意义,或者是否有更好的方法来做到这一点。

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

结果,我决定将整个过程分为两步:

  1. 首先发送一个 POST 请求以创建一个新对象,该对象在 Location 标头中返回新对象的 URI。

  2. 其次,对提供的位置执行幂等 PUT 请求,以用数据填充新对象。如果新对象在 24 小时内未填充,服务器可能会通过某种批处理作业将其删除。

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

4

6 回答 6

39

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

相反,我会在 URL 中使用带有UUID的 PUT。由于 UUID 生成器,您几乎可以确定您在客户端生成的 ID 将是唯一的服务器端。

于 2012-12-28T13:06:52.283 回答
17

这一切都取决于,首先你应该更多地谈论URI、资源和表示,而不是关心对象。

POST 方法是为非幂等请求或具有副作用的请求而设计的,但它可以用于幂等请求

将表单数据 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

我假设你:

  • 需要与哈希值不同的标识值
  • 无法更改用于身份的数据字段
于 2013-01-02T12:43:56.890 回答
5

您在服务器、应用程序、专用请求响应中生成 id 的方法非常好!唯一性非常重要,但是客户,就像追求者一样,会不断重复请求,直到他们成功,或者直到他们愿意接受(不太可能)失败。所以你需要从某个地方获得独特性,你只有两个选择。客户端,如 Aurélien 所建议的 GUID,或服务器,如您所建议的。我碰巧喜欢服务器选项。关系数据库中的种子列是一种现成的唯一性来源,冲突风险为零。在 2000 年左右,我读到了一篇提倡这种解决方案的文章,名为“使用 HTTP 进行简单可靠的消息传递”,因此这是解决实际问题的既定方法。

阅读 REST 的东西,你可能会认为一群青少年刚刚继承了 Elvis 的豪宅。他们正在兴奋地讨论如何重新布置家具,他们对可能需要从家里带一些东西的想法感到歇斯底里。建议使用 POST ,因为它在那里,而没有提出非幂等请求的问题。

在实践中,您可能希望确保对您的 api 的所有不安全请求都是幂等的,但身份生成请求除外,正如您所指出的那样,这并不重要。生成身份很便宜,未使用的身份很容易被丢弃。作为对 REST 的认可,请记住使用 POST 获取您的新身份,这样它就不会被缓存并在所有地方重复。

关于幂等意味着什么的无意义的辩论,我说它需要是一切。连续的请求应该不会产生额外的影响,并且应该收到与第一个处理的请求相同的响应。为了实现这一点,您需要存储所有服务器响应,以便可以重放它们,并且您的 id 将识别操作,而不仅仅是资源。你会被赶出猫王的豪宅,但你会拥有一个防弹 api。

于 2016-02-10T16:23:03.750 回答
3

但是现在您有两个请求可能会丢失?POST 仍然可以重复,创建另一个资源实例。不要想太多东西。只需让批处理过程寻找受骗者。可能对您的资源有一些“访问”计数统计信息,以查看哪些被欺骗的候选人是被放弃的职位的结果。

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

于 2012-12-21T14:52:06.813 回答
3

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

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

于 2012-12-31T19:26:08.443 回答
2

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

于 2012-12-21T14:37:07.993 回答