用现实生活中的例子把PUT与PATCH结合起来

首先,一些定义:

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不是。

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

现在让我们说我想修改我的电子邮件。 电子邮件修改被视为“一系列变更”,因此我应该使用“修补程序文档”将PATCH /users/1 。 在我的情况下,它将是一个JSON {email: 'skwee357@newdomain.com'} 。 服务器然后返回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是没有意义的。 我错了吗?

  • 注意 :当我第一次花时间阅读REST时,idempotence是一个混淆的概念,试图找对。 我的原始答案仍然没有完全正确,因为进一步的评论(和Jason Hoetger的回答)已经表明。 有一段时间,我拒绝广泛地更新这个答案,以避免有效地剽窃贾森,但我现在正在编辑它,因为我被要求(在评论中)。

    在阅读我的答案后,我建议你也阅读Jason Hoetger对这个问题的出色答案,并且我会尽量让自己的答案更好,而不是简单地从Jason那里偷窃。

    为什么PUT是幂等的?

    正如您在RFC 2616引用中指出的那样,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 )。

    当使用PUT时,假定你正在发送完整的实体,并且该完整的实体将替换该URI处的任何现有实体。 在上面的例子中,PUT和PATCH完成相同的目标:它们都改变这个用户的电子邮件地址。 但是PUT通过替换整个实体来处理它,而PATCH只更新所提供的字段,而让其他人独立。

    由于PUT请求包含整个实体,如果您重复发出相同的请求,它应始终具有相同的结果(您发送的数据现在是实体的整个数据)。 因此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如何具有幂等性?

    在上面的例子中,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中发送的电子邮件现在是实体上的电子邮件地址。

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

    为了全面处理这个问题,我再次向您推荐Jason Hoetger的答案。 我只是想放弃这一点,因为我真的不认为我能比他已经有更好的回答这个部分。


    虽然Dan Lowe的出色答案非常全面地回答了OP关于PUT和PATCH之间差异的问题,但它对PATCH为什么不是幂等的问题的回答并不完全正确。

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

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

    在更易于访问的语言中,可以将幂等PATCH定义为:在用补丁文档修补资源之后,所有后续PATCH调用具有相同补丁文档的相同资源不会改变资源。

    相反,非幂等运算是f(f(x))!= f(x),其中对于PATCH可以表示为:在用补丁文档修补资源之后,随后的PATCH调用相同的资源相同的补丁文件改变资源。

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

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

    正如在OP的例子中,假设服务器允许PATCHING /用户,而不是PATCHing / 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请求之间发生冲突的不良结果。

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


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

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

    HTTP RFC指定PUT必须将全新的资源表示作为请求实体。 这意味着如果仅提供某些属性,则应删除(即设置为空)。

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

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

    这将有效地更新电子邮件。 PUT可能不太有效的原因是你唯一真正修改一个字段并包含用户名是没用的。 下一个例子显示了差异。

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

    现在,如果PUT是根据规范设计的,那么PUT会将用户名设置为null,并且您将返回以下内容。

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

    当您使用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“应该”将缺失值设置为空。 这可能不是'最正确'的方法,但祝你好运,编码完美。

    链接地址: http://www.djcxy.com/p/1087.html

    上一篇: PUT vs PATCH with real life examples

    下一篇: How are parameters sent in an HTTP POST request?