HTTP REST:如果请求是幂等的,但资源一旦插入就无法更改,则应使用 PUT 或 POST



>想象一个HTTP REST端点,其中插入了一个资源,并且资源被理解为一条"消息"。 每条消息都由唯一标识符标识,例如某种 GUID 值。不能复制相同的消息。

现在,在很多情况下,这适用于PUT动词,因为它是幂等的。 但是,请考虑以下方案:

1. The sender sends this message to the receiver:
{
"id": 123,
"text": "original text"
}
2. Now the receiver has this value for the message stored in its database:
{
"id": 123,
"text": "original text"
}
3. For whatever reason, the sender tries to send the same message again, but with amended
text:
{
"id": 123,
"text": "amended text"
}
4. The receiver receives that as well, but since the id field is the same as before, no
action is taken, and this is what the receiver still has in its database:
{
"id": 123,
"text": "original text"
}

接收方行为的原因是每个不同的消息都由其id字段唯一标识,并且如果以相同的id发送另一条消息,则只是将其视为重复。 此外,尝试像这样更改邮件的内容并使用相同的 ID 重新发送邮件是发件人端的无效行为

所以从技术上讲,这是幂等的,通常倾向于PUT. 但是,此处不允许在任何上下文中进行更新,只需插入即可。 那么我们选择PUT还是POST,或者它甚至重要吗?PUT应该是资源的完整表示形式,但如果只是简单地丢弃它,是否仍然可以返回2xx响应?

出于此问题的目的,假设使用的路由始终是<host>/.../message形式,而不是<host>/.../message/{id}形式。 在这种情况下,我想知道是否仅限于<host>/.../message路线方案是否自动意味着应该使用POST

现在,在很多情况下,这将适用于 POST 动词,因为它是幂等的。

这可能是您的错字,但POST不是幂等的。然而PUT是这样。

PUT 应该是资源的完整表示形式,但如果只是简单地丢弃它,是否仍然可以返回 2xx 响应?

完全没问题。您请求具有给定 ID 的资源具有给定状态,并且在发回响应时将如此。

那么我们选择PUT还是POST,或者它甚至重要吗?

我会说好的选择是

PUT /messages/{id}, 如果 id 是新的,则为 201,如果消息与现有消息具有相同的文本,则为
  • 204,如果文本与现有消息不同,则为 4xx
  • POST /messages, 201 如果 id 是新的,则为 4xx 如果 id 已经存在

通过PUT,客户端可以看到"哦,2xx,消息已传递"或"4xx,我搞砸了"。但是您必须有一些机制(可能是指纹)来快速检查文本是否与初始消息相同。
使用POST,客户端需要检查错误响应,以查看是否需要修复某些内容并重试,或者消息是否已传递。如果我们从现有消息的POST返回 2xx(202 除外),大多数客户端会将其解释为已发送新的(重复)消息。

假设使用的路由始终是形式<host>/.../message,而不是形式<host>/.../message/{id}

一个不幸的要求,在这种情况下,我会如上所述使用POST。从技术上讲,我们可以在该路径中使用PUT,但通常这会表明我们想要创建/替换整个消息历史记录(您可以围绕它进行记录,但是不执行它们看起来应该的操作通常不被视为 RESTfull)。

想象一个HTTP REST端点,...

根据菲尔丁本人的说法

没有 REST 端点这样的东西。有资源。可数无限的资源集,仅受 URL 长度限制的约束。(来源)

所以从技术上讲,这是幂等的,通常倾向于 PUT 或 PATCH ......

幂等性是客户端在临时网络问题方面的一个重要属性,它允许它在合理的时间内未收到响应时自动重新发送请求。在这种情况下,客户端根本不知道初始请求是否收到了服务器,或者只是响应丢失了。幂等性与您只允许写入资源一次的需求无关。除此之外,默认情况下,PATCH不是幂等的。幂等只是意味着处理请求始终会产生相同的结果,无论您将该请求发送到服务器多少次。

每条消息都由唯一标识符标识,例如某种 GUID 值

。每个不同的消息都由其 ID 字段唯一标识,如果发送另一条消息具有相同的 ID,则只是将其视为重复消息。

资源始终通过其 URI 唯一标识,因为 URI 本身只能引用一个资源。但是,单个资源可能具有多个 URI,即一个与另一个没有可用查询参数的 URI。URI 是统一资源标识符的缩写,其中统一表示在所有情况下和始终保持不变。在我看来,这使得对 id字段的要求是多余的。

据我理解您的问题,消息的实际文本并未定义消息是否重复。所以基本上

PUT /messages/eaff31bd-5291-4287-a7dd-fbe5b1e47b67 HTTP/1.1
Host: www.acme.com
Content-Type: application/json; charset="utf-8"
...
{
"text": "original text"
}

PUT /messages/eb1db214-d231-4a50-916c-8de2d64c7d3a HTTP/1.1
Host: www.acme.com
Content-Type: application/json; charset="utf-8"
...
{
"text": "original text"
}

是两个不同消息的请求,因为它们针对不同的资源,而您不想允许类似

PUT /messages/eaff31bd-5291-4287-a7dd-fbe5b1e47b67 HTTP/1.1
Host: www.acme.com
Content-Type: application/json; charset="utf-8"
...
{
"text": "amended text"
}

因为这将更新现有消息。

在这种情况下,我会通过不支持消息来完全禁用消息PUT,同时仅允许通过POST创建新消息。请求/响应示例可能如下所示:

POST /messages HTTP/1.1
Host: www.acme.com
Content-Type: application/json; charset="utf-8"
Accept: application/json
...
{
"text": "Some other text"
}

这可能会产生如下响应:

HTTP/1.1 201 Created
Content-Type: application/json; charset="utf-8"
Location: http://www.acme.com/messages/740177d2-1de9-41bd-bd5b-6a52f39bf227
Etag: 33a64df551425fcc55e4d42a148795d9f25f89d4
{
"text": "some other text"
}

尝试通过以下方式更新此类资源

PUT /messages/740177d2-1de9-41bd-bd5b-6a52f39bf227 HTTP/1.1
Host: www.acme.com
Content-Type: application/json; charset="utf-8"
Etag: 33a64df551425fcc55e4d42a148795d9f25f89d4
{
"text": "updated text"
}

应导致

HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD

响应,指出服务器知道指示的 PUT 操作,但不支持对目标资源执行该操作。强制Allow标头进一步向客户端介绍该资源支持的有效 HTTP 操作。

出于此问题的目的,假设使用的路由始终是/.../message 的形式,而不是/.../message/{id} 的形式。在这种情况下,我想知道是否仅限于/.../消息路由方案是否自动意味着应该使用 POST。

在这种情况下,除非您PUT请求包含在处理请求后应可用的所有消息,否则您不会选择此选项。PUT 的语义定义为将存储在目标资源上的当前表示形式替换为请求有效负载中提供的表示形式。因此,如果您将 PUT 请求发送到"集合资源",则从技术上讲,您将用请求中定义的消息替换当前可用的所有消息。

最新更新