HTTP PUT和DELETE的幂等性

8
因此,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 返回什么并不重要。 - Julian Reschke
@JulianReschke,真的吗?第一个DELETE可以返回200、202或204,而第二个DELETE可以返回404?那么这篇文章的底部部分是不是有误导性的呢?http://www.asp.net/web-api/overview/creating-web-apis/creating-a-web-api-that-supports-crud-operations“因此,如果产品已经被删除,该方法不应返回错误代码。” - danludwig
是的,那篇文章是误导性的。它与幂等性无关。 - Julian Reschke
1
对我来说,从第二个DELETE返回404更有意义。 - Cheeso
2
你的问题应明确说明禁止在给定URI处使用PUT是你特定的限制。RFC2616中没有任何禁止这样做的规定。这是你应用程序协议中的设计选择。 - Cheeso
@Cheeso,你的评论和回答都很恰当。我应该说明一下,我的服务器不允许客户端通过PUT方法创建资源URI。 - danludwig
2个回答

10

RFC 2616, 第9.6节, PUT:

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

并且:

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

因此,定义“适当”的方法是查看400系列,表示存在客户端错误。首先,我将排除不相关的内容:

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

那么,我们可以使用哪些状态码呢?

403 Forbidden

服务器理解了请求,但拒绝满足它。授权无法解决问题,不应重复请求。

这个描述非常贴切,尽管通常用于权限相关的上下文中(例如:“您可能不允许...”)。

404未找到

服务器没有找到与请求URI匹配的任何内容。没有给出条件是临时还是永久的指示。如果服务器通过一些内部可配置机制知道旧资源永久不可用并且没有转发地址,则应使用410(已删除)状态代码。当服务器不希望透露拒绝请求的确切原因或者没有其他响应适用时,通常使用此状态代码。

这个也是,特别是最后一行。

405方法不允许

请求行中指定的方法不允许由请求URI标识的资源执行。响应必须包含一个Allow头,其中包含所请求资源的有效方法列表。

目前我们不希望在此资源上执行任何方法,因此我们无法返回405。我们没有有效的方法可以做出响应。

409 冲突

冲突最有可能是对PUT请求的响应。例如,如果正在使用版本控制,并且被放置的实体包含与早期(第三方)请求所做的更改发生冲突的资源更改,则服务器可能使用409响应来指示无法完成请求。在这种情况下,响应实体可能以由响应Content-Type定义的格式列出两个版本之间的差异列表。

但这假设URI上已经存在一个资源(什么都没有会怎样冲突呢?)。

410 已删除

所请求的资源在服务器上不再可用,也没有任何转发地址。这种情况被认为是永久性的。具有链接编辑功能的客户端应在用户批准后删除对请求URI的引用。如果服务器不知道或无法确定条件是否永久性,则应使用状态码404(未找到)代替。

我已经编辑了几次这篇文章,最初被接受时它声称“使用410或404”,但现在我认为403也可能适用,因为RFC没有规定403必须与权限相关(但是流行的Web服务器似乎是这样实现的)。我认为我已经排除了所有其他400代码,但欢迎评论(在您投票之前)。


@danludwig 幂等性关乎服务器状态。这篇文章是错误的。第二个DELETE必须返回410或404,因为资源不再存在。另请参阅:https://dev59.com/-m855IYBdhLWcg3w5IrZ - CodeCaster
@Cheeso,它说应该这样做:“如果无法使用请求URI创建或修改资源,则应给出适当的错误响应,反映问题的性质”。 - CodeCaster
@codeCaster - 感谢您引用规范的那一部分,但它并不相关。我并没有建议返回不反映问题性质的状态码。您和我显然对RFC2616的解释不同。我想说的是,在规范中没有说明禁止将PUT应用于指定的URL的原因。我会重复一遍:如果给定路径上不存在对象,则规范确实要求PUT失败。 - Cheeso
我并不是说每个URI都应该允许PUT。RFC规定服务器可能允许通过PUT方法在“新的”URI中创建资源,但同时也规定服务器可以拒绝此类请求。然而我并不知道这与本文主题有何关系:客户端执行PUT方法到它认为某个资源所在的URI上,以便更新该资源。在这个服务器上不能使用PUT来创建资源,OP说,必须使用POST来创建资源,但PUT可用于更新资源。 - CodeCaster
1
因此,“如果无法使用请求URI创建或修改资源,则应给出适当的错误响应,反映问题的性质”是绝对相关的:该URI上的资源无法更新,因为在该URI上找不到资源,因此可以使用410(如果曾经有资源并且服务器知道并愿意发布)或404。 - CodeCaster
显示剩余7条评论

2
您的问题有一个未明确说明的假设前提,即“必须存在资源才能成功执行PUT请求。”这是一个无效的假设。
规范(RFC2616)的相关部分如下所述:
引用:
用户代理知道要使用哪个URI,并且服务器不得尝试将请求应用于其他资源。
规范并没有说:“在引用的URI上必须已经存在对象才能成功执行PUT请求。”
一个易懂的例子是通过 REST 实现的 Web 商店。 GET 返回给定路径上对象的表示形式,而 DELETE 删除给定路径上的项目。这很容易理解。但是,POSTPUT 也不难理解。 POST 可以做任何事情,但是其中一种用途是在客户端指定的容器中创建对象,并让服务器返回该容器内新创建的对象的 URI。 PUT 的功能更为有限;它提供了在给定 URI 上表示对象的服务器。该对象可能已经存在,也可能不存在。 PUT 不是 REPLACE 的同义词。

就我个人而言,对于 PUT 请求来说,使用 409 或 410 是错误的,除非容器本身不存在。

因此:
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语义取决于您。但是规范中没有任何内容表明您不得允许客户端提供他正在PUT的资源的URI。


请查看我的更新答案。返回400绝对不是正确的方法,因为这是用于请求中语法错误的情况。您没有为400做出选择进行辩护,也没有说明为什么409或410是错误的,您只是说它们是错误的。此外,您谈论容器不存在,这将允许409或410响应,我认为您的意思是像OP的/api/items/路径作为容器。为什么将PUT/api/tiems/6(不存在的容器)会返回409或410? - CodeCaster
这是正确的,我将/api/items称为容器。为什么PUT/api/items/6会返回409或410对我来说不清楚;我不是提倡这样的协议的人。我建议使用201、200或400。你指出我没有解释为什么我认为409和410是错误的,但你从未要求我解释。这是你的一种间接攻击吗? - Cheeso
请阅读我的回答,了解为什么PUT可能会导致404、409或410,并随时评论我的推理。现在你只是在冒犯我,而不是回答我的问题,可能是因为你不同意我。我希望你能解释一下我所提出的状态码为什么无效,因为我已经尽力解释它们的有效性,而你只是回答“它们是错误的,400是正确的”。 - CodeCaster
我说过一个PUT,在OP的情况下,409是不合适的,这一点我在我的答案中已经解释了。我在你的答案中的第一个评论中提出了一个问题。RFC说:“如果请求URI没有指向现有资源,并且该URI能够被请求用户代理定义为新资源,则源服务器可以使用该URI创建资源。[...]如果无法使用请求URI创建或修改资源,则应给出反映问题性质的适当错误响应”。 - CodeCaster
1
好的,CodeCaster,假设410表示“所请求的资源不再可用”,如果客户端尝试将PUT请求发送到从未存在过的URI,服务器应该返回什么?是返回410 Gone吗?你是否建议如果URI曾经存在过,则服务器应该返回410,如果URI以前从未使用过,则返回400(或其他不是410的代码)?对我来说,这在协议上没有意义。它会给服务器和客户端带来不必要的负担。400更简单,并且适用于所有情况。 - Cheeso
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接