【问题标题】:HTTP REST: If a request is idempotent, but the resource is unchangeable once inserted, should PUT or POST be used?HTTP REST:如果请求是幂等的,但资源一旦插入就不可更改,应该使用 PUT 还是 POST?
【发布时间】:2020-09-16 01:53:29
【问题描述】:

想象一个 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

【问题讨论】:

    标签: rest http httpverbs


    【解决方案1】:

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

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

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

    完全没问题。您请求具有给定 id 的资源具有给定状态,当响应被发回时,这将是正确的。

    那么我们是选择 PUT 还是 POST,或者这有关系吗?

    我想说好的选择是

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

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

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

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

    【讨论】:

    • 是的,对不起,POST 是一个错字。感谢您指出这一点。
    • 谢谢。听起来您的回答是建议 PUT <host>/.../message/{id} 在这种情况下通常是必要的,POST<host>/.../message 仅在具体情况下才需要。我理解正确吗?
    • 是的,没错。我一般会推荐PUT 方法,但由于您的限制,这将是POST 的情况。
    【解决方案2】:

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

    根据菲尔丁本人的说法

    没有 REST 端点这样的东西。有资源。一组可数无限的资源,仅受 URL 长度限制。 (Source)

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

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

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

    ...每条不同的消息都将由其 id 字段唯一标识,并且如果发送具有相同 id 的另一条消息,则仅将其视为重复消息。

    资源始终可以通过其 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
    

    response,其中指出a server knows the indicated PUT operation but does not support that operation on the target resource。强制性的 Allow 标头还可以让您的客户端了解该资源支持的有效 HTTP 操作。

    就本问题而言,假设使用的路由始终采用 /.../message 形式,而不是 /.../message/{id} 形式。在这种情况下,我想知道是否自动限制为 /.../message 路由方案意味着应该使用 POST。

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

    【讨论】:

    • 谢谢。早些时候,虽然我回去修复了“PATCH 是幂等的”的东西,因为它更接近一个错字。 (从技术上讲,我对此感到困惑,但通常意识到它不是幂等的。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-26
    • 2014-11-06
    • 2015-05-25
    • 1970-01-01
    相关资源
    最近更新 更多