注意:当我第一次花时间阅读有关REST的知识时,幂等性是一个难以理解的概念。正如我进一步的评论(以及Jason Hoetger的答案)所显示的那样,我在原始答案中仍然没有完全正确。有一阵子,我拒绝广泛地更新此答案,以避免有效地Ja窃Jason,但我现在正在编辑它,因为(在评论中)我被要求这样做。
阅读我的回答后,建议您也阅读Jason Hoetger对这个问题的出色回答,我将尝试使我的回答更好,而不仅仅是从Jason那里窃取。
为什么PUT是幂等的?
正如您在RFC 2616引用中指出的那样,PUT被认为是幂等的。当您放置资源时,这两个假设起作用了:
您指的是实体,而不是集合。
您要提供的实体是完整的(整个实体)。
让我们来看一个例子。
{ "username": "skwee357", "email": "skwee357@domain.com" }
如果/users
按照建议将文档发布到,则可能会找回诸如
## /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
如果以后要修改此实体,请在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仅包含正在修改的参数(email
)。
使用PUT时,假定您正在发送完整实体,并且该完整实体将替换该URI上的任何现有实体。在上面的示例中,PUT和PATCH实现了相同的目标:它们都更改了该用户的电子邮件地址。但是PUT通过替换整个实体来处理它,而PATCH仅更新所提供的字段,而其他字段则不予处理。
由于PUT请求包含整个实体,因此,如果您反复发出相同的请求,则它应始终具有相同的结果(您发送的数据现在是该实体的整个数据)。因此,PUT是幂等的。
错误使用PUT
如果在PUT请求中使用上述PATCH数据,会发生什么情况?
GET /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
PUT /users/1
{
"email": "skwee357@gmail.com" // new email address
}
GET /users/1
{
"email": "skwee357@gmail.com" // new email address... and nothing else!
}
(出于这个问题的目的,我假设服务器没有任何特定的必填字段,并且将允许这种情况发生……实际上可能并非如此。)
由于我们使用的是PUT,但仅提供email
,因此这是该实体中唯一的东西。这导致数据丢失。
此示例仅出于说明目的-切勿实际执行此操作。这个PUT请求在技术上是幂等的,但这并不意味着它不是一个糟糕的主意。
PATCH如何成为幂等的?
在上面的示例中,PATCH 是幂等的。您进行了更改,但是如果一次又一次地进行相同的更改,它将始终返回相同的结果:您已将电子邮件地址更改为新值。
GET /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
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.com",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "10001"
}
进行PATCH之后,您拥有一个已修改的实体:
PATCH /users/1
{"email": "skwee357@newdomain.com"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.com", // 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.com", // 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。
PATCH /users/1
{"email": "skwee357@newdomain.com"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.com",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "12345"
}
您的补丁程序具有与昨天相同的效果:它设置了电子邮件地址。A进去了,A出来了,因此这也是幂等的。
我的原始答案有误
我想划出一个重要的区别(我原来的回答有误)。许多服务器将通过发送回您修改后的新实体状态来响应您的REST请求。因此,当您收到此答复时,它与昨天收到的答复有所不同,因为邮政编码不是您上次收到的邮政编码。但是,您的请求与邮政编码无关,仅与电子邮件无关。因此,您的PATCH文档仍然是幂等的-您在PATCH中发送的电子邮件现在是实体上的电子邮件地址。
那么,什么时候PATCH不是幂等的呢?
有关此问题的完整说明,我再次请您参考Jason Hoetger的回答。我只是要保留它,因为老实说我认为我不能比他已经更好地回答这部分。