2013-04-09 101 views
4

我一直在寻找最佳实践,以防止在使用POST创建新资源时意外创建重复资源,这是因为资源将由服务器命名,因此PUT无法使用。我正在构建的API将被移动客户端使用,我关心的情况是客户端在提交POST请求后但在获取响应之前断开连接。我发现this question,但没有提到使用条件POST,因此我的问题。REST:防止通过条件POST创建重复资源?

正在对父资源执行条件POST,类似于使用条件PUT修改资源,这个问题的合理解决方案?如果不是,为什么不呢?

客户机/服务器交互将是一样与条件PUT:

  1. 客户端获取父资源,包括ETag的反映其当前状态(其中包括它的从属资源),

  2. 客户端执行有条件POST父资源(包括在If-Match头父母的ETag值)来创建一个新的资源,

  3. 客户端获取得到之前断开服务器响应,所以不知道它是否成功,

  4. 之后,重新连接时,客户重新提交相同的条件POST请求,

  5. 无论是早先的要求,没有达到服务器,因此服务器创建资源并用201回复,或者更早的请求确实到达服务器,所以服务器回复412并且不创建重复资源。

+0

如果另一个客户端在两者之间执行GET/POST对,这是否意味着GET/POST对将会失败?这可能是高流量情况下的一个问题。 – geon 2013-05-02 15:49:48

+1

@geon您说得对,但所讨论的集合资源是用户特定的,而不是全局的,因此这种冲突的可能性很小。 – 2015-03-03 07:55:44

回答

0

我觉得这个方案可行。如果你想确保POST不会导致重复,你需要客户端发送POST中唯一的东西。然后服务器可以验证唯一性。

您不妨让客户端为每个请求生成一个GUID,而不是通过GET从服务器获取该GUID。

你的步骤则变为: -

  1. 客户端生成一个GUID
  2. 客户端执行POST的资源,其中包括GUID
  3. 客户端断开连接,不知道它是否成功
  4. 客户端再次连接并执行具有相同GUID的另一个POST
  5. 服务器检查GUID并创建资源(如果它从未收到第一个POST)或指示这是一个dupl icate

使用PUT可能更安宁,并让客户端决定资源名称。如果您不喜欢所选的名称,则可能表明您已创建了该资源,但它的典型位置是服务器选择的某个位置。

+0

缺点w /添加一个GUID字段是,你最终不得不围绕一个额外的字段只是为了重复检测的目的,这是不可取的。最后我们确实使用了客户端生成的GUID,所以我们可以使用PUT。 – 2015-03-03 08:00:41

+0

您是不是指客户端生成的URI? – 2015-03-03 08:01:50

+0

是的,带有GUID的URI。 – 2015-03-03 18:56:02

0

为什么不简单地使用服务器选择使用的任何内部机制在服务器上根据实际资源重复检测。

这只是更安全的方式。

然后您将URL返回到适当的资源(无论是否新创建)。

如果父母ETag是基于子资源的状态,那么它不是检查“重复资源”的可靠机制。你所知道的是,自上次以来,父母已经“改变”了。你甚至知道这是因为你的旧POST在断开连接后被处理了? ETag可能会改变任何东西。

这基本上是一个乐观锁定情景正在播放,并且归结为另一个问题。如果资源已经创建,那么是什么?这是一个错误?还是一个功能?你关心?当资源已经存在时,发送服务器默认忽略的创建请求会不好吗?

如果它已经存在,但是“不同”足够(即说名称匹配但地址不同),是否是重复的?是更新吗?这是尝试更改现有资源的错误吗?

另一个解决方案是做两次旅行。一个来提出请求,另一个提交请求。如果请求中断,则可以查询请求的状态。如果提交没有完成,您可以再次提交。如果确实如此,你很高兴,可以继续前进。

只是取决于你的通信有多不稳定,以及这个特定的操作是多么重要,你是否想要跳过安全环来做这件事。

+0

在我们的案例中,资源集合是一个不是集合的列表,因此合法地可以有相同资源的多个副本(尽管具有不同的ID)。我们想要防止的问题是客户端代表请求单个副本的用户意外创建多个副本。一般来说,父母的ETag是不可靠的,但是对于特定于用户的父母资源来说,它是更可行的。最后,我们使用客户端生成的GUID和PUT的简单而强大的解决方案。 – 2015-03-03 08:08:04

+0

一个人们试图维持REST纯度的标准混乱的可爱例子。 – bbsimonbb 2016-02-16 10:32:24

1

您的解决方案很聪明,但并不理想。您的客户可能永远无法获得他的201确认,并且必须将412错误解释为成功。

REST afficianados通常建议您使用空POST创建资源,然后,一旦客户端拥有新创建的资源的ID,他就可以执行“幂等”更新来填充它。这很好,但是你可能需要使数据库列可以为空,否则就不会这样,而且如果没有其他人试图同时更新,那么你的更新只会是幂等的。

据我说,HTTP是片状的。请求超时,浏览器窗口关闭,连接重新设置,列车与移动用户一起进入隧道。有一个简单,强大的模式来处理这个问题。不安全的操作应始终唯一标识,服务器应存储并能够在必要时重复对任何不安全请求的响应。这不是HTTP缓存,可以从缓存提供请求,但缓存可能因任何原因被刷新。这是服务器应用程序的保证,如果第二次看到“动作”请求,则存储的响应将被重复而没有其他任何事情发生。如果动作身份由服务器生成,那么请求 - 响应应该专用于发送该ID。如果你为一个不安全的请求实现这一点,那么你可以为所有人做这样的事情,这样做可以避免许多棘手的问题:连续的更新请求消除了其他用户的更改或者达到了不兼容的状态(“已经提交的订单“),连续删除请求产生404错误。

我有一个小小的谷歌文档探索更充分的模式if you're interested

+0

有趣的建议,谢谢,但它会非常沉重的服务器实施,因为他们必须保持所有行动和他们产生的反应历史。此外,您提倡PUT对动作URI的不同语义(替代目标资源IFF在通过此动作URI之前未被替换),而不是“普通”PUT(总是替换目标资源),这是一个相当戏剧性的离开来自标准。正如你所说,它确实有一些好处,但我仍然赞成已经提到的其他更简单的解决方案。 – 2016-02-17 06:30:10

+0

“非常”有点强!响应很小,只有几千字节。如果你有很多卷,你可以使用一个ACID键值存储(couchDB?)来存储响应。我第一次使用这种模式的支付Web服务已经在SQL Server数据库上快乐地离开了15年。开发,集成和支持非常简单,我发现自己对这个问题的其他答案非常反感。你不会注意到:在所有关于你应该如何处理这个问题的RESTful讨论中,没有人会谈论他们的经验,他们的问题,他们的数量。 – bbsimonbb 2016-02-17 09:05:01

+0

也许,但是存储资源所有修改的历史可能需要比资源本身更多的存储空间。从概念上讲,存储修改历史是一个重大变化。为了防止意外重复的资源创建,使用客户端生成URI的PUT最简单最简单,然后如您所述,POST之后的空POST只需要额外存储创建的ID直到它们被填充。 – 2016-02-17 20:10:54