注意: 当我第一次花时间阅读有关REST的内容时,幂等性是一个让人困惑的概念。即使在我的原始回答中,进一步的评论和
Jason Hoetger的答案也表明我还没有完全理解它。一段时间以来,我一直抵制大量更新这个答案,以避免实际上剽窃Jason的内容,但现在我正在编辑它,因为,好吧,在评论中有人要求我这样做。
在阅读我的回答后,我建议您还阅读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"
}
您可以使用PATCH完成相同的操作。可能是这样的:
PATCH /users/1
{
"email": "skwee357@gmail.com"
}
这两者之间立即就会有一个区别。PUT请求包含该用户的所有参数,但PATCH仅包含被修改的一个参数(email
)。
使用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"
}
GET /users/1
{
"email": "skwee357@gmail.com"
}
我假设这个服务器没有任何特定的必填字段,可以允许这种情况发生...但在现实中可能并非如此。由于我们使用了PUT方法,但只提供了“电子邮件”字段,所以现在这个实体中只有这一个字段。这导致了数据丢失。
这个例子是为了说明目的而提供的--千万不要真的这样做(除非你的意图是删除被省略的字段...那么你正在正确地使用PUT)。这个PUT请求在技术上是幂等的,但这并不意味着它不是一个可怕的、破碎的想法。
PATCH如何成为幂等操作?
在上面的例子中,PATCH确实是幂等的。你进行了一次更改,但如果你一遍又一遍地进行相同的更改,它总是会返回相同的结果:你将电子邮件地址更改为新值。
GET /users/1
{
"username": "skwee357",
"email": "skwee357@domain.example"
}
PATCH /users/1
{
"email": "skwee357@gmail.com"
}
GET /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com"
}
PATCH /users/1
{
"email": "skwee357@gmail.com"
}
GET /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com"
}
我的原始示例,修正后更准确
我最初提供的示例被认为是展示非幂等性的,但它们是误导性/不正确的。我将保留这些示例,但用它们来说明另一件事情:对同一实体进行多个 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",
"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",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "12345"
}
由于这个来自邮局的PATCH只涉及邮政编码而不是电子邮件,如果重复应用它,它也会得到相同的结果:邮政编码被设置为新值。A进去,A出来,因此这也是幂等的。
第二天,您决定再次发送您的PATCH。
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,因此这也是幂等的。
我在原回答中错误的地方
我想要强调一个重要的区别(这是我在原回答中犯的错误)。许多服务器会通过发送带有您修改后的新实体状态来响应您的REST请求。因此,当您收到此响应时,它与您昨天收到的不同,因为邮政编码不是上次收到的那个。但是,您的请求并不关心邮政编码,仅关心电子邮件。因此,您的PATCH文档仍然是幂等的 - 您在PATCH中发送的电子邮件现在是实体上的电子邮件地址。
那么,PATCH 什么时候不是幂等的呢?
关于这个问题的详细解答,请再次参考Jason Hoetger的答案。