注意:当我第一次花时间阅读有关 REST 的内容时,幂等性是一个令人困惑的概念,试图正确理解。正如进一步的 cmets(和Jason Hoetger's answer)所显示的那样,我的原始答案仍然没有完全正确。有一段时间,我一直拒绝广泛更新这个答案,以避免有效地抄袭 Jason,但我现在正在编辑它,因为,嗯,我被要求(在 cmets 中)。
看完我的回答后,我建议你也读一下Jason Hoetger's excellent answer这个问题,我会尽量让我的回答变得更好,而不是简单地从杰森那里偷东西。
为什么 PUT 是幂等的?
正如您在 RFC 2616 引用中所指出的,PUT 被认为是幂等的。当你 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 只更新提供的字段,而其他的则不理会。p>
由于 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不关心email,只关心邮政编码,如果重复申请,也会得到同样的结果:邮政编码设置为新值。 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's answer。我将就此搁置,因为老实说,我认为我无法比他已经回答的更好。