6

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

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

客户端/服务器交互就像条件 PUT 一样:

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

  2. 客户端对父资源执行条件 POST(在 If-Match 标头中包含父资源的 ETag 值)以创建新资源,

  3. 客户端在得到服务器响应之前就断开了连接,所以不知道它是否成功,

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

  5. 较早的请求未到达服务器,因此服务器创建资源并回复 201,或者较早的请求确实到达了服务器,因此服务器回复 412 并且未创建重复的资源。

4

3 回答 3

2

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

REST 爱好者通常建议您使用空 POST 创建资源,然后,一旦客户端获得新创建资源的 id,他就可以进行“幂等”更新来填充它。这很好,但是您可能需要使 DB 列可以为空,否则您的更新只有在没有其他人试图同时更新时才具有幂等性。

根据我的说法,HTTP 是不稳定的。请求超时,浏览器窗口关闭,连接重置,火车进入隧道,移动用户乘坐。有一个简单、健壮的模式来处理这个问题。不安全的操作应该始终被唯一标识,并且服务器应该存储并且能够在必要时重复对任何不安全请求的响应。这不是 HTTP 缓存,其中可以从缓存中提供请求,但可能出于任何原因刷新缓存。这是服务器应用程序的保证,即如果第二次看到“动作”请求,将重复存储的响应,而不会发生任何其他事情. 如果动作标识由服务器生成,那么请求-响应应该专门用于发送 id。如果你为一个不安全的请求实现这一点,你不妨为所有的请求都这样做,这样做你会避免许多棘手的问题:连续的更新请求清除其他用户的更改,或者遇到不兼容的状态(“订单已经提交"),连续删除请求会产生 404 错误。

如果您有兴趣,我有一个小谷歌文档更全面地探索该模式。

于 2016-02-16T09:53:43.347 回答
0

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

只是这样更安全。

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

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

这基本上是一个乐观的锁定场景,它归结为另一个问题。如果资源已经创建,那又如何呢?这是一个错误吗?还是一个特点?你关心?当资源已经存在时,发送一个被服务器静默忽略的创建请求是不是很糟糕?

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

另一种解决方案是进行两次旅行。一个负责提交请求,另一个负责提交。如果请求中断,您可以在返回时查询请求的状态。如果提交没有通过,您可以再次提交。如果是这样,你会很高兴并且可以继续前进。

仅取决于您的通讯有多不稳定,以及此特定操作对于您是否要跳过箍以安全地进行操作有多重要。

于 2015-03-03T01:17:18.443 回答
0

我认为这个方案会奏效。如果要确保 POST 不会导致重复,则需要客户端在 POST 中发送唯一的内容。然后服务器可以验证唯一性。

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

然后你的步骤变成: -

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

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

于 2015-03-03T00:33:43.000 回答