REST 中的 PUT 与 POST

根据 HTTP / 1.1 规范:

POST方法用于请求源服务器接受请求中包含的实体作为Request-Line Request-URI标识的资源的新下属

换句话说, POST用于创建

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

也就是说, PUT用于创建或更新

那么,应该使用哪一个来创建资源?还是需要同时支持两者?

答案

总体:

PUT 和 POST 均可用于创建。

您必须问 “您要对该动作执行什么操作?” 来区分您应该使用的内容。假设您正在设计一个用于询问问题的 API。如果要使用 POST,则可以对一系列问题进行处理。如果要使用 PUT,则可以对特定问题进行操作。

两者都可以使用,因此在 RESTful 设计中应该使用哪一个:

您不需要同时支持 PUT 和 POST。

使用哪种取决于您自己。但请记住,请根据您在请求中引用的对象来使用正确的对象。

一些注意事项:

  • 您是否命名您明确创建的 URL 对象,还是由服务器决定?如果命名,请使用 PUT。如果让服务器决定,则使用 POST。
  • PUT 是幂等的,因此,如果将一个对象两次放置,则不会起作用。这是一个很好的属性,所以我会尽可能使用 PUT。
  • 您可以使用具有相同对象 URL 的 PUT 更新或创建资源
  • 使用 POST,您可以同时收到 2 个请求,以对 URL 进行修改,它们可能会更新对象的不同部分。

一个例子:

我写了以下内容作为对此的另一个答案

开机自检:

用于修改和更新资源

POST /questions/<existing_question> HTTP/1.1
Host: www.example.com/

请注意以下是错误:

POST /questions/<new_question> HTTP/1.1
Host: www.example.com/

如果尚未创建 URL,则在指定名称时不应使用 POST 来创建 URL。这将导致 “找不到资源” 错误,因为<new_question>还不存在。您应该先在服务器上放置<new_question>资源。

您可以执行以下操作来使用 POST 创建资源:

POST /questions HTTP/1.1
Host: www.example.com/

请注意,在这种情况下,未指定资源名称,新对象的 URL 路径将返回给您。

放:

用于创建资源或覆盖它。当您指定资源时,新的 URL。

对于新资源:

PUT /questions/<new_question> HTTP/1.1
Host: www.example.com/

覆盖现有资源:

PUT /questions/<existing_question> HTTP/1.1
Host: www.example.com/

此外,更简洁一点的是RFC 7231 第 4.3.4 节 PUT状态(添加了强调),

4.3.4。放

PUT 方法请求created目标资源的状态或replaced为请求消息有效负载中包含的表示形式所定义的状态。

您可以在网络上找到这样的断言:

两者都不对。


更好的是根据操作的幂等性在 PUT 和 POST 之间进行选择。

PUT意味着放置资源 - 用另一件事完全替换给定 URL 上可用的任何内容。根据定义,PUT 是幂等的。随意执行多次,结果是相同的。 x=5是幂等的。您可以放置一个资源,无论它先前是否存在(例如,创建或更新)!

POST更新资源,添加辅助资源或引起更改。 POST 不是幂等的,因为x++不是幂等的。


通过此参数,PUT 用于在您知道要创建的事物的 URL 时进行创建。当您知道要创建的事物类别的 “工厂” 或管理者的 URL 时,可以使用 POST 进行创建。

所以:

POST /expense-report

要么:

PUT  /expense-report/10929
  • POST到 URL 服务器定义的 URL 上创建子资源
  • 放置到 URL 客户端定义的 URL 上完整地创建 / 替换资源
  • 对 URL 进行PATCH 更新会在该客户端定义的 URL 上更新资源的一部分

PUT 和 POST 的相关规范是RFC 2616§9.5ff。

POST 创建一个子资源 ,因此 POST 到/items创建一个位于/items资源下的资源。例如。 /items/1 。两次发送相同的邮包将创建两个资源。

PUT用于在客户端已知URL处创建或替换资源。

因此:在创建资源之前, PUT仅是 CREATE 的候选对象,在此客户端客户已经知道该 URL。例如。 /blogs/nigel/entry/when_to_use_post_vs_put作为标题用作资源密钥

如果已存在,则PUT将替换已知 URL 处的资源,因此两次发送相同的请求无效。换句话说, 对 PUT 的调用是幂等的

RFC 的内容如下:

POST 和 PUT 请求之间的根本区别体现在 Request-URI 的不同含义上。 POST 请求中的 URI 标识将处理封闭实体的资源。该资源可能是一个数据接受过程,某个其他协议的网关或一个接受注释的单独实体。相比之下,PUT 请求中的 URI 标识请求中包含的实体 - 用户代理知道要使用的 URI,并且服务器绝不能尝试将请求应用于其他资源。如果服务器希望将请求应用于其他 URI,

注意: PUT 主要用于更新资源(通过整体替换它们),但是最近有一种趋势是使用 PATCH 来更新现有资源,因为 PUT 指定它将替换整个资源。 RFC 5789。

2018 年更新 :可以避免使用 PUT。请参阅“没有 PUT 的 REST”

使用 “不带 PUT 的 REST” 技术,想法是迫使消费者发布新的 “统一” 请求资源。如前所述,更改客户的邮寄地址是对新 “ChangeOfAddress” 资源的 POST,而不是具有不同邮寄地址字段值的 “Customer” 资源的 PUT。

摘自REST API 设计 - Thoughtworks 的 Prakash Subramaniam 编写的资源建模

这迫使 API 避免多个客户端更新单个资源的状态转换问题,并与事件源和 CQRS 更好地匹配。当工作异步完成时,发布转换并等待其应用似乎是适当的。

摘要:

创建:

可以通过以下方式同时使用 PUT 或 POST 执行:

在 / resources URI 或collection下使用newResourceId作为标识符创建THE新资源。

PUT /resources/<newResourceId> HTTP/1.1

开机自检

在 / resources URI 或collection下创建一个新资源。通常,标识符由服务器返回。

POST /resources HTTP/1.1

更新:

只能通过以下方式通过 PUT 执行:

在 / resources URI 或collection下,以现存资源为标识更新资源。

PUT /resources/<existingResourceId> HTTP/1.1

说明:

当与 REST 和 URI 一般打交道,你必须在左边具体权利 一般泛型通常称为集合 ,更具体的项目称为资源 。请注意, 资源可以包含集合

例子:

<- 通用 - 特定 ->

URI: website.com/users/john
website.com  - whole site
users        - collection of users
john         - item of the collection, or a resource

URI:website.com/users/john/posts/23
website.com  - whole site
users        - collection of users
john         - item of the collection, or a resource
posts        - collection of posts from john
23           - post from john with identifier 23, also a resource

使用 POST 时,您总是引用collection ,因此无论何时您说:

POST /users HTTP/1.1

您正在将新用户发布到用户 集合

如果继续尝试以下操作:

POST /users/john HTTP/1.1

它会起作用,但从语义上讲,您是在向用户 集合下的john 集合添加资源。

使用 PUT 后,您将引用资源或单个项目,可能在collection 中 。所以当你说:

PUT /users/john HTTP/1.1

您要告诉服务器更新,或者在用户 集合下创建john 资源 (如果不存在)。

规格:

让我强调一下规范的一些重要部分:

开机自检

POST方法用于请求源服务器接受请求中包含的实体作为请求行中 Request-URI 标识的资源的下属

因此,在collection上创建一个新资源

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

因此,根据资源的存在来创建或更新。

参考:

我想补充一下我的 “务实” 建议。当您知道可以检索保存的对象的 “id” 时,请使用 PUT。如果您需要返回数据库生成的 ID 以供您以后进行查找或更新,则使用 PUT 不能很好地工作。

因此:要保存现有用户,或保存客户端生成 ID 的用户,并已验证 ID 是唯一的:

PUT /user/12345 HTTP/1.1  <-- create the user providing the id 12345
Host: mydomain.com

GET /user/12345 HTTP/1.1  <-- return that user
Host: mydomain.com

否则,使用 POST 最初创建对象,然后使用 PUT 更新对象:

POST /user HTTP/1.1   <--- create the user, server returns 12345
Host: mydomain.com

PUT /user/12345 HTTP/1.1  <--- update the user
Host: mydomain.com

POST意思是 “新建”,如 “此处是创建用户的输入,请为我创建”。

PUT意思是 “插入,如果已经存在则替换”,如 “这是用户 5 的数据” 中所述。

POST到 example.com/users,因为你不知道URL的用户呢,你想服务器创建它。

由于您要替换 / 创建特定用户,因此将其PUT example.com/users/id。

使用相同的数据两次发布意味着创建两个具有不同 ID 的相同用户。使用相同的数据两次输入将首先创建用户,第二次将其更新为相同状态(无更改)。由于无论执行多少次PUT之后,您最终都会处于相同的状态,因此每次都被称为 “同等有效”- 等幂。这对于自动重试请求很有用。按下浏览器上的 “后退” 按钮时,不再 “确定要重新发送”。

一般建议是,当您需要服务器控制资源的URL生成时,请使用POST 。否则使用PUT 。与POST首选PUT

使用 POST 创建,并使用 PUT 更新。无论如何,这就是 Ruby on Rails 的做法。

PUT    /items/1      #=> update
POST   /items        #=> create

两者都用于客户端到服务器之间的数据传输,但是它们之间存在细微的差异,它们是:

在此处输入图片说明

比喻:

  • 放置 ,即放在原处。
  • 邮局以邮寄方式发送邮件。

在此处输入图片说明

社交媒体 / 网络类比:

  • 发表于社交媒体:当我们发布的消息,它创造了新的岗位。
  • 放入 (即编辑)我们已经发布的消息。

REST 是一个非常高级的概念。实际上,它甚至根本没有提到 HTTP!

如果您对如何在 HTTP 中实现 REST 存有疑问,可以随时查看Atom 发布协议(AtomPub)规范。 AtomPub 是使用 HTTP 编写 RESTful Web 服务的标准,该标准是由许多 HTTP 和 REST 专家开发的,并由 REST 的发明者和 HTTP 的(共同)发明者 Roy Fielding 提出了一些建议。

实际上,您甚至可以直接使用 AtomPub。尽管它来自博客社区,但绝不限于博客:它是用于通过 HTTP 与任意资源(嵌套)的任意资源的 RESTful 交互的通用协议。如果您可以将应用程序表示为资源的嵌套集合,则可以只使用 AtomPub,而不必担心要使用 PUT 还是 POST,要返回的 HTTP 状态代码以及所有这些详细信息。

这是 AtomPub 关于资源创建的内容(第 9.2 节):

要将成员添加到集合,客户端将 POST 请求发送到集合的 URI。

是否使用 PUT 或 POST 在具有 HTTP + REST API 的服务器上创建资源的决定取决于谁拥有 URL 结构。使客户知道或参与定义 URL 结构是类似于 SOA 产生的不良耦合的不必要耦合。转义类型是 REST 如此流行的原因。因此,使用的正确方法是 POST。该规则有一些例外,当客户端希望保留对其部署的资源的位置结构的控制权时,就会发生这些例外。这种情况很少见,很可能意味着其他地方出了问题。

在这一点上,有人会争辩说,如果使用RESTful-URL ,则客户端确实知道资源的 URL,因此可以接受 PUT。毕竟,这就是为什么规范的,规范化的,Ruby on Rails,Django URL 很重要的原因,请看一下 Twitter API…… 等等等等。那些人需要了解没有 Restful-URL 之类的东西Roy Fielding 自己指出

REST API 不得定义固定的资源名称或层次结构(客户端和服务器的明显结合)。服务器必须具有控制自己的名称空间的自由。相反,允许服务器通过在媒体类型和链接关系中定义那些指令来指导客户端如何构造适当的 URI(例如以 HTML 表单和 URI 模板完成)。 [此处失败表示客户端由于带外信息(例如,特定于域的标准)而采用了一种资源结构,该特定于域的标准在数据方面等同于 RPC 的功能耦合]。

http://roy.gbiv.com/untangled/2008/rest-apis-must-be - 超文本驱动

RESTful-URL的概念实际上违反了 REST,因为服务器负责 URL 结构,应该自由决定如何使用它来避免耦合。如果这使您感到困惑,您将了解自我发现对 API 设计的重要性。

使用 POST 创建资源需要进行设计考虑,因为 POST 不是幂等的。这意味着多次重复 POST 并不能保证每次都具有相同的行为。 这使人们在不应该使用 PUT 来创建资源时感到恐惧。他们知道这是错误的(POST 是用于 CREATE 的),但他们还是这样做,因为他们不知道如何解决此问题。在以下情况下证明了这种担忧:

  1. 客户端将新资源发布到服务器。
  2. 服务器处理该请求并发送响应。
  3. 客户端永远不会收到响应。
  4. 服务器未意识到客户端尚未收到响应。
  5. 客户端没有资源的 URL(因此,PUT 不是一个选项),并重复 POST。
  6. POST 不是幂等的,服务器…

第 6 步是人们通常对要做的事情感到困惑的地方。但是,没有理由创建一个解决方案来解决此问题。相反,可以按照RFC 2616 中的指定使用 HTTP,并且服务器会回复:

10.4.10 409 冲突

由于与资源的当前状态存在冲突,因此无法完成请求。仅在预期用户可能能够解决冲突并重新提交请求的情况下才允许使用此代码。响应正文应包含足够的内容

供用户识别冲突源的信息。理想情况下,响应实体应包括足够的信息供用户或用户代理解决问题。但是,这可能是不可能的,也不是必需的。

响应 PUT 请求最有可能发生冲突。例如,如果正在使用版本控制,并且正在 PUT 的实体包括对资源的更改,该更改与先前的(第三方)请求所做的更改冲突,则服务器可能会使用 409 响应来指示它无法完成请求。在这种情况下,响应实体可能会以响应 Content-Type 定义的格式包含两个版本之间差异的列表。

回复状态码为 409 冲突是正确的方法,因为

  • 对具有与系统中已有资源匹配的 ID 的数据执行 POST 是 “与资源的当前状态冲突”。
  • 由于重要的部分是让客户端了解服务器拥有的资源并采取适当的措施。这是一个 “期望用户能够解决冲突并重新提交请求的情况”。
  • 包含具有冲突 ID 的资源 URL 和资源的适当前提条件的响应将提供 “足够的信息供用户或用户代理解决问题”,这是 RFC 2616 的理想情况。

基于 RFC 7231 版本的更新以替换 2616

RFC 7231旨在替代 2616,并在第 4.3.3 节中描述了 POST 的以下可能响应

如果处理 POST 的结果等同于现有资源的表示,则源服务器可以通过在位置字段中发送带有现有资源标识符的 303(见其他)响应,将用户代理重定向到该资源。这样做的好处是为用户代理提供了一个资源标识符,并通过一种更适合共享缓存的方法来传输表示,尽管如果用户代理尚未缓存表示,则要付出额外的请求。

现在可能很想在重复 POST 的情况下简单地返回 303。但是,事实恰恰相反。仅当多个创建请求(创建不同的资源)返回相同的内容时,才返回 303。一个示例是 “谢谢您提交请求消息”,客户端无需每次都重新下载。 RFC 7231 在第 4.2.2 节中仍然保持 POST 不具有幂等性,并继续保持 POST 应该用于创建。

有关此的更多信息,请阅读本文