首先,一些定义:
PUT 在第 9.6 节 RFC 2616中定义:
PUT 方法请求将封闭的实体存储在提供的 Request-URI 下。如果 Request-URI 引用一个已经存在的资源,封闭的实体 应该被认为是源服务器上的一个修改版本 。如果 Request-URI 不指向现有资源,并且该 URI 能够被请求用户代理定义为新资源,则源服务器可以使用该 URI 创建资源。
PATCH 在RFC 5789中定义:
PATCH 方法请求将请求实体中描述的 一组更改 应用于由 Request-URI 标识的资源。
同样根据RFC 2616 第 9.1.2 节, PUT 是幂等的,而 PATCH 不是。
现在让我们看一个真实的例子。当我/users对数据进行 POST{username: 'skwee357', email: 'skwee357@domain.com'}并且服务器能够创建资源时,它将以 201 和资源位置(让我们假设/users/1)响应,并且对 GET 的任何下一次调用都/users/1将返回{id: 1, username: 'skwee357', email: 'skwee357@domain.com'}。
/users
{username: 'skwee357', email: 'skwee357@domain.com'}
/users/1
{id: 1, username: 'skwee357', email: 'skwee357@domain.com'}
现在让我们说我想修改我的电子邮件。电子邮件修改被认为是“一组更改”,因此我应该/users/1使用“补丁文档”进行修补。在我的情况下,它将是 json 文档:{email: 'skwee357@newdomain.com'}. 然后服务器返回 200(假设权限正常)。这让我想到了第一个问题:
{email: 'skwee357@newdomain.com'}
PATCH 是一个相对较新的动词(RFC 于 2010 年 3 月推出),用于解决“打补丁”或修改一组字段的问题。在引入 PATCH 之前,大家都使用 PUT 来更新资源。但是在引入 PATCH 之后,它让我对 PUT 的用途感到困惑。这让我想到了我的第二个(也是主要的)问题:
注意 :当我第一次花时间阅读 REST 时,幂等性是一个难以理解的概念。正如进一步的评论(和Jason Hoetger 的回答)所显示的那样,我的原始答案仍然没有完全正确。有一段时间,我一直拒绝广泛更新这个答案,以避免有效地抄袭杰森,但我现在正在编辑它,因为,好吧,我被要求(在评论中)。
看完我的回答后,我建议你也阅读Jason Hoetger对这个问题的出色回答,我会尽量让我的回答变得更好,而不是简单地从 Jason 那里偷东西。
正如您在 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)。
email
使用 PUT 时,假定您发送的是完整实体,并且该完整实体 替换 了该 URI 处的任何现有实体。在上面的示例中,PUT 和 PATCH 实现了相同的目标:它们都更改了该用户的电子邮件地址。但是 PUT 通过替换整个实体来处理它,而 PATCH 只更新提供的字段,而让其他字段不理会。
由于 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 是 幂等的。您进行了更改,但如果您一次又一次地进行相同的更改,它总是会返回相同的结果:您将电子邮件地址更改为新值。
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 中发送的电子邮件现在是实体上的电子邮件地址。
对于这个问题的完整处理,我再次向您推荐 Jason Hoetger 的回答。我只想把它留在那里,因为老实说,我认为我不能比他已经回答的更好。