ChatGPT解决这个技术问题 Extra ChatGPT

在 REST API 现实生活场景中使用 PUT 与 PATCH 方法

首先,一些定义:

PUT 在 Section 9.6 RFC 2616 中定义:

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

PATCH 在 RFC 5789 中定义:

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

同样根据 RFC 2616 Section 9.1.2 PUT 是幂等的,而 PATCH 不是。

现在让我们看一个真实的例子。当我使用数据 {username: 'skwee357', email: 'skwee357@domain.example'}/users 进行 POST 并且服务器能够创建资源时,它将以 201 和资源位置(假设 /users/1)响应,并且任何下一次对 GET /users/1 的调用都将返回{id: 1, username: 'skwee357', email: 'skwee357@domain.example'}

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

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

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

PUT 和 PATCH 之间的真正区别是什么?我在某处读到 PUT 可能用于替换特定资源下的整个实体,因此应该发送完整的实体(而不是像 PATCH 那样的属性集)。这种情况的真正实际用途是什么?您何时想替换/覆盖特定资源 URI 处的实体,为什么不考虑更新/修补实体这样的操作?我看到的 PUT 的唯一实际用例是在集合上发出 PUT,即 /users 来替换整个集合。引入 PATCH 后,在特定实体上发出 PUT 毫无意义。我错了吗?

a) 它是 RFC 2616,而不是 2612。b) RFC 2616 已经过时,PUT 的当前规范在 greenbytes.de/tech/webdav/rfc7231.html#PUT,c) 我不明白你的问题;不是很明显 PUT 可以用来替换任何资源,不仅是一个集合,d) 在引入 PATCH 之前,人们通常使用 POST,e) 最后,是的,一个 specific PATCH 请求(取决于补丁格式)可以是幂等的;只是一般情况下不是这样。
如果有帮助,我写了一篇关于 PATCH 与 PUT eq8.eu/blogs/36-patch-vs-put-and-the-patch-json-syntax-war 的文章
简单:POST 在集合中创建一个项目。 PUT 替换一个项目。 PATCH 修改一个项目。 POST 时,计算新项目的 URL 并在响应中返回,而 PUT 和 PATCH 在请求中需要 URL。正确的?
抱歉@theking2 网址已更改,假设为 blog.eq8.eu/article/put-vs-patch.html

D
Dan Lowe

注意:当我第一次花时间阅读有关 REST 的内容时,幂等性是一个难以理解的概念。正如进一步的评论(和Jason Hoetger's answer)所显示的那样,我的原始答案仍然没有完全正确。有一段时间,我一直拒绝广泛更新这个答案,以避免有效地抄袭杰森,但我现在正在编辑它,因为,好吧,我被要求(在评论中)。

阅读我的答案后,我建议您也阅读此问题的Jason Hoetger's excellent answer,我将尝试使我的答案更好,而不是简单地从 Jason 那里窃取。

为什么 PUT 是幂等的?

正如您在 RFC 2616 引用中所指出的,PUT 被认为是幂等的。当你 PUT 一个资源时,这两个假设在起作用:

您指的是一个实体,而不是一个集合。您提供的实体是完整的(整个实体)。

让我们看一个你的例子。

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

如果您按照您的建议将此文档发布到 /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 仅包含正在修改的参数 (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"       // new email address
}

GET /users/1
{
    "email": "skwee357@gmail.com"      // new email address... and nothing else!
}

(出于这个问题的目的,我假设服务器没有任何特定的必填字段,并且会允许这种情况发生......实际上可能并非如此。)

由于我们使用了 PUT,但只提供了 email,现在这是该实体中唯一的东西。这导致了数据丢失。

这个例子在这里是为了说明的目的——永远不要这样做(除非你的意图是删除省略的字段,当然......然后你正在使用应该使用的 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。

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's answer,它已经完全回答了这个问题。


这句话不太正确:“但它是幂等的:只要 A 进去,B 总是出来”。例如,如果您在 Post Office 更新邮政编码之前 GET /users/1,然后在 Post Office 更新后再次发出相同的 GET /users/1 请求,您将收到两个不同的响应(不同的邮政编码)。相同的“A”(GET 请求)正在进入,但您会得到不同的结果。然而 GET 仍然是幂等的。
@DanLowe:GET 绝对保证是幂等的。它在 RFC 2616 的第 9.1.2 节和更新的规范 RFC 7231 section 4.2.2 中准确地说,“在本规范定义的请求方法中,PUT、DELETE 和安全请求方法是幂等的。”幂等性并不意味着“每次发出相同的请求时都会得到相同的响应”。 7231 4.2.2 继续说:“重复请求将具有相同的预期效果,即使原始请求成功,尽管响应可能不同。
@JasonHoetger我承认这一点,但我不明白它与这个答案有什么关系,它讨论了PUT和PATCH,甚至从未提到GET ...
“这个 PUT 请求在技术上是幂等的” - 是的,但它发送了错误的数据(即丢失的数据),这就是重点。好片。
啊,@JasonHoetger 的评论澄清了这一点:只有多个幂等方法请求的结果状态而不是响应需要相同。
A
Artelius

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

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

幂等一词更全面地用于描述如果执行一次或多次将产生相同结果的操作 [...] 幂等函数是具有 f(f(x)) = f(x) 的属性的函数任何值 x。

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

相反,非幂等操作是 f(f(x)) != f(x),对于 PATCH 可以表示为:在使用补丁文档对资源进行 PATCH 之后,随后的 PATCH 调用使用相同的补丁文件确实会更改资源。

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

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

假设服务器允许 PATCHing /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)))


假设服务器还允许在 /users 发出 PUT,这也会使 PUT 非幂等。所有这一切都归结为服务器是如何设计来处理请求的。
因此,我们可以只使用 PATCH 操作来构建 API。那么,使用 http VERBS 对 Resources 进行 CRUD 操作的 REST 原则是什么?先生们,我们不是过于复杂了 PATCH 边界吗?
如果 PUT 在集合上实现(例如 /users),则任何 PUT 请求都应替换该集合的内容。因此,对 /users 的 PUT 应该期望用户集合并删除所有其他用户。这是幂等的。您不太可能在 /users 端点上做这样的事情。但是像 /users/1/emails 这样的东西可能是一个集合,并且允许用一个新集合替换整个集合可能是完全有效的。
尽管这个答案提供了幂等性的一个很好的例子,但我相信这可能会使典型 REST 场景中的水变得浑浊。在这种情况下,您有一个 PATCH 请求,其中包含触发特定服务器端逻辑的附加 op 操作。这将要求服务器和客户端了解为 op 字段传递的特定值以触发服务器端工作流。在更直接的 REST 场景中,这种类型的 op 功能是不好的做法,应该直接通过 HTTP 动词处理。
我永远不会考虑针对集合发布 PATCH,只有 POST 和 DELETE。这真的做过吗?因此,对于所有实际目的,PATCH 是否可以被认为是幂等的?
B
Bijan

TLDR - 简化版

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

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


另外: PATCH => 可以是指令,而不仅仅是更新的属性
如果 gal 易于更新,我们为什么要发送现有资源的所有属性?为什么检查它很重要?而不是简单地更新发送的字段?
S
Stephen Ostermiller

我对此也很好奇,发现了一些有趣的文章。我可能无法完全回答您的问题,但这至少提供了更多信息。

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/

PUT 和 PATCH 请求之间的区别体现在服务器处理封闭实体以修改由 Request-URI 标识的资源的方式上。在 PUT 请求中,包含的实体被认为是存储在源服务器上的资源的修改版本,并且客户端请求替换存储的版本。然而,对于 PATCH,封闭的实体包含一组指令,描述如何修改当前驻留在源服务器上的资源以生成新版本。 PATCH 方法会影响 Request-URI 标识的资源,也可能对其他资源产生副作用;即,可以通过应用 PATCH 创建新资源或修改现有资源。

PATCH /users/123

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

您或多或少地将 PATCH 视为更新字段的一种方式。因此,您不是通过部分对象发送,而是通过操作发送。即用价值替换电子邮件。

文章以此结束。

值得一提的是,PATCH 并不是真正为真正的 REST API 设计的,因为 Fielding 的论文没有定义任何方式来部分修改资源。但是,Roy Fielding 自己说 PATCH 是 [他] 为最初的 HTTP/1.1 提案创建的东西,因为部分 PUT 绝不是 RESTful。当然,您没有传输完整的表示,但 REST 并不要求表示是完整的。

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

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


可能值得添加:在 William Durand 的文章(和 rfc 6902)中有“op”是“add”的示例。这显然不是幂等的。
或者,您可以更轻松地使用 RFC 7396 Merge Patch 代替,避免构建补丁 JSON。
对于 nosql 表,patch 和 put 之间的区别很重要,因为 nosql 没有列
B
Bilal

tl;博士版

POST:用于创建实体

PUT:用于更新/替换现有实体,您必须在其中发送您希望存储的实体的整个表示

PATCH:用于更新您只发送需要更新的字段的实体


为什么发送所有字段进行更新很重要?
@jossefaz 因为您想替换整个资源。
那么您认为我们可以将您对 PUT 的答案更新为“用于更新/替换现有实体”吗?
谢谢@jossefaz,我更新了我的答案
简明扼要 = 正是我们想要的。应该是公认的答案。
S
Stephen Ostermiller

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”这样的操作,你可以很容易地看到它不再是幂等的了。


Z
Zbigniew Szczęsny

在我的拙见中,幂等性意味着:

放:

我发送了一个完整的资源定义,所以 - 生成的资源状态与 PUT 参数定义的完全相同。每次我使用相同的 PUT 参数更新资源时 - 结果状态完全相同。

修补:

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

假设一个对象定义如下:

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

我修补它:

{红色'}

结果对象是:

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

然后,其他一些用户对这辆车进行了修补:

{类型:'掀背车'}

所以,结果对象是:

CAR:-颜色:红色,-类型:掀背车,-座位:5

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

{红色'}

结果对象是:

CAR:-颜色:红色,-类型:掀背车,-座位:5

和我以前的有什么不同!

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


S
Stephen Ostermiller

让我更仔细地引用和评论 RFC 7231 section 4.2.2,在之前的评论中已经引用了:

如果使用该方法的多个相同请求对服务器的预期效果与单个此类请求的效果相同,则该请求方法被认为是“幂等的”。在本规范定义的请求方法中,PUT、DELETE 和安全请求方法是幂等的。 (...) 幂等方法的区别在于,如果在客户端能够读取服务器的响应之前发生通信故障,请求可以自动重复。例如,如果客户端发送 PUT 请求,并且在收到任何响应之前底层连接已关闭,则客户端可以建立新连接并重试幂等请求。它知道重复请求将具有相同的预期效果,即使原始请求成功,尽管响应可能不同。

那么,一个幂等方法的重复请求后,应该是什么“相同”呢?不是服务器状态,也不是服务器响应,而是预期效果。特别是,“从客户端的角度来看”该方法应该是幂等的。现在,我认为这种观点表明,Dan Lowe's answer 中的最后一个示例,我不想在这里抄袭,确实表明 PATCH 请求可以是非幂等的(以比示例更自然的方式Jason Hoetger's answer)。

事实上,让我们通过明确地为第一个客户制定一个可能的意图来使示例更加精确。假设这个客户通过项目的用户列表检查他们的电子邮件和邮政编码。他从用户 1 开始,注意到 zip 是正确的,但电子邮件是错误的。他决定用一个完全合法的 PATCH 请求来纠正这个问题,并且只发送

PATCH /users/1
{"email": "skwee357@newdomain.example"}

因为这是唯一的更正。现在,由于某些网络问题,请求失败,并在几个小时后自动重新提交。同时,另一个客户端(错误地)修改了用户 1 的 zip。然后,第二次发送相同的 PATCH 请求并没有达到客户端的预期效果,因为我们最终得到了一个不正确的 zip。因此,该方法在 RFC 的意义上不是幂等的。

如果客户端使用 PUT 请求更正电子邮件,将用户 1 的所有属性连同电子邮件一起发送到服务器,即使稍后必须重新发送请求并且用户 1 已被修改,也将达到预期的效果同时 --- 因为第二个 PUT 请求将覆盖自第一个请求以来的所有更改。


E
Eric Wood

其他人都回答了 PUT vs PATCH。我只是要回答原始问题标题的哪一部分:“......在 REST API 现实生活场景中”。在现实世界中,这发生在我的互联网应用程序中,该应用程序有一个 RESTful 服务器和一个具有“宽”(大约 40 列)客户表的关系数据库。我错误地使用了 PUT,但认为它就像一个 SQL 更新命令并且没有填写所有列。问题:1)一些列是可选的(所以空白是有效的答案),2)许多列很少改变,3)一些列不允许用户更改,例如最后购买日期的时间戳,4)一列是免费的-表单文本“评论”栏,用户努力填写半页客户服务评论,如配偶姓名询问或常规订单,5)我当时正在开发互联网应用程序,担心数据包大小。

PUT 的缺点是它迫使您发送大量信息(包括整个评论列的所有列,即使只有少数内容发生了变化)以及 2 个以上用户同时编辑同一客户的多用户问题(所以最后一按更新获胜)。 PATCH 的缺点是您必须在视图/屏幕方面跟踪更改的内容,并具有一些智能来仅发送更改的部分。 Patch 的多用户问题仅限于编辑同一客户的同一列。


B
Bharat Reddy

PUT 方法非常适合以表格格式更新数据,例如关系数据库或实体(例如存储)。根据用例,它可用于部分更新数据或整体替换实体。这将始终是幂等的。

PATCH 方法可用于更新(或重构)存储在本地文件系统或无 sql 数据库中的 json 或 xml 格式的数据。这可以通过提及要在请求中执行的操作/操作来执行,例如将键值对添加/删除/移动到 json 对象。 remove 操作可用于删除键值对,重复请求将导致错误,因为该键已被删除,使其成为非幂等方法。有关 json 数据修补请求,请参阅 RFC 6902

artical 包含与 PATCH 方法相关的详细信息。


感谢您的文章链接。我对 HTTP PATCH 和 JSON-PATCH 一致性进行了有趣的阐述
S
Stephen Ostermiller

为了结束关于幂等性的讨论,我应该指出,可以通过两种方式在 REST 上下文中定义幂等性。让我们首先形式化一些事情:

resource 是一个函数,其共同域是字符串类。换言之,资源是 String × Any 的子集,其中所有键都是唯一的。我们将资源类称为 Res

对资源的 REST 操作是一个函数 f(x: Res, y: Res): Res。 REST 操作的两个示例是:

PUT(x: Res, y: Res): Res = x, 和

PATCH(x: Res, y: Res): Res,其作用类似于 PATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}。

(这个定义专门用来讨论 PUTPOST,例如,对于 GETPOST 没有多大意义,因为它不关心持久性)。

现在,通过修复 x: Res(从信息上讲,使用柯里化),PUT(x: Res)PATCH(x: Res)Res → Res 类型的单变量函数。

一个函数 g: Res → Res 称为全局幂等,当 g ○ g == g 时,即对于任何 y: Res,g(g(y)) = g(y)。让 x: Res 资源,并且 k = x.keys。函数 g = f(x) 称为左幂等,当对于每个 y: Res,我们有 g(g(y))|ₖ == g(y)|ₖ。如果我们查看应用的键,这基本上意味着结果应该是相同的。

因此,PATCH(x) 不是全局幂等的,而是左幂等的。左幂等性在这里很重要:如果我们修补资源的几个键,如果我们再次修补它,我们希望这些键相同,并且我们不关心资源的其余部分。

当 RFC 谈论 PATCH 不是幂等时,它是在谈论全局幂等性。好吧,它不是全局幂等的很好,否则它会是一个失败的操作。

现在,Jason Hoetger's answer 试图证明 PATCH 甚至不是幂等的,但这样做会破坏太多东西:

首先,PATCH 用于集合,尽管 PATCH 被定义为在地图/字典/键值对象上工作。

如果有人真的想将 PATCH 应用于集合,那么应该使用自然转换:t: Set → Map, 在 A 中用 x 定义 iff t(A)(x) ==真的。使用此定义,修补是左幂等的。

在这个例子中,没有使用这个翻译,而是 PATCH 像一个 POST 一样工作。首先,为什么要为对象生成一个ID?它是什么时候生成的?如果首先将对象与集合的元素进行比较,并且如果没有找到匹配的对象,则生成 ID,然后程序应该再次以不同的方式工作({id: 1, email: "me@site.example"}必须与 {email: "me@site.example"} 匹配,否则程序总是被破坏并且 PATCH 不可能修补)。如果在检查集合之前生成了 ID,则程序再次被破坏。

可以举出 PUT 是非幂等的例子,它破坏了这个例子中被破坏的一半事物:

生成附加功能的一个示例是版本控制。人们可能会记录单个对象的更改次数。在这种情况下,PUT 不是幂等的:PUT /user/12 {email: "me@site.example"} 第一次导致 {email: "...", version: 1} 和 {email: "。 ..", version: 2} 第二次。

与 ID 混淆后,每次更新对象时可能会生成一个新 ID,从而导致非幂等 PUT。

以上所有例子都是人们可能遇到的自然例子。

我的最后一点是,PATCH 不应该是全局幂等的,否则不会给你想要的效果。您希望更改用户的电子邮件地址,而不触及其余信息,并且您不想覆盖访问同一资源的另一方的更改。


h
harit

一个很好的解释在这里-

https://blog.segunolalive.com/posts/restful-api-design-%E2%80%94-put-vs-patch/#:~:text=RFC%205789,not%20required%20to%20be%20idempotent

Normal Payload- // 地块 1 上的房子 { 地址:'地块 1',所有者:'segun',类型:'duplex',颜色:'green',房间:'5',厨房:'1',窗户: 20 } PUT For Updated- // PUT 请求有效负载以更新地块 1 上的房屋窗口 { 地址:'地块 1',所有者:'segun',类型:'duplex',颜色:'green',房间:'5' , kitchens: '1', windows: 21 } 注意:在上面的有效载荷中,我们试图将 windows 从 20 更新到 21。

现在查看 PATH 有效负载-// 修补请求有效负载以更新 House { windows: 21 } 上的窗口

由于 PATCH 不是幂等的,失败的请求不会在网络上自动重新尝试。此外,如果对不存在的 url 发出 PATCH 请求,例如尝试替换不存在的建筑物的前门,它应该会失败而不会创建新资源,这与 PUT 不同,后者将使用有效负载创建新资源。想一想,在一个家庭地址有一扇单独的门会很奇怪。


A
Alexandru Dinu

我将尝试用外行的方式总结我所理解的(也许有帮助)

补丁不是完全幂等的(它可以在没有人更改实体的另一个字段的理想情况下)。

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

因此,如果它不能涵盖 100% 的情况,则不能称其为幂等。也许这对某些人来说并不那么重要,但对其他人来说


B
Benjamin

我要补充的一个附加信息是,与 PUT 请求相比,PATCH 请求使用的带宽更少,因为只发送了一部分数据而不是整个实体。因此,只需使用 PATCH 请求更新特定记录(如(1-3 条记录)),而 PUT 请求更新大量数据。就是这样,不要想太多,也不要太担心。