【问题标题】:Server return status 200 but client doesn't receive it because network connection is broken服务器返回状态 200 但客户端没有收到它,因为网络连接断开
【发布时间】:2018-05-24 09:36:31
【问题描述】:

我有向 REST 服务发送 POST 请求的 REST 服务和客户端(Android 应用程序)。在客户端,有需要与 Web 服务器同步的文档(订单)。同步意味着客户端针对每个订单向 REST 服务发送 POST 请求。当 REST 服务收到 POST 请求时,它会将数据写入数据库并向客户端发送状态为 200 的响应。客户收到 200 并将该订单标记为已同步。

问题是在服务器发送状态 200 响应之后但在客户端收到响应之前连接中断。客户未将订单标记为同步。下次客户端再次发送此订单,服务器再次将其写入数据库,因此我们有两次相同的订单。

处理这类问题有什么好的做法?

【问题讨论】:

    标签: rest http post


    【解决方案1】:

    问题是在服务器发送状态 200 响应之后但客户端收到响应之前连接中断。客户未将订单标记为同步。下次客户端再次发送此订单,服务器再次将其写入数据库,因此我们有两次相同的订单。

    欢迎来到不可靠消息传递的世界。

    处理这类问题有什么好的做法?

    您应该查看“无人需要可靠消息传递”,Marc de Graauw (2010)。

    可靠消息传递的基石是idempotent 请求处理。幂等语义就是这样描述的

    如果使用该方法的多个相同请求对服务器的预期效果与单个此类请求的效果相同,则该请求方法被认为是“幂等的”。

    然而,仅仅对请求方法大惊小怪并不能得到任何东西。首先,消息中的other语义可能与幂等请求方法不一致,其次,服务器需要知道如何实现效果按预期

    幂等请求处理有两种基本模式。其中更简单的是set,意思是“用我提供的那个覆盖当前的表示”。

    // X == 6
    
    server.setX(7)
    // X == 7
    
    server.setX(7) <- a second, identical request, but the _effect_ is the same.
    // X == 7
    

    替代方案是test and set(有时称为compare and swap);在这种模式中,请求有两部分 - 一个用于确定某个条件是否成立的谓词,以及如果该条件成立则应用的更改。

    // X == 6
    
    server.testAndSetX(6,7)
    // X == 7 
    
    server.testAndSetX(6,7) <- this is a no op, because 7 != 6
    // X == 7
    

    这是核心思想。

    根据您的描述,您正在做的是操纵一系列订单。
    相同的基本思想在那里有效。如果您可以根据请求中的信息计算出唯一标识符,那么您可以将您的集合视为集合/键值存储。

    // collection.get(Id.of(7)) == Nothing
    
    collection.put(Id.of(7), 7)
    // collection.get(Id.of(7)) == Just(7)
    
    collection.put(Id.of(7), 7) <- a second, identical request, but the _effect_ is the same.
    // collection.get(Id.of(7)) == Just(7)
    

    如果这不是一个选项,那么您需要集合的某些属性,这些属性会在您进行编辑时更改,并编码到请求中

    if (collection.size() == 3) {
       collection.append(7)
    }
    

    管理此类事情的通用方法是考虑版本号——每次进​​行更改时,版本号都会作为同一事务的一部分递增

    // begin transaction
    if (resource.version.get() == expectedVersion) {
       resource.version.set(1 + expectedVersion)
       resource.applyChange(request)
    }
    // end transaction
    

    对于现实世界的示例,请考虑 JSON Patch,其中包含一个 test operation,可用作防止“并发”修改文档的条件。

    我们在所有这些test and set 场景中描述的是conditional request 的概念

    条件请求是 HTTP 请求 [RFC7231],包括一个或多个标头字段,指示在将方法语义应用于目标资源之前要测试的先决条件。

    条件请求规范为您提供的是一种通用方式来描述您的请求和响应的元数据中的条件,以便 通用 http 组件可以做出有用的贡献。

    请注意:这样做的结果并不能保证服务器会按照客户端的要求进行操作。相反,它的弱点是:客户端可以安全地重复请求,直到它收到来自服务器的确认。

    【讨论】:

      【解决方案2】:

      当然,您的文件必须具有唯一标识符。语义上正确的方法是使用 If-None-Match 标头发送该标识符。

      然后服务器检查具有该标识符的文档是否已经存在,如果是这样,将返回412 Precondition Failed

      【讨论】:

        【解决方案3】:

        其中一个可能的选项是在服务器端进行验证。订单应该有一些唯一性参数:名称或 id 或其他东西。但是这个参数也应该由客户端发送。然后你得到这个值(例如,如果名称是唯一的并且客户端发送它),在数据库中找到这个订单。如果订单已创建,则无需将其保存到数据库中,应向客户端发送 409 冲突响应。如果您在数据库中没有找到这样的订单,则将其保存并发送 201 Ok 响应。

        最佳实践:

        • 201 可以进行 POST 处理
        • 409 冲突 - 如果资源已存在

        【讨论】:

          【解决方案4】:

          您的请求应为idempotent
          根据您的描述,您应该使用 PUT 而不是 POST。
          客户端生成的 Ids (guids) 和 Upsert 逻辑服务器端,帮助实现这一点。
          这样,您可以为失败的请求实现重试逻辑客户端,而无需引入多条记录。

          【讨论】:

            猜你喜欢
            • 2014-11-04
            • 2018-07-07
            • 2021-10-01
            • 2016-07-26
            • 2013-05-18
            • 1970-01-01
            • 2017-10-06
            • 1970-01-01
            • 2011-12-26
            相关资源
            最近更新 更多