首先,一些定义:

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?


当前回答

虽然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))))。

其他回答

PUT和PATCH的区别在于:

PUT必须是等幂的。为了实现这一点,您必须将整个完整的资源放在请求体中。 PATCH可以是非等幂的。这意味着在某些情况下它也可以是等幂的,比如你描述的情况。

PATCH需要一些“补丁语言”来告诉服务器如何修改资源。调用方和服务器需要定义一些“操作”,如“添加”、“替换”、“删除”。例如:

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@olddomain.example",
  "state": "NY",
  "zip": "10001"
}

PATCH /contacts/1
{
 [{"operation": "add", "field": "address", "value": "123 main street"},
  {"operation": "replace", "field": "email", "value": "abc@myemail.example"},
  {"operation": "delete", "field": "zip"}]
}

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "abc@myemail.example",
  "state": "NY",
  "address": "123 main street",
}

而不是使用显式的“操作”字段,补丁语言可以通过定义如下约定使其隐式:

PATCH请求体:

字段的存在意味着“替换”或“添加”该字段。 如果字段的值为空,则表示删除该字段。

使用上述约定,示例中的PATCH可以采用以下形式:

PATCH /contacts/1
{
  "address": "123 main street",
  "email": "abc@myemail.example",
  "zip":
}

这看起来更简洁和用户友好。但是用户需要了解底层的约定。

通过上面提到的运算,PATCH仍然是幂等的。但如果你定义像"increment"或"append"这样的操作,你可以很容易地看到它不再是幂等的。

TLDR -简化版

PUT =>设置现有资源的所有新属性。

PATCH =>部分更新现有资源(不需要所有属性)。

我将试着用门外汉的语言来总结我所理解的(也许这有帮助)

Patch不是完全等幂的(它可能是在一个理想的情况下,没有人改变你实体的另一个字段)。

在一个不理想的(现实生活)情况下,有人通过另一个补丁操作修改了对象的另一个字段,然后这两个操作都不是幂等的(意味着你都在修改的资源返回“错误”从任何一个角度来看)

所以你不能称它为等幂如果它不能覆盖所有的情况。 也许这对一些人来说不那么重要,但对另一些人来说很重要

虽然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))))。

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

把:

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

补丁:

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

假设一个对象定义如下:

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

我用:

{颜色:“红色”}

结果对象是:

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

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

{类型:“掀背车”}

因此,结果对象是:

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

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

{颜色:“红色”}

结果对象是:

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

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

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