注意:当我第一次花时间阅读REST时,幂等性是一个难以理解的概念。在我最初的答案中,我仍然没有完全正确地理解它,正如进一步的评论(以及Jason Hoetger的回答)所显示的那样。有一段时间,我一直拒绝广泛更新这个答案,以避免有效地抄袭杰森,但我现在正在编辑它,因为,嗯,我被要求(在评论中)。
读完我的回答后,我建议你也读一下Jason Hoetger对这个问题的精彩回答,我将努力使我的答案更好,而不是简单地抄袭Jason。
为什么PUT是幂等的?
正如你在RFC 2616引用中提到的,PUT被认为是幂等的。当你PUT一个资源时,这两个假设在起作用:
您引用的是实体,而不是集合。
您提供的实体是完整的(整个实体)。
让我们看看你的一个例子。
{ "username": "skwee357", "email": "skwee357@domain.example" }
如果您将此文档POST到/users(如您所建议的),那么您可能会返回一个实体,例如
## /users/1
{
"username": "skwee357",
"email": "skwee357@domain.example"
}
如果稍后想修改这个实体,可以在PUT和PATCH之间进行选择。PUT可能是这样的:
PUT /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // new email address
}
您可以使用PATCH完成同样的任务。它可能是这样的:
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address
}
你会马上注意到这两者之间的区别。PUT包含该用户的所有参数,但PATCH只包含正在修改的参数(电子邮件)。
在使用PUT时,假定您正在发送完整的实体,并且该完整实体将替换该URI上的任何现有实体。在上面的示例中,PUT和PATCH实现了相同的目标:它们都更改了该用户的电子邮件地址。但是PUT通过替换整个实体来处理它,而PATCH只更新所提供的字段,而不影响其他字段。
由于PUT请求包括整个实体,如果您重复发出相同的请求,它应该总是有相同的结果(您发送的数据现在是实体的整个数据)。因此PUT是等幂的。
错误使用PUT
如果在PUT请求中使用上述PATCH数据会发生什么?
GET /users/1
{
"username": "skwee357",
"email": "skwee357@domain.example"
}
PUT /users/1
{
"email": "skwee357@gmail.com" // new email address
}
GET /users/1
{
"email": "skwee357@gmail.com" // new email address... and nothing else!
}
(为了解决这个问题,我假设服务器没有任何特定的必填项,并且允许这种情况发生……现实中可能并非如此。)
因为我们使用PUT,但只提供电子邮件,现在这是这个实体中唯一的东西。这导致了数据丢失。
这里的示例是为了说明目的——永远不要这样做(当然,除非您的意图是删除省略的字段……那么你是在使用PUT,因为它应该被使用)。这个PUT请求在技术上是等幂的,但这并不意味着它不是一个糟糕的、坏的想法。
PATCH怎么会是等幂的?
在上面的例子中,PATCH是等幂的。您进行了更改,但如果您一次又一次地进行相同的更改,它总是会返回相同的结果:您将电子邮件地址更改为新值。
GET /users/1
{
"username": "skwee357",
"email": "skwee357@domain.example"
}
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address
}
GET /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // email address was changed
}
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address... again
}
GET /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // nothing changed since last GET
}
我最初的例子,为了精确而修正
我原本有一些我认为是非等幂的例子,但它们是误导/不正确的。我将保留这些示例,但使用它们来说明不同的事情:针对同一个实体的多个PATCH文档,修改不同的属性,不会使PATCH非幂等。
假设在过去的某个时间,添加了一个用户。这是开始时的状态。
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@olddomain.example",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "10001"
}
PATCH之后,你有一个修改过的实体:
PATCH /users/1
{"email": "skwee357@newdomain.example"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.example", // the email changed, yay!
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "10001"
}
如果您重复应用PATCH,您将继续得到相同的结果:电子邮件已更改为新值。A进去,A出来,因此这是幂等的。
一个小时后,当你去泡咖啡休息一下后,另一个人带着他们自己的PATCH来了。看来邮局在做一些改变。
PATCH /users/1
{"zip": "12345"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.example", // still the new email you set
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "12345" // and this change as well
}
由于邮局的这个PATCH不涉及邮件,只涉及邮政编码,如果重复应用它,也会得到相同的结果:邮政编码被设置为新的值。A进去,A出来,因此这也是等幂的。
第二天,你决定再次发送补丁。
PATCH /users/1
{"email": "skwee357@newdomain.example"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.example",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "12345"
}
你的补丁和昨天的效果一样:它设置了电子邮件地址。A进去,A出来,因此这也是等幂的。
我原来的答案错在哪里
I want to draw an important distinction (something I got wrong in my original answer). Many servers will respond to your REST requests by sending back the new entity state, with your modifications (if any). So, when you get this response back, it is different from the one you got back yesterday, because the zip code is not the one you received last time. However, your request was not concerned with the zip code, only with the email. So your PATCH document is still idempotent - the email you sent in PATCH is now the email address on the entity.
那么PATCH什么时候不是等幂的呢?
对于这个问题的完整处理,我再次推荐你参考Jason Hoetger的答案,它已经完全回答了这个问题。