本文介绍了在REST API实际场景中使用PUT与PATCH方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,一些定义:

PUT在第9.6节RFC 2616 中定义:

PATCH在 RFC 5789 中定义:

还根据 RFC 2616第9.1.2节 > PUT是幂等的,而PATCH不是.

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

现在让我们说我想修改我的电子邮件.电子邮件修改被认为是一组更改",因此我应该使用"补丁程序来修补/users/1文档".在我的情况下,它将是json文档:{email: '[email protected]'}.然后,服务器返回200(假设允许).这使我想到第一个问题:

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

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

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

解决方案

注意:当我第一次阅读有关REST的文章时,幂等性是一个难以理解的概念.如进一步的评论(和 Jason Hoetger的答案)所示,我在原始答案中仍然没有完全正确.一段时间以来,我一直拒绝广泛地更新此答案,以避免有效地窃Jason,但我现在正在编辑它,因为(在评论中)我被要求这样做.

阅读我的答案后,建议您也阅读 Jason Hoetger的出色答案,我将尝试不用简单地从Jason那里窃取,就可以使我的答案更好.

为什么PUT是幂等的?

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

  1. 您指的是实体,而不是集合.

  2. 您提供的实体已完成(整个实体).

让我们看看您的示例之一.

 { "username": "skwee357", "email": "[email protected]" }
 

如果您按照建议将文档发布到/users,则可能会找回诸如

的实体

 ## /users/1

{
    "username": "skwee357",
    "email": "[email protected]"
}
 

如果以后要修改此实体,请在PUT和PATCH之间进行选择. PUT可能看起来像这样:

 PUT /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // new email address
}
 

您可以使用PATCH完成相同的操作.可能看起来像这样:

 PATCH /users/1
{
    "email": "[email protected]"       // new email address
}
 

您会立即注意到两者之间的差异. PUT包含该用户的所有参数,但PATCH仅包含正在修改的参数(email).

使用PUT时,假定您正在发送完整实体,并且该完整实体替换该URI上的任何现有实体.在上面的示例中,PUT和PATCH实现了相同的目标:它们都更改了该用户的电子邮件地址.但是PUT通过替换整个实体来处理它,而PATCH仅更新所提供的字段,而其他字段则保持不变.

由于PUT请求包含整个实体,因此,如果您反复发出相同的请求,则它应始终具有相同的结果(您发送的数据现在是该实体的整个数据).因此,PUT是幂等的.

使用PUT错误

如果在PUT请求中使用上述PATCH数据会发生什么?

 GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"
}
PUT /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "email": "[email protected]"      // new email address... and nothing else!
}
 

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

由于我们使用了PUT,但仅提供了email,因此这是该实体中唯一的东西.这导致数据丢失.

此示例仅用于说明目的-切勿实际执行此操作.这个PUT请求在技术上是幂等的,但这并不意味着它不是一个糟糕的主意.

PATCH如何幂等?

在上面的示例中,PATCH 是幂等的.您进行了更改,但是如果一次又一次地进行相同的更改,它将始终返回相同的结果:您已将电子邮件地址更改为新值.

 GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // email address was changed
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address... again
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // nothing changed since last GET
}
 

我的原始示例,出于准确性而固定

我最初有一些例子,我认为这些例子显示出非幂等性,但它们具有误导性/不正确性.我将保留这些示例,但使用它们来说明另一件事:针对同一个实体的多个PATCH文档,修改不同的属性,不会使PATCH成为非幂等的.

比方说,在过去的某个时间,添加了一个用户.这是您开始的状态.

 {
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}
 

在PATCH之后,您有一个修改后的实体:

 PATCH /users/1
{"email": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",    // 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": "[email protected]",  // 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": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"
}
 

您的补丁程序具有与昨天相同的效果:它设置了电子邮件地址. A进去了,A出来了,因此这也是幂等的.

我的原始答案有误

我想划出一个重要的区别(我原来的回答有误).许多服务器将通过发送回您修改的新实体状态来响应您的REST请求(如果有的话).因此,当您找回此响应时,它与您昨天收到的有所不同,因为邮政编码不是您上次收到的邮政编码.但是,您的请求与邮递区号无关,仅与电子邮件有关.因此,您的PATCH文档仍然是幂等的-您在PATCH中发送的电子邮件现在是实体上的电子邮件地址.

那么什么时候PATCH不是幂等的呢?

要对此问题进行全面处理,我再次请您参考 Jason Hoetger的答案.我只想保留它,因为老实说我认为我无法比他已经更好地回答这部分.

First of all, some definitions:

PUT is defined in Section 9.6 RFC 2616:

PATCH is defined in RFC 5789:

Also according to RFC 2616 Section 9.1.2 PUT is Idempotent while PATCH is not.

Now let us take a look at a real example. When I do POST to /users with the data {username: 'skwee357', email: '[email protected]'} and the server is capable of creating a resource, it will respond with 201 and resource location (lets assume /users/1) and any next call to GET /users/1 will return {id: 1, username: 'skwee357', email: '[email protected]'}.

Now let us say I want to modify my email. Email modification is considered "a set of changes" and therefore I should PATCH /users/1 with "patch document". In my case it would be the json document: {email: '[email protected]'}. The server then returns 200 (assuming permission are ok). This brings me to first question:

  • PATCH is NOT idempotent. It said so in RFC 2616 and RFC 5789. However if I issue the same PATCH request (with my new email), I will get the same resource state (with my email being modified to the requested value). Why is PATCH not then idempotent?

PATCH is a relatively new verb (RFC introduced in March 2010), and it comes to solve the problem of "patching" or modifying a set of fields. Before PATCH was introduced, everybody used PUT to update resources. But after PATCH was introduced, it leaves me confused about what PUT is used for. And this brings me to my second (and the main) question:

  • 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?

解决方案

NOTE: When I first spent time reading about REST, idempotence was a confusing concept to try to get right. I still didn't get it quite right in my original answer, as further comments (and Jason Hoetger's answer) have shown. For a while, I have resisted updating this answer extensively, to avoid effectively plagiarizing Jason, but I'm editing it now because, well, I was asked to (in the comments).

After reading my answer, I suggest you also read Jason Hoetger's excellent answer to this question, and I will try to make my answer better without simply stealing from Jason.

Why is PUT idempotent?

As you noted in your RFC 2616 citation, PUT is considered idempotent. When you PUT a resource, these two assumptions are in play:

  1. You are referring to an entity, not to a collection.

  2. The entity you are supplying is complete (the entire entity).

Let's look at one of your examples.

{ "username": "skwee357", "email": "[email protected]" }

If you POST this document to /users, as you suggest, then you might get back an entity such as

## /users/1

{
    "username": "skwee357",
    "email": "[email protected]"
}

If you want to modify this entity later, you choose between PUT and PATCH. A PUT might look like this:

PUT /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // new email address
}

You can accomplish the same using PATCH. That might look like this:

PATCH /users/1
{
    "email": "[email protected]"       // new email address
}

You'll notice a difference right away between these two. The PUT included all of the parameters on this user, but PATCH only included the one that was being modified (email).

When using PUT, it is assumed that you are sending the complete entity, and that complete entity replaces any existing entity at that URI. In the above example, the PUT and PATCH accomplish the same goal: they both change this user's email address. But PUT handles it by replacing the entire entity, while PATCH only updates the fields that were supplied, leaving the others alone.

Since PUT requests include the entire entity, if you issue the same request repeatedly, it should always have the same outcome (the data you sent is now the entire data of the entity). Therefore PUT is idempotent.

Using PUT wrong

What happens if you use the above PATCH data in a PUT request?

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"
}
PUT /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "email": "[email protected]"      // new email address... and nothing else!
}

(I'm assuming for the purposes of this question that the server doesn't have any specific required fields, and would allow this to happen... that may not be the case in reality.)

Since we used PUT, but only supplied email, now that's the only thing in this entity. This has resulted in data loss.

This example is here for illustrative purposes -- don't ever actually do this. This PUT request is technically idempotent, but that doesn't mean it isn't a terrible, broken idea.

How can PATCH be idempotent?

In the above example, PATCH was idempotent. You made a change, but if you made the same change again and again, it would always give back the same result: you changed the email address to the new value.

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // email address was changed
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address... again
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // nothing changed since last GET
}

My original example, fixed for accuracy

I originally had examples that I thought were showing non-idempotency, but they were misleading / incorrect. I am going to keep the examples, but use them to illustrate a different thing: that multiple PATCH documents against the same entity, modifying different attributes, do not make the PATCHes non-idempotent.

Let's say that at some past time, a user was added. This is the state that you are starting from.

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

After a PATCH, you have a modified entity:

PATCH /users/1
{"email": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",    // the email changed, yay!
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

If you then repeatedly apply your PATCH, you will continue to get the same result: the email was changed to the new value. A goes in, A comes out, therefore this is idempotent.

An hour later, after you have gone to make some coffee and take a break, someone else comes along with their own PATCH. It seems the Post Office has been making some changes.

PATCH /users/1
{"zip": "12345"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",  // still the new email you set
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"                      // and this change as well
}

Since this PATCH from the post office doesn't concern itself with email, only zip code, if it is repeatedly applied, it will also get the same result: the zip code is set to the new value. A goes in, A comes out, therefore this is also idempotent.

The next day, you decide to send your PATCH again.

PATCH /users/1
{"email": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"
}

Your patch has the same effect it had yesterday: it set the email address. A went in, A came out, therefore this is idempotent as well.

What I got wrong in my original answer

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.

So when is PATCH not idempotent, then?

For a full treatment of this question, I again refer you to Jason Hoetger's answer. I'm just going to leave it at that, because I honestly don't think I can answer this part better than he already has.

这篇关于在REST API实际场景中使用PUT与PATCH方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-12 04:39