【问题标题】:Consequences of POST not being idempotent (RESTful API)POST 不是幂等的后果(RESTful API)
【发布时间】:2012-12-09 03:18:38
【问题描述】:

我想知道我目前的方法是否有意义,或者是否有更好的方法。

我有多种情况,我想创建新对象并让服务器为这些对象分配一个 ID。发送 POST 请求似乎是最合适的方式。 然而,由于 POST 不是幂等的,请求可能会丢失,再次发送它可能会创建第二个对象。此外,由于 API 经常通过移动网络访问,因此丢失请求可能很常见。

因此,我决定将整个过程分为两步:

  1. 首先发送一个 POST 请求以创建一个新对象,该对象在 Location 标头中返回新对象的 URI。

  2. 第二次对提供的位置执行幂等 PUT 请求,以用数据填充新对象。如果新对象未在 24 小时内填充,服务器可能会通过某种批处理作业将其删除。

这听起来合理还是有更好的方法?

【问题讨论】:

    标签: http rest post put idempotent


    【解决方案1】:

    POST 创建优于 PUT 创建的唯一优势是服务器生成 ID。 我认为它不值得缺乏幂等性(然后需要删除重复项或空对象)。

    相反,我会在 URL 中使用带有 UUID 的 PUT。由于 UUID 生成器,您是 nearly sure,您在客户端生成的 ID 在服务器端将是唯一的。

    【讨论】:

    • 我喜欢这个主意……没想到。谢谢
    • 如果有人模拟前端(例如使用soapUI)并发送乱码代替您的UUID怎么办?
    • @PriiduNeemre 即使是“胡言乱语”,ID 也是 ID。乱码 ID 不会破坏整个系统。但是,您是对的,如果有几个“乱码”前端,它们之间就会发生 ID 冲突(但不会与其他前端发生冲突)。如果不是故意的,您可以在服务器端检查该 ID 至少遵循正确的模式。如果是故意的,您可以设置身份验证、授权和记帐以防止再次发生这种情况。
    • @Aurélien 表示您假设使用您的 API 的开发人员正在生成良好的 uuid。如果您不控制客户端,则不能保证他们不会产生更多的重复项。即使他们做得很好并创造了良好的 uuid,仍然有机会。您必须考虑由于重复的 uuid 可能意味着丢失资源。如果 2 个客户端生成相同的 uuid,则第二个客户端将覆盖第一个客户端数据。这意味着在银行系统之类的系统中,这可能非常糟糕。
    • 使用 POST 创建资源并保留 PUT 进行更新还有另一个很大的优势,因为如果您还处理并发问题,那么如果您只有一个 PUT 请求,就很难区分出什么正确的响应应该是客户端重试但没有意识到他们的第一次尝试成功并更改了版本。您不想用 309 或 412 打他们,因为实际上成功的是他们最初的请求。您需要能够从创建中识别更新,那是我一直使用幂等 POST 进行创建。
    【解决方案2】:

    这一切都取决于,首先你应该更多地谈论URI、资源和表示,而不是关心对象。

    POST 方法是为非幂等请求或具有副作用的请求而设计的,但它can be used for idempotent requests

    关于表单数据的 POST 到 /some_collection/

    normalize the natural key of your data (Eg. "lowercase" the Title field for a blog post)
    calculate a suitable hash value (Eg. simplest case is your normalized field value)
    lookup resource by hash value
    if none then
        generate a server identity, create resource
            Respond =>  "201 Created", "Location": "/some_collection/<new_id>" 
    if found but no updates should be carried out due to app logic
            Respond => 302 Found/Moved Temporarily or 303 See Other 
            (client will need to GET that resource which might include fields required for updates, like version_numbers)
    if found but updates may occur
       Respond => 307 Moved Temporarily, Location: /some_collection/<id> 
       (like a 302, but the client should use original http method and might do automatically) 
    

    一个合适的哈希函数可能像一些连接字段一样简单,或者对于大字段或值,可以使用截断的 md5 函数。详情见【哈希函数】2

    我假设你是:

    • 需要与哈希值不同的标识值
    • 使用的数据字段 因为身份无法更改

    【讨论】:

    • 这里要小心,正如@bdargan 指出的那样。 '假设用于身份的数据字段不能更改'。如果您没有一组用户无法更改的唯一数据字段,这将非常重要。
    【解决方案3】:

    您在服务器、应用程序、专用请求响应中生成 id 的方法非常好!唯一性非常重要,但是客户,就像追求者一样,会不断重复请求,直到他们成功,或者直到他们愿意接受的失败(不太可能)。所以你需要从某个地方获得独特性,你只有两个选择。客户端,如 Aurélien 所建议的 GUID,或服务器,如您所建议的。我碰巧喜欢服务器选项。关系数据库中的种子列是一种现成的唯一性来源,冲突风险为零。 2000 年左右,我读到了一篇提倡这种解决方案的文章,名为“使用 HTTP 的简单可靠消息传递”,因此这是解决实际问题的既定方法。

    阅读 REST 资料,您可能会认为一群青少年刚刚继承了猫王的豪宅。他们正在兴奋地讨论如何重新布置家具,他们对可能需要从家里带一些东西的想法感到歇斯底里。建议使用 POST,因为它在那里,而不是提出非幂等请求的问题。

    在实践中,您可能会want to make sure all unsafe requests to your api are idempotent,除了身份生成请求的必要例外,正如您指出的那样,这并不重要。生成身份很便宜,未使用的身份很容易被丢弃。作为对 REST 的认可,请记住使用 POST 获取您的新身份,这样它就不会被缓存和到处重复。

    关于the sterile debate about what idempotent means,我说它必须是一切。连续的请求应该不会产生额外的影响,并且应该收到与第一个处理的请求相同的响应。要实现这一点,您需要存储所有服务器响应,以便可以重放它们,并且您的 id 将识别操作,而不仅仅是资源。你会被赶出猫王的豪宅,但你会有一个防弹api。

    【讨论】:

    • 感谢您对此事的意见。因此,对于您的最后一点,您建议幂等 DELETE 应始终返回 200。不是第一次调用时返回 200,而在其他调用时返回 404,正如圆顶人所说,在关注服务器状态并考虑与该问题无关的返回码时。
    • 没错。根据 ME 的说法,所有不安全的请求都应该从请求对资源的空操作开始,然后实质性的不安全请求针对的是操作,而不是资源。这使服务器可以重新发送对先前看到的请求的响应,而无需重新处理该请求。我有一篇很短的小论文,如果你有兴趣,我希望你校对一下。 gmail dot com 的 bbsimonbb。
    • 当然...请随时通过 Outlook dot com 将其发送给 mibollma
    • 您的客户端可以在 POST 请求中包含客户端生成的(客户端)唯一 ID,而不是需要两次往返服务器。后端将此 ID 与创建的对象一起存储。当服务器接收到一个 POST 请求并找到一个在过去五分钟内使用该请求创建的对象时,它会将其识别为重复,而不是创建新对象并返回已经存在的对象。当然,您需要确保经过身份验证的客户端不能欺骗其他客户端的唯一 ID,这样才能检索这些其他客户端发布的数据。
    • 我建议不要以持续时间为基础。使用 id 和存储的响应,您就不需要了。 id 是您识别重复的方式。
    【解决方案4】:

    但是现在您有两个请求可能会丢失? POST 仍然可以重复,创建另一个资源实例。不要想太多东西。只需让批处理过程寻找受骗者。可能有一些关于您的资源的“访问”计数统计信息,以查看哪些被欺骗的候选人是被放弃的帖子的结果。

    另一种方法:根据一些日志筛选传入的 POST 以查看它是否是重复的。应该很容易找到:如果请求的正文内容与 x 前的请求的正文内容相同,则认为它是重复的。您可以检查额外的参数,例如原始 IP、相同的身份验证、...

    【讨论】:

    • 你说得对,现在我可能会丢失两个请求。我的想法是,丢失第一个没有问题,因为它是一个未初始化的对象,很容易被检测为未初始化。丢失第二个没有问题,因为请求是幂等的,可以重复。我想避免两个或更多对象出现在客户端。但你是对的……在服务器上安装一些筛选算法也可以:)
    • 你建议不要想太多,然后你就想多了。问题中提出的解决方案比这个更优雅。您是否试图保持 REST 的纯度?
    【解决方案5】:

    无论您使用哪种 HTTP 方法,理论上都不可能在不生成唯一标识符客户端、临时(作为某些请求检查系统的一部分)或作为永久服务器 id 的情况下发出幂等请求。丢失的 HTTP 请求不会产生重复,尽管有人担心请求可以成功到达服务器但响应不会返回到客户端。

    如果最终客户端可以轻松删除重复项并且它们不会导致固有的数据冲突,那么开发一个临时的重复预防系统可能还不够大。对请求使用 POST 并将 HTTP 标头中的 201 状态和响应正文中服务器生成的唯一 ID 发送回客户端。如果您的数据显示重复经常发生或任何重复都会导致严重问题,我会使用 PUT 并在客户端创建唯一 id。使用客户端创建的 id 作为数据库 id - 在服务器上创建额外的唯一 id 没有任何好处。

    【讨论】:

    【解决方案6】:

    我认为您也可以将创建和更新请求合并为一个请求(upsert)。为了创建新资源,客户端 POST 一个“工厂”资源,例如位于 /factory-url-name。然后服务器返回新资源的 URI。

    【讨论】:

    • 我不确定我是否完全理解他是如何合并成一个请求的。您介意更新一下答案吗?
    猜你喜欢
    • 1970-01-01
    • 2020-11-10
    • 1970-01-01
    • 2015-05-25
    • 1970-01-01
    • 2015-02-27
    • 1970-01-01
    • 1970-01-01
    • 2017-05-01
    相关资源
    最近更新 更多