HTTP REST:如果请求是幂等的,但资源一旦插入就不可更改,应该使用 PUT 还是 POST?

HTTP REST: If a request is idempotent, but the resource is unchangeable once inserted, should PUT or POST be used?

想象一个 HTTP REST 端点,其中插入了一个资源,并且该资源被理解为 "message"。每条单独的消息都由唯一标识符标识,例如某种 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 应该是资源的完整表示,但如果它只是被丢弃,是否还可以 return 一个 2xx 响应?

为了这个问题的目的,假设所使用的路线总是 <host>/.../message 的形式,而不是 <host>/.../message/{id} 的形式。在那种情况下,我想知道是否自动限制为 <host>/.../message 路由方案意味着应该使用 POST

Now, in a lot of cases, that would lend itself to the POST verb, since it's idempotent.

这可能是您的拼写错误,但 POST 不是幂等的。然而 PUT 是。

PUT is supposed to be the full representation of the resource, but if it's simply discarded, is it okay to still return a 2xx response?

完全没问题。您请求具有给定 ID 的资源具有给定状态,并且当响应被发回时这将是真实的。

So do we choose PUT or POST, or does it even matter?

我认为不错的选择是

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

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

assume that the route used is always of the form <host>/.../message, never of the form <host>/.../message/{id}

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

Imagine an HTTP REST endpoint, ...

根据菲尔丁自己的说法

There is no such thing as a REST endpoint. There are resources. A countably infinite set of resources bound only by restrictions on URL length. (Source)

So technically this is idempotent, which usually leans toward either PUT or PATCH ...

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

Each individual message is identified by a unique identifier, such as a GUID value of some sort

... each distinct message is to be uniquely identified by its id field, and that if another message is sent with the same id, it is simply considered a duplicate.

资源始终可以通过其 URI 唯一标识,因为 URI 本身只能引用一个资源。然而,单个资源可能有多个 URI,即一个与另一个没有可用查询参数的 URI。 URI是uniform resource identifier的缩写,其中uniform表示在所有情况下始终保持相同。这使得 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 创建新消息。 request/response 示例可能如下所示:

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

响应,指出 a server knows the indicated PUT operation but does not support that operation on the target resource。强制性 Allow header 进一步向您的客户介绍该资源支持的有效 HTTP 操作。

For the purposes of this question, assume that the route used is always of the form /.../message, never of the form /.../message/{id}. In that case, I am wondering if being restricted to the /.../message route scheme automatically means POST should be used.

在这种情况下,PUT 不是您的选择,除非该请求包含处理请求后应该可用的所有消息。 PUT 的语义被定义为用请求负载中提供的表示替换存储在目标资源上的当前表示。因此,如果您向“collection 资源”发送 PUT 请求,从技术上讲,您将用请求中定义的消息替换当前可用的所有消息。