为什么在实现RestFul服务时,HTTP方法PUT应该是幂等的而不是POST?

9

有许多互联网资源讨论PUT vs POST。但我不明白这会如何影响Java实现或在RestFul服务下面完成的后端实现?我查看的链接如下:

https://www.keycdn.com/support/put-vs-post/

https://spring.io/understanding/REST#post

https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html

http://javarevisited.blogspot.com/2016/10/difference-between-put-and-post-in-restful-web-service.html

例如,假设有一个与地址相关的RestFul webservice。 因此,POST /addresses 将更新地址,而 PUT /addresses/1 将创建地址。 那么,HTTP方法PUT和POST如何控制webservice代码在幕后执行的内容?
PUT /addresses/1 

可能会在数据库中创建多个相同地址的条目。

所以我的问题是,为什么幂等行为与HTTP方法相关联?

您将如何使用特定的HTTP方法控制幂等行为?或者这只是一个指导方针或标准实践建议?

我不是在寻找关于什么是幂等行为的解释,而是想知道是什么让我们将这些HTTP方法打上标签?


1
顺便提一下:所以POST将完成更新地址的工作,而PUT将创建新地址的工作 - 实际上是相反的。PUT用于更新,POST用于创建。 - BackSlash
2
@BackSlash PUT 的语义是应该用请求的有效负载提供的内容替换调用端点的当前表示。如果之前没有可用的,则 PUT 可以创建一个,这会导致实际资源的创建。通过让实现者定义接收到的有效负载将执行什么操作,POST 请求的语义甚至更加模糊。因此,“PUT 更新,POST 创建”在一般情况下并不正确,只是对这些操作的过度简化视图。 - Roman Vottner
6个回答

5
这是HTTP特定的。正如您提供的RFC所述,https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html(请参见本答案底部的最新RFC链接)。它并不被描述为REST的一部分:https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm 现在您写道:
“我不是要解释什么是幂等行为,而是想知道是什么让我们将这些HTTP方法标记起来?”
幂等操作始终具有相同的结果(我知道您知道),但结果不同于HTTP响应。从HTTP的角度来看,显然可以使用任何方法进行多个请求,甚至所有相同的参数都可能有不同的响应(即时间戳)。因此,它们实际上可能会有所不同。
不应改变的是操作的结果。因此,多次调用PUT /addresses/1 不应创建多个地址。
正如您所看到的,它被称为PUT而不是CREATE,有其原因。如果不存在,则可能创建资源。如果存在,则可以使用新版本覆盖它(更新),如果完全相同,则在服务器上不执行任何操作,并且结果与如果是相同请求重复(因为可能是重复的先前请求,因为客户端未收到响应)相同。
与SQL相比,PUT更像INSERT OR UPDATE而不仅仅是INSERTUPDATE
“那么我的问题是,为什么幂等行为与HTTP方法相关联?”
这与HTTP方法相关,因此某些服务(代理)知道在请求失败的情况下,它们可以安全地(不是在安全的HTTP方法方面,而是在幂等性方面)重复尝试它们。
“您将如何通过使用特定的HTTP方法来控制幂等行为?”
我不确定您正在问什么。但:
  • GET,HEAD只返回数据,不会改变任何内容(也许只有一些日志、统计信息、元数据?),因此它是安全的和幂等的。
  • POST,PATCH可以做任何事情,它既不安全也不幂等
  • PUT,DELETE-不安全(它们会改变数据),但它们是幂等的,因此重复它们是“安全”的。
这基本上意味着安全的方法可以由代理、缓存、Web爬虫等进行安全地制作,而不会改变任何内容。幂等可以被软件重复,它不会改变结果。
“还是说这只是建议或标准惯例?”

这是“标准”的。也许RFC目前还不是标准,但它最终会成为一个标准,我们没有其他可遵循的(并且应该遵循)。

编辑:

由于上述RFC已经过时,因此以下是关于该主题的当前RFC的一些参考:

感谢Roman Vottner提供的建议。


2
请使用最新版本的HTTP 1.1规范:消息语法和路由语义和内容条件请求范围请求缓存身份验证 - Roman Vottner

3
所以我的问题是,为什么幂等行为与HTTP方法相关联?我不是在寻找什么是幂等行为的解释,而是我们是如何将这些HTTP方法标记的? 这样,交换消息的通用、领域无关的参与者可以做出有用的贡献。 RFC 7231在其idempotent的定义中指出了一个具体的例子。 幂等方法之所以与众不同,是因为如果客户端在能够读取服务器响应之前发生通信故障,则可以自动重复请求。例如,如果客户端发送PUT请求并且在接收到任何响应之前关闭了底层连接,则客户端可以建立新连接并重试幂等请求。它知道重复请求将具有相同的预期效果,即使原始请求成功,响应也可能不同。
客户端或中间件不需要了解您的定制API或其底层实现就可以这样操作。所有必要的信息都在规范中(RFC 7231对PUTidempotent的定义),以及服务器宣布支持PUT的资源中。
请注意,PUT需要幂等请求处理,但对于POST并非禁止。具有幂等POST请求处理程序甚至具有safe的处理程序并不是错误的。但是,仅具有元数据和HTTP规范的通用组件将不知道或发现POST请求处理程序是否幂等。

我不明白这会影响Java实现或为RestFul服务所做的后端实现吗?

没有什么魔法;使用PUT并不会自动更改服务的底层实现;技术上讲,它甚至不会限制底层实现。它所做的是清楚地记录责任所在。

这类似于Fielding 2002年的观察关于GET是安全的

HTTP不要求GET的结果必须是安全的。它要求操作的语义是安全的,因此如果由此导致任何损失(顺便说一句,钱被认为是财产),那么这是实现的错误,而不是接口或接口使用者的错误。

重要的一点要认识到,就是在HTTP中,并不存在“资源层次结构”。例如,/addresses/addresses/1之间并没有任何关系——对其中一个的消息不会影响缓存另一个的表示。认为/addresses是一个“集合”,而/addresses/1是“/addresses集合中的项”是实现细节,仅供源服务器使用。
(过去的情况是,POST的语义将指向下级资源,例如请参见RFC 1945;但即使那时下级标识符的拼写也不受约束。)

我的意思是PUT /employee是否可行或者必须是PUT/employee/<employee-id>?

PUT /employee的语义是“用我提供的表示替换/employee的当前表示”。如果/employee是一个集合的表示,则通过PUT传递集合的新表示来修改该集合是完全可以的。
GET /collection

200 OK

{/collection/1, collection/2}

PUT /collection

{/collection/1, /collection/2, /collection/3}

200 OK

GET /collection

200 OK

{/collection/1, /collection/2, /collection/3}

PUT /collection

{/collection/4}

200 OK

GET /collection

200 OK

{/collection/4}

如果这不是你想要的;如果你想要“追加”到集合中,而不是替换整个表示,则对于集合应用PUT具有错误的语义。你可以把项表示放到项资源中,或者在集合上使用其他方法(如POST或PATCH)。
GET /collection

200 OK

{/collection/1, collection/2}

PUT /collection/3

200 OK

GET /collection

200 OK

{/collection/1, /collection/2, /collection/3}

PATCH /collection

{ op: add, path: /4, ... }

200 OK

GET /collection

200 OK

{/collection/1, /collection/2, /collection/3, /collection/4 }

你能否通过使用一个例子,如Employee的RestFul服务,来更加详细地解释你的回答?我并不期望代码,但是当你执行POST/employee和PUT/employee/2操作时,预计会发生什么?另外,PUT是否应该事先了解资源?我的意思是,PUT/employee是否可接受,还是必须是PUT/employee/<employee-id>? - Sam
@Sam 理想情况下,创建表示形式的文档类型决定了允许什么和不允许什么。想想 HTML。它定义了哪些标签可用以及语义和提示客户端如何执行进一步的请求。当然,服务器有权将接收到的表示形式转换为当前内容的表示格式或拒绝请求。遵循 REST 模型的服务器将提示客户端如何执行进一步的操作(例如,HTML 使用表单或类似方法)。 - Roman Vottner
1
@Sam 应该事先了解资源: 基本上,客户端不应假定某些资源具有某种类型。Fielding 将此称为 typed resources。应使用内容类型协商。如果客户端想要向服务器发送数据,则可以事先得到服务器的指示或发送 mime 类型规范定义的任何内容,服务器将接受、拒绝或转换请求。 - Roman Vottner

1

POST通常不是幂等的,因此多次调用将创建具有不同ID的多个对象。

PUT - 在URI中给定ID,您将向数据库应用“创建或更新”查询,因此一旦资源被创建,每个后续调用都不会对后端状态产生任何影响。

即在生成新的/更新现有存储对象的方式上,后端存在明显的差异。例如,假设您正在使用MySQL和自动生成的ID:

POST将成为INSERT查询

PUT将成为INSERT ... ON DUPLICATE KEY UPDATE查询


1
一个常见的误解是HTTP方法POST、GET、PUT、DELETE直接映射到CRUD数据库功能。你不能也不应该盲目地将HTTP操作映射到与数据库相关的指令上。POST的语义完全取决于实现者,因此并不需要将新记录插入到数据库表中。它甚至可能删除一些语句或根本不执行任何操作。此外,HTTP在其核心上用于管理远程机器上的文件。从任何文档修改中推导出的任何业务逻辑都只是文档管理的副作用。 - Roman Vottner
@RomanVottner 最初的问题是关于后端实现可能有何不同,因此我举了一个例子来说明可能的区别。 - S. Pauk

1
你如何使用特定的HTTP方法来控制幂等行为?这只是一个指南或标准实践吗?更多的是关于HTTP规范,应用程序必须遵循这些规范。没有什么可以阻止你在服务器端更改行为。Web服务和Restful Web服务之间总是有所不同。考虑一些使用servlet的传统应用程序。Servlets曾经有doGet和doPost方法。由于信息嵌入在请求本身中并且未暴露给外部世界,因此始终建议使用doPost进行安全性和存储数据在服务器/数据库上。即使如此,在doGet中保存数据或在doPost中返回某些静态页面也没有任何限制,因此这一切都是关于遵循底层规范的问题。

1
所以我的问题是,为什么幂等行为与HTTP方法相关联?
因为HTTP规范这样规定:
4.2.2. 幂等方法
如果使用该方法进行多个相同请求,对服务器的预期影响与单个请求的影响相同,则将请求方法视为“幂等”。在本规范定义的请求方法中,PUT、DELETE和安全请求方法是幂等的。
(来源:RFC 7231
为什么规范会这样说呢?
因为能够区分幂等和非幂等请求对于实现基于HTTP的系统非常有用,而“方法”类型提供了一种很好的区分方式。
此外,HTTP规范的其他部分也是建立在能够区分幂等方法的基础上的,例如代理和缓存规则。
你将如何使用特定的HTTP方法来控制幂等行为?
服务器实现PUT和DELETE以具有幂等行为。如果服务器没有,则违反了HTTP规范。
这是必需的行为,而不是仅仅是指南或标准实践建议。
现在,你可以忽略要求(没有协议警察!),但如果这样做,可能会导致系统崩溃。特别是如果你需要与其他人实现的系统集成,他们可能编写客户端代码,假设如果它重播PUT或DELETE,你的服务器不会报错。
简而言之,我们使用像HTTP这样的规范来使我们的系统互通。但是,只有在每个人的代码正确实现规范时,这种策略才能正常运作。

我可以说PUT代表创建/替换,POST代表更新/创建吗? - Sam
严格来说,POST不能进行更新。而且更新和替换之间没有真正的区别。请参见https://dev59.com/HHRB5IYBdhLWcg3wa2q2#18243587。 - Stephen C

0
通常在Rest API中,我们使用以下方法:
POST - 添加数据 GET - 获取数据 PUT - 更新数据 DELETE - 删除数据
阅读下面的文章以获取更多想法。 REST API最佳实践

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接