首先,一些定义:

PUT的定义见章节9.6 RFC 2616:

PUT方法要求将所包含的实体存储在所提供的Request-URI下。如果Request-URI引用了一个已经存在的资源,那么所包含的实体应该被认为是原始服务器上的实体的修改版本。如果Request-URI不指向现有资源,并且请求用户代理能够将该URI定义为新资源,则源服务器可以使用该URI创建资源。

PATCH在RFC 5789中定义:

方法中描述的一组更改 请求实体应用于由request -标识的资源 URI。

此外,根据RFC 2616节9.1.2 PUT是等幂的,而PATCH不是。

现在让我们看一个真实的例子。当我用数据{用户名:'skwee357',电子邮件:'skwee357@domain.example'} POST到/users时,服务器能够创建资源,它将响应201和资源位置(让我们假设/users/1),任何下一次调用GET /users/1将返回{id: 1,用户名:'skwee357',电子邮件:'skwee357@domain.example'}。

现在假设我想修改我的电子邮件。邮件修改被认为是“一组更改”,因此我应该用“补丁文档”PATCH /users/1。在我的例子中,它将是JSON文档:{email: 'skwee357@newdomain.example'}。然后服务器返回200(假设权限正常)。这让我想到了第一个问题:

PATCH不是等幂的。RFC 2616和RFC 5789都是这么说的。但是,如果我发出相同的PATCH请求(用我的新电子邮件),我将获得相同的资源状态(我的电子邮件被修改为所请求的值)。为什么PATCH不是幂等的?

PATCH是一个相对较新的动词(RFC于2010年3月引入),它用来解决“修补”或修改一组字段的问题。在PATCH引入之前,每个人都使用PUT来更新资源。但是在引入PATCH之后,它让我对PUT的用途感到困惑。这就引出了我的第二个(也是主要的)问题:

What is the real difference between PUT and PATCH? I have read somewhere that PUT might be used to replace entire entity under specific resource, so one should send the full entity (instead of set of attributes as with PATCH). What is the real practical usage for such case? When would you like to replace / overwrite an entity at a specific resource URI and why is such an operation not considered updating / patching the entity? The only practical use case I see for PUT is issuing a PUT on a collection, i.e. /users to replace the entire collection. Issuing PUT on a specific entity makes no sense after PATCH was introduced. Am I wrong?


当前回答

考虑到你关于等幂的问题,我可能有点跑题了,但我希望你考虑一下进化论。

假设你有以下元素:

{
  "username": "skwee357",
  "email": "skwee357@domain.example"
}

如果你使用PUT进行修改,你必须给出对象的完整表示:

PUT /users/1
{
  "username": "skwee357",
  "email": "skwee357@newdomain.example"
}

现在更新模式,并添加一个现场电话:

PUT /users/1
{
  "username": "skwee357",
  "email": "skwee357@newdomain.example",
  "phone": "123-456-7890"
}

现在用PUT以同样的方式再次更新它,它将设置phone为null。为了避免这种坏的副作用,您必须在每次更新模式时更新所有修改元素的组件。站不住脚的。

使用PATCH就不会有这个问题,因为PATCH只更新给定的字段。因此,在我看来,您应该使用PATCH来修改一个元素(无论它是否真的是幂等的)。这是现实生活中经验的回归。

其他回答

我对此也很好奇,并找到了一些有趣的文章。我可能不能完全回答你的问题,但至少提供了一些信息。

http://restful-api-design.readthedocs.org/en/latest/methods.html

HTTP RFC指定PUT必须接受一个全新的资源 表示为请求实体。这意味着如果举个例子 只提供了某些属性,这些属性应该被删除(即设置 为null)。

鉴于此,PUT应该发送整个对象。例如,

/users/1
PUT {id: 1, username: 'skwee357', email: 'newemail@domain.example'}

这将有效地更新电子邮件。PUT可能不太有效的原因是,只修改一个字段并包括用户名是无用的。下一个例子显示了区别。

/users/1
PUT {id: 1, email: 'newemail@domain.example'}

现在,如果PUT是根据规范设计的,那么PUT将把用户名设置为null,您将得到以下结果。

{id: 1, username: null, email: 'newemail@domain.example'}

当您使用PATCH时,您只更新您指定的字段,其余的保持不变。

以下对PATCH的看法与我以前从未见过的略有不同。

http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/

The difference between the PUT and PATCH requests is reflected in the way the server processes the enclosed entity to modify the resource identified by the Request-URI. In a PUT request, the enclosed entity is considered to be a modified version of the resource stored on the origin server, and the client is requesting that the stored version be replaced. With PATCH, however, the enclosed entity contains a set of instructions describing how a resource currently residing on the origin server should be modified to produce a new version. The PATCH method affects the resource identified by the Request-URI, and it also MAY have side effects on other resources; i.e., new resources may be created, or existing ones modified, by the application of a PATCH.

PATCH /users/123

[
    { "op": "replace", "path": "/email", "value": "new.email@example.org" }
]

您或多或少地将PATCH视为更新字段的一种方式。所以你发送的不是部分对象,而是操作。例如,用价值取代电子邮件。

文章的结尾是这样的。

值得一提的是PATCH并不是真正为REST设计的 api,因为Fielding的论文没有定义任何方法来部分 修改资源。但是Roy Fielding自己说PATCH是 这是他为最初的HTTP/1.1提案创建的,因为 partial PUT从来不是RESTful的。确定你没有转移一个完整的 表示,但是REST不需要表示 完成了。

现在,我不知道我是否像许多评论员指出的那样特别同意这篇文章。发送部分表示可以很容易地成为更改的描述。

对我来说,我不太喜欢使用PATCH。在大多数情况下,我将把PUT视为PATCH,因为到目前为止我注意到的唯一真正的区别是PUT“应该”将缺失的值设置为null。这可能不是“最正确”的方法,但祝你好运,能写出完美的代码。

注意:当我第一次花时间阅读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的答案,它已经完全回答了这个问题。

虽然Dan Lowe的精彩回答非常彻底地回答了OP关于PUT和PATCH之间区别的问题,但它对PATCH为什么不是幂等的问题的回答并不完全正确。

为了说明为什么PATCH不是幂等的,它有助于从幂等的定义开始(来自维基百科):

幂等这个术语更广泛地用于描述一个操作,如果执行一次或多次将产生相同的结果[…]幂等函数对于任意值x具有f(f(x)) = f(x)的性质。

在更容易理解的语言中,幂等PATCH可以定义为:在使用补丁文档修补一个资源之后,所有后续使用相同补丁文档对同一资源的PATCH调用都不会改变该资源。

相反,一个非幂等的操作是f(f(x)) != f(x),对于PATCH来说,它可以表述为:在用补丁文档修补一个资源之后,后续的PATCH调用使用相同的补丁文档对同一资源进行更改。

为了说明一个非幂等PATCH,假设有一个/users资源,并且假设调用GET /users返回一个用户列表,当前:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" }]

假设服务器允许修补/users,而不是像OP的例子中那样修补/users/{id}。让我们发出这个PATCH请求:

PATCH /users
[{ "op": "add", "username": "newuser", "email": "newuser@example.org" }]

我们的补丁文档指示服务器将一个名为newuser的新用户添加到用户列表中。第一次调用后,GET /users将返回:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
 { "id": 2, "username": "newuser", "email": "newuser@example.org" }]

现在,如果我们发出与上面完全相同的PATCH请求,会发生什么?(为了便于本例,我们假设/users资源允许重复用户名。)“op”是“add”,所以一个新用户被添加到列表中,并且后续的GET /users返回:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
 { "id": 2, "username": "newuser", "email": "newuser@example.org" },
 { "id": 3, "username": "newuser", "email": "newuser@example.org" }]

/users资源再次发生了变化,尽管我们对完全相同的端点发出了完全相同的PATCH。如果PATCH是f(x) f(f(x))不等于f(x)因此,这个PATCH不是幂等的。

尽管PATCH不能保证是幂等的,但PATCH规范中没有任何内容可以阻止您在特定服务器上执行所有PATCH操作。RFC 5789甚至预测了幂等PATCH请求的优势:

PATCH请求可以以一种幂等的方式发出, 这也有助于防止两者相撞的坏结果 PATCH请求在相同的时间范围内对相同的资源。

在Dan的例子中,他的PATCH操作实际上是等幂的。在那个例子中,/users/1实体在我们的PATCH请求之间发生了变化,但不是因为我们的PATCH请求;实际上是邮局的不同补丁文件导致邮政编码发生变化。邮局不同的PATCH是不同的操作;如果我们的PATCH是f(x),邮局的PATCH就是g(x)。幂等性说明f(f(f(x))) = f(x),但不保证f(g(f(x))))。

Everyone else has answered the PUT vs PATCH. I was just going to answer what part of the title of the original question asks: "... in REST API real life scenarios". In the real world, this happened to me with internet application that had a RESTful server and a relational database with a Customer table that was "wide" (about 40 columns). I mistakenly used PUT but had assumed it was like a SQL Update command and had not filled out all the columns. Problems: 1) Some columns were optional (so blank was valid answer), 2) many columns rarely changed, 3) some columns the user was not allowed to change such as time stamp of Last Purchase Date, 4) one column was a free-form text "Comments" column that users diligently filled with half-page customer services comments like spouses name to ask about OR usual order, 5) I was working on an internet app at time and there was worry about packet size.

The disadvantage of PUT is that it forces you to send a large packet of info (all columns including the entire Comments column, even though only a few things changed) AND multi-user issue of 2+ users editing the same customer simultaneously (so last one to press Update wins). The disadvantage of PATCH is that you have to keep track on the view/screen side of what changed and have some intelligence to send only the parts that changed. Patch's multi-user issue is limited to editing the same column(s) of same customer.

在我看来,幂等性意味着:

把:

我发送了一个竞争资源定义,因此-结果资源状态与PUT参数所定义的完全相同。每次我用相同的PUT参数更新资源时——结果状态完全相同。

补丁:

我只发送了资源定义的一部分,所以可能会发生其他用户正在更新此资源的other参数。因此,具有相同参数及其值的连续补丁可能导致不同的资源状态。例如:

假设一个对象定义如下:

汽车: -颜色:黑色, -类型:轿车, -座位:5个

我用:

{颜色:“红色”}

结果对象是:

汽车: -颜色:红色, -类型:轿车, -座位:5个

然后,其他一些用户给这辆车打了补丁:

{类型:“掀背车”}

因此,结果对象是:

汽车: -颜色:红色, -类型:掀背式, -座位:5个

现在,如果我再次修补这个对象:

{颜色:“红色”}

结果对象是:

汽车: -颜色:红色, -类型:掀背式, -座位:5个

与我之前得到的有什么不同!

这就是为什么PATCH不是幂等的,而PUT是幂等的。