【问题标题】:Idempotence of HTTP PUT and DELETEHTTP PUT 和 DELETE 的幂等性
【发布时间】:2012-10-18 23:27:31
【问题描述】:

所以 HTTP 规范说 HTTP PUT 和 DELETE 应该是幂等的。这意味着,对具有相同正文的相同 URL 的多个 PUT 请求不应在服务器上产生额外的副作用。多个 HTTP DELETE 也是如此,如果将 2 个或多个 DELETE 请求发送到同一个 URL,则第二个(或第三个等)请求不应返回指示资源已被删除的错误。

但是,在处理完 DELETE 之后对 URI 的 PUT 请求会怎样呢?它应该返回 404 吗?

例如,考虑以下请求按此顺序执行:

  • POST /api/items - 创建 item 资源,返回 HTTP 201 和 URI /api/items/6
  • PUT /api/items/6 - 更新与item #6 关联的数据
  • PUT /api/items/6 - 只要请求正文与之前的 PUT 相同,就没有副作用
  • DELETE /api/items/6 - 删除 item #6 并返回 HTTP 202
  • DELETE /api/items/6 - 没有副作用,还返回 HTTP 202
  • GET /api/items/6 - 现在将返回 404
  • PUT /api/items/6 - 这里应该发生什么? 404? 409?还有什么?

那么,PUT 是否应该与 get 一致并返回 404,或者像 @CodeCaster 建议的那样,409 是否更合适?

【问题讨论】:

  • 您的问题具有误导性。只要资源确实不再存在,第二个 DELETE 返回什么实际上并不重要。
  • @JulianReschke,真的吗?那么第一个DELETE可以返回200、202还是204,第二个delete可以返回404呢?那么这篇文章的底部是否具有误导性? asp.net/web-api/overview/creating-web-apis/… "因此,如果产品已被删除,该方法不应返回错误代码。"
  • 是的,那篇文章具有误导性。就幂等性而言,这真的无关紧要。
  • 从第二个 DELETE 返回 404 对我来说更有意义。
  • 您的问题应明确说明在给定 URI 上禁止 PUT 的愿望是您的特定限制。 RFC2616 中没有任何内容不允许这样做。这是您的应用程序协议中的设计选择。

标签: api http rest asp.net-web-api


【解决方案1】:

RFC 2616,第 9.6 节,PUT:

POST 和 PUT 请求的根本区别在于 体现在Request-URI的不同含义上。 URI 中的 POST 请求标识将处理封闭的资源 实体。该资源可能是一个数据接受过程,一个通往 一些其他协议,或接受注释的单独实体。 相反,PUT 请求中的 URI 标识包含的实体 通过请求 -- 用户代理知道 URI 的意图和 服务器不得尝试将请求应用于其他资源。

还有:

如果无法使用 Request-URI 创建或修改资源,则应给出反映问题性质的适当错误响应。

所以定义'适当'就是看400系列,说明有客户端错误。首先,我将消除不相关的:

  • 400 Bad Request:由于格式错误,服务器无法理解请求 语法。
  • 401 Unauthorized:请求需要用户身份验证。
  • 402 需要付款:此代码保留供将来使用。
  • 406 Not Acceptable:请求标识的资源 [...] 不可接受 根据请求中发送的接受标头。
  • 需要 407 代理身份验证:此代码 [...] 表示 客户端必须首先通过代理对自己进行身份验证。
  • 408 Request Timeout:在服务器准备等待的时间内,客户端没有产生请求。
  • 411 Length Required:服务器拒绝接受没有定义内容的请求- 长度。

那么,我们可以使用哪些?

403 Forbidden

服务器理解请求,但拒绝执行。 授权将无济于事,并且不应重复请求。

这个描述实际上非常合适,尽管它通常用于与权限相关的上下文中(例如:你可能不会......)。

404 Not Found

服务器没有找到任何匹配 Request-URI 的东西。不 指示该状况是暂时的还是 永恒的。如果服务器应该使用 410 (Gone) 状态码 通过一些内部可配置的机制知道,旧的 资源永久不可用且没有转发地址。 当服务器不希望这样做时,通常使用此状态码 准确说明请求被拒绝的原因,或者当没有其他请求时 响应是适用的。

这个也是,尤其是最后一行。

405 Method Not Allowed

Request-Line 中指定的方法不允许用于 由 Request-URI 标识的资源。响应必须包含一个 允许包含请求的有效方法列表的标头 资源。

没有我们可以响应的有效方法,因为我们现在不希望在这个资源上执行任何方法,所以我们不能返回 405。

409 Conflict

在响应 PUT 请求时最有可能发生冲突。为了 例如,如果正在使用版本控制并且实体被 PUT 包括对资源的更改,这些更改与由 较早的(第三方)请求,服务器可能会使用 409 响应 表示它无法完成请求。在这种情况下, 响应实体可能包含差异列表 以响应定义的格式在两个版本之间 内容类型。

但这假设在 URI 上已经有一个资源(怎么可能有冲突?)。

410 Gone

请求的资源在服务器上不再可用,并且没有 转发地址是已知的。这种情况预计 被认为是永久性的。具有链接编辑能力的客户应该 用户批准后删除对 Request-URI 的引用。如果 服务器不知道或无法确定是否 条件是永久的,状态码 404(未找到)应该是 改为使用。

这个也有道理。


我已经编辑了几次这篇文章,当它声称“使用 410 或 404”时它被接受了,但现在我认为 403 也可能适用,因为 RFC 没有说明 403 必须是权限-related(但它似乎是由流行的 Web 服务器以这种方式实现的)。我想我已经消除了所有其他 400 码,但请随时发表评论(在您投反对票之前)。

【讨论】:

  • 感谢您提供这个非常有参考价值的答案。不和谐的部分与删除项目后 GET 请求的行为方式有关。 DELETE /api/itesm/6 之后,GET /api/items/6 将返回 404。我不想让用户 PUT /api/items/6 除非资源已经存在(要创建,他们必须 POST 到 / api/项目)。所以404似乎比409更合适。我会更新我的问题。
  • @danludwig 我编辑了我的帖子并了解到我应该更加注意阅读 RFC,因为我的帖子有一半是无关紧要的。如果您的项目上没有 Deleted 位,我会说使用 410,或者实际上是 404。
  • 没有删除位,但我们确实记录了审计跟踪。我想避免为了确定某个项目以前是否存在而进行调查。朱利安的评论呢?从第一个 DELETE 返回 202,从第二个 DELETE 返回 404 是否可以?也许我正在阅读一篇错误的文章......
  • @danludwig idempotence 是关于服务器状态的。文章有误。第二个 DELETE 必须返回 410 或 404,因为资源不再存在。另见:stackoverflow.com/questions/4088350/…
  • 所以:"如果资源不能通过 Request-URI 创建或修改,则应给出反映问题性质的适当错误响应" 是绝对相关的:该 URI 处的资源无法更新,因为在该 URI 处 没有找到资源,所以 410(如果曾经有资源并且服务器是知道并愿意发布)或 404。
【解决方案2】:

您的问题有一个未说明的假设前提,即 PUT 必须存在资源才能成功。这不是一个有效的假设。

规范(RFC2616)的相关部分说:

用户代理知道 URI 的意图,服务器不得尝试将请求应用于其他资源。

规范并没有说,“引用的 URI 上的对象必须已经存在才能使对该 URI 的 PUT 成功。”


简单的例子是通过 REST 实现的网络商店。 GET 返回给定路径处对象的表示,而DELETE 删除给定路径处的项目。这些很容易。但是 POST 和 PUT 并不难理解。 POST 可以做任何事情,但 POST 的一种用法是在客户端指定的容器中创建一个对象,并让服务器返回该容器中新创建的对象的 URI。 PUT 更受限制;它为服务器提供给定 URI 处对象的表示。该对象可能已经存在,也可能不存在。 PUT 不是 REPLACE 的同义词。

在我看来,409 或 410 对于 PUT 是错误的,除非容器本身 - 您要放入的东西不存在。

因此:

POST /container
   ==> returns 200 with `Location:/container/resource-12345`

PUT /container/resource-98928
   ==> returns 201 CREATED or 200 OK

PUT /this-container-does-not-exist/resource-22828282
   --> returns 400

当然,您是否希望您的服务器允许这些 PUT 语义由您决定。但是规范中没有任何内容说您不能允许客户端提供他正在放置的资源的 URI。

【讨论】:

  • 查看我的更新答案。返回 400 绝对不是要走的路,因为这是请求中格式错误的语法。您不会为 400 的选择辩护,也不会说 为什么 409 或 410 是错误的,您只是说它们是错误的。此外,您谈到允许 409 或 410 响应的容器不存在,我认为您的意思是像 OP 的 /api/items/ 路径作为容器。为什么 PUTting 到 /api/tiems/6(不存在的容器)会返回 409 或 410?
  • 没错,我将/api/items称为容器。我不清楚为什么 PUT/api/items/6 会返回 409 或 410;我不是提倡这种协议的人。我建议了 201、200 或 400。你指出我没有解释为什么我认为 409 和 410 是错误的,但你从未要求我解释。是这是你的某种被动攻击性问题?
  • 请阅读我的回答,了解为什么 PUT 可能会导致 404、409 或 410,并随时评论我的推理。现在你只是冒犯,不回答我的问题,可能是因为你不同意我的观点。我想让你解释一下为什么我认为的状态码是无效的,因为我已经尽力解释了为什么,而你只是回答“他们错了,400 是对的”。
  • 409 不合适,因为没有冲突。 410 是不合适的,因为事实上,当它不存在的时候,PUT 是可以的。您将 PUT 解释为“替换”,这在一般情况下是不正确的。 HTTP 和 REST 允许将文档 PUT 到服务器上的特定位置,而不管给定位置是否已经存在文档。关于“不回答您的问题”:在此评论之前,您没有提出任何问题。相反,你指出了我没有提供的东西。这不是问题,而是观察。
  • 我说 a PUT,在 OP 的情况下 409 是不合适的,我在回答中对此进行了解释。我在对您的回答的第一条评论中问了一个问题。 RFC 说:“如果 Request-URI 不指向现有资源,并且该 URI 能够被请求用户代理定义为新资源,则源服务器可以使用该 URI 创建资源。[ ...] 如果无法使用 Request-URI 创建或修改资源,则应给出反映问题性质的适当错误响应".
猜你喜欢
  • 2016-01-08
  • 1970-01-01
  • 1970-01-01
  • 2011-10-24
  • 2014-04-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-10
  • 1970-01-01
相关资源
最近更新 更多