在HTTP DELETE方法中,如何正确理解幂等性的概念?

15

最近我花了很多时间阅读HTTP 1.1规范并将其与REST相关联。关于HTTP DELETE方法的“幂等性”和安全性,我发现有两种解释。这里是两个不同的观点:

  1. 如果你使用HTTP DELETE删除一个资源,并且它成功(200 OK),然后你尝试N次删除该资源,每一次删除调用都应该返回成功消息(200 OK)。这就是它的“幂等性”。

  2. 如果你使用HTTP DELETE删除一个资源,并且它成功(200 OK),然后你再次尝试删除该资源,你应该得到错误消息(410 Gone),因为资源已被删除。

规范说DELETE是幂等的,当然,但它也说幂等事件序列仍然可以产生副作用。我真的觉得第二个观点是正确的,第一个观点是误导人的。让客户端认为他们是先前删除资源的原因,我们引入了什么“安全性”呢?

有很多人属于第一个阵营,包括几位作者在内,所以我想确认除了情感之外是否还有其他令人信服的理由让人们倾向于第一个阵营。


我认为你应该去掉“recently”,删除的时间与此响应的策略无关。 - Jeff Martin
@JeffMartin 你是对的,那段话更像是闲聊而不是事实。已删除。 - Daniel Crenna
3个回答

22

幂等并不意味着请求不能有副作用(这是“safe”的属性描述的),它只是意味着多次发出相同的请求不会产生不同或附加的副作用。

在我看来,随后的DELETE请求应该返回错误-它仍然是幂等的,因为服务器的状态与仅进行一次DELETE请求时相同。但是,返回200 OK状态也应该没有问题-我认为幂等不要求对随后的DELETE请求返回错误代码,只是返回错误状态似乎更有意义。


我有同样的看法,很高兴看到有人分享它。我想我只是需要听到这个。谢谢。我有很多关于这个主题的书,令人惊讶的是其中相当多的书把幂等性视为安全性,但实际上并不是这样。 - Daniel Crenna
作为对立的观点,我认为DELETE请求更接近于“确保删除/移除此资源”。该资源在请求时是否存在并不重要,也不影响响应的成功状态。如果两个不同的客户端“同时”发送DELETE请求,为什么一个会收到错误响应,另一个却成功,事实上它们都成功地完成了目标。 - Jeff Martin
@JeffMartin 你可以这样做,但我认为第二个请求收到“错误”的原因是因为第一个请求先到达了。这更准确。在某些系统中,删除的先后顺序很重要,以及由于缺少资源而导致无操作的结果。如果您的系统只是确保不存在,则您的概念可行。非200只是非200,它不是错误,而是结果。一些框架将其视为异常情况,这可能会导致认为非200是“不好的”。 - Daniel Crenna
2
我更赞同@JeffMartin的观点,我不明白客户端拿到一个已经删除的信息后该怎么做。这可能意味着它发送了多个请求或其他客户端先删除了它。但如果客户端确实有所不同,那么就意味着状态从服务器的第一次删除转变为第二次删除,因为显然在第一次请求之后,服务器的状态发生了变化,而响应(函数返回)才是最重要的,因为状态在第一次请求之后显然发生了变化。 - Dandre Allison
这太疯狂了。如果您的意图是通过标识符删除现有对象,那么如果该资源不存在或已被删除,它应该是一个明显的失败。绝不能成功调用delete {bogus_id}。换句话说,如果无法知道{bogus_id}是否曾经有效,它应该失败。 - Triynko
显示剩余2条评论

2

@MichaelBurr关于幂等性和副作用的观点是正确的。

我的看法是,在给定的REST请求中涉及到两个状态,客户端的状态和服务器的状态。REST就是在服务器和客户端之间传输这些状态,使得客户端的状态映射到服务器状态的子集,换句话说,子集与服务器保持一致。因此,幂等性应该意味着后续的幂等请求不会导致任何一个状态与仅发出一次请求时不同。在第一次DELETE操作中,你可以想象服务器删除了资源并让客户端知道它也可以删除资源(因为资源“不再存在”)。现在,两个状态应该与删除前完全相同,只不过少了被删除的项。如果客户端在已经删除该项后尝试删除该项时做出任何不同的操作,则从服务器传输到客户端的状态必须包含不同的信息。服务器可以稍微以不同的方式处理资源已被删除的信息,但一旦它以不同的方式响应,方法的幂等性基本上就被破坏了。

对于幂等函数:

delete(client_state) -> client_state - {item}
delete(delete(client_state)) -> client_state - {item}
delete(client_state) = delete(delete(client_state))

最好保证幂等性的方法是确保服务器的响应是相同的,这意味着客户端状态破坏幂等性的唯一方式是客户端处理响应时存在不确定性或副作用(这可能指向处理响应的实现不正确)。
如果客户端和服务器之间有一个协议,即状态码存在于正在传输的状态表示之外(REST),那么就可以通知客户端该项“不再存在”(就像在第一个请求中一样),并额外注明它以前已被删除。客户端对此信息的处理方式不清楚,但它不应影响最终的客户端状态。但是,状态码不能用于通信状态,或者如果它在其他情况下也用于通信状态(例如“您没有删除此项的权限”或“项目未被删除”),则会引入一些模棱两可或混淆。因此,如果您想说DELETE是幂等的并且服务器的响应仍然取决于先前的相同DELETE请求,则至少需要一个非常好的理由来引入更多的通信混淆。
HTTP请求涉及删除方法,因此函数可能类似于:
delete(client_state) = send_delete(client_state) -> receive_delete(client_state) 
                                                 -> respond_to_delete(informative_state) 
                                                 -> handle_response(informative_state) 
                                                 -> client_state - {item} 

这是一个有趣的论点。我仍然不清楚“OK”如何解决这种不一致性或强制模糊性。我同意如果你提供相同的响应,那会更好,但是如果我们选择澄清“这个资源根本不存在,所以你不能删除它”(404)和“这个资源以前在这里,但现在不再存在,所以你可能需要清空缓存”(406),我并不认为我们会违反任何状态转换... - Daniel Crenna
1
在特定资源的生命周期内,如果这是真实的情况,您将从服务器获得相同的回复。通过OK要求相同性是要求服务器隐藏客户端响应之前/之后发生的细节,这可能不是良好的设计,具体取决于API。我会再考虑一下,但我仍然喜欢DELETE 1 -> OK,DELETE 2..n - GONE用于曾经创建过的资源,DELETE 1..n 404 NOT FOUND用于其他所有内容。 - Daniel Crenna
@DanielCrenna 但是尝试删除某些东西意味着您认为它存在(即客户端假定它必须在服务器上)。对于从未创建的资源,404 对于 GET 操作更加适当。 - tenkod

0

维基百科定义幂等性为:

一个操作可以被多次应用,而不会改变除了初始应用之外的结果。

请注意,他们谈到操作的 结果。对我来说,这包括服务器状态和响应代码 两者

HTTP规范在这个问题上有些含糊不清。它定义HTTP方法是幂等的:

如果多个相同请求的预期效果与单个请求的预期效果相同,则该请求是幂等的。

如果您将维基百科定义中的 效果 解释为 结果,则它们的意思相同。无论如何,告诉客户端资源已经被删除并没有实际好处。

最后一点:幂等性是以单个客户端为基础定义的。一旦您开始引入其他客户端的并发请求,所有的赌注都会失效。您应该使用条件更新标头(例如If-Match-ETag)来处理这种情况。
重申一下:无论资源刚刚被删除、被先前的请求删除还是根本不存在,您都应该返回相同的返回代码。

不,"effect"并不打算覆盖响应。你提出的是DELETE要么总是成功("200"),要么失败("404"或"410"),无论服务器的状态如何。这毫无意义,而且对客户端没有任何帮助。 - Julian Reschke
@JulianReschke,如果您阅读http://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-24#section-4.3.5,规范列出了所有合法的`DELETE`响应。 404410不在其中。因此,请取消您的负评。 - Gili
不,规范并没有列出“所有”DELETE的合法响应。它只是给出了一些例子。 - Julian Reschke

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