REST中的Client Server API模式(不可靠的网络用例)

时间:2022-03-15 19:33:42

Let's assume we have a client/server interaction happening over unreliable network (packet drop). A client is calling server's RESTful api (over http over tcp):

假设我们在不可靠的网络上发生客户端/服务器交互(数据包丢弃)。客户端正在调用服务器的RESTful api(通过http over tcp):

  • issuing a POST to http://server.com/products
  • 向http://server.com/products发布POST
  • server is creating an object of "product" resource (persists it to a database, etc)
  • 服务器正在创建“产品”资源的对象(将其持久保存到数据库等)
  • server is returning 201 Created with a Location header of "http://server.com/products/12345"
  • 服务器正在返回201使用位置标题“http://server.com/products/12345”创建
  • ! TCP packet containing an http response gets dropped and eventually this leads to a tcp connection reset
  • !包含http响应的TCP数据包被丢弃,最终导致tcp连接重置

I see the following problem: the client will never get an ID of a newly created resource yet the server will have a resource created.

我看到以下问题:客户端永远不会获得新创建的资源的ID,但服务器将创建资源。

Questions: Is this application level behavior or should framework take care of that? How should a web framework (and Rails in particular) handle a situation like that? Are there any articles/whitepapers on REST for this topic?

问题:这个应用程序级别的行为或框架应该处理这个问题吗? Web框架(特别是Rails)应该如何处理这种情况?关于此主题,是否有关于REST的文章/白皮书?

5 个解决方案

#1


4  

If it isn't reasonable for duplicate resources to be created (e.g. products with identical titles, descriptions, etc.), then unique identifiers can be generated on the server which can be tracked against created resources to prevent duplicate requests from being processed. Unlike Darrel's suggestion of generating unique IDs on the client, this would also prevent separate users from creating duplicate resources (which you may or may not find desirable). Clients will be able to distinguish between "created" responses and "duplicate" responses by their response codes (201 and 303 respectively, in my example below).

如果创建重复资源是不合理的(例如,具有相同标题,描述等的产品),则可以在服务器上生成唯一标识符,可以针对创建的资源跟踪该唯一标识符以防止重复请求被处理。与Darrel关于在客户端上生成唯一ID的建议不同,这也会阻止单独的用户创建重复的资源(您可能会或可能不会想到这些资源)。客户将能够通过其响应代码区分“已创建”响应和“重​​复”响应(分别在下面的示例中为201和303)。

Pseudocode for generating such an identifier — in this case, a hash of a canonical representation of the request:

用于生成此类标识符的伪代码 - 在这种情况下,是请求的规范表示的哈希:

func product_POST
    // the canonical representation need not contain every field in
    // the request, just those which contribute to its "identity"
    tags = join sorted request.tags
    canonical = join [request.name, request.maker, tags, request.desc]
    id = hash canonical

    if id in products
        http303 products[id]
    else
        products[id] = create_product_from request
        http201 products[id]
    end
end

This ID may or may not be part of the created resources' URIs. Personally, I'd be inclined to track them separately — at the cost of an extra lookup table — if the URIs were going to be exposed to users, as hashes tend to be ugly and difficult for humans to remember.

此ID可能是也可能不是创建的资源URI的一部分。就个人而言,我倾向于单独跟踪它们 - 以额外的查找表为代价 - 如果URI将暴露给用户,因为哈希往往是丑陋的,人类难以记住。

In many cases, it also makes sense to "expire" these unique hashes after some time. For example, if you were to make a money transfer API, a user transferring the same amount of money to the same person a few minutes apart probably indicates that the client never received the "success" response. If a user transfers the same amount of money to the same person once a month, on the other hand, they're probably paying their rent. ;-)

在许多情况下,在一段时间后“过期”这些独特的哈希值也是有意义的。例如,如果您要进行汇款API,则用户将相同数量的资金转移到相隔几分钟的同一个人可能表示客户从未收到过“成功”回复。如果用户每月向同一个人转移相同数量的钱,另一方面,他们可能会支付他们的租金。 ;-)

#2


5  

The client will receive an error when the server does not respond to the POST. The client would then normally re-issue the request as they assume that it has failed. Off the top of my head I can think of two approaches to this problem.

当服务器不响应POST时,客户端将收到错误。然后,客户端通常会重新发出请求,因为他们认为该请求已失败。在我的脑海中,我可以想到两个解决这个问题的方法。

One is that the client can generate some kind of request identifier, such as a guid, which it includes in the request. If the server receives a POST request with a duplicate GUID then it can refuse it.

一个是客户端可以生成某种请求标识符,例如guid,它包含在请求中。如果服务器收到带有重复GUID的POST请求,则它可以拒绝它。

The other approach is to PUT instead of POST to create. If you cannot get the client to generate the URI then you can ask the server to provide a new URI with a GET and then do a PUT to that URI.

另一种方法是PUT而不是POST来创建。如果您无法让客户端生成URI,那么您可以要求服务器提供带有GET的新URI,然后对该URI执行PUT。

If you search for something like "make POST idempotent" you will probably find a bunch of other suggestions on how to do this.

如果您搜索“make POST idempotent”之类的内容,您可能会发现一些关于如何执行此操作的其他建议。

#3


1  

The problem as you describe it boils down to avoiding what are called double-adds. As mentioned by others, you need to make your posts idempotent.

你描述的问题归结为避免所谓的双重添加。正如其他人所说,你需要使你的帖子具有幂等性。

This can be easily implemented at the framework level. The framework can keep a cache of completed responses. The requests have to have a request unique so that any retries are treated as such, and not as new requests.

这可以在框架级别轻松实现。该框架可以保留已完成响应的缓存。请求必须具有唯一的请求,以便任何重试都被视为这样,而不是新请求。

If the successful response gets lost on its way to the client, the client will retry with the same request unique, the server will then respond with its cached response.

如果成功的响应在前往客户端的途中丢失,则客户端将使用相同的请求进行重试,然后服务器将使用其缓存的响应进行响应。

You are left with durability of the cache, how long to keep responses, etc. One approach is to remove responses from the server cache after a given period of time, this will depend on your app domain and traffic and can be left as a configurable step on the framework piece. Another approach is to force the client to sent acknowledgements. The acks can be sent either as separate requests (note that these could be lost too), or as extra data piggy backed on real requests.

您将获得缓存的持久性,保留响应的时间等。一种方法是在给定的时间段后从服务器缓存中删除响应,这取决于您的应用程序域和流量,并且可以保留为可配置踩在框架上。另一种方法是强制客户端发送确认。可以将ack作为单独的请求发送(请注意,这些请求也可能丢失),或者作为实际请求上的额外数据。

Although what I suggest is similar to what others suggest, I strongly encourage you to keep this layer of network resiliency to do only that, deal with drop requests/responses and not allow it to deal with duplicate resources from separate requests which is an application level task. Merging both pieces will mush all functionality and will not leave you with a clear separation of responsibilities.

虽然我的建议与其他人的建议相似,但我强烈建议您保持这层网络弹性,只处理丢弃请求/响应,不允许它处理来自单独请求的重复资源,这是一个应用程序级别任务。合并这两个部分将会影响所有功能,并且不会让您明确分离责任。

Not an easy problem, but if you keep it clean you can make your app much more resilient to bad networks without introducing too much complexity.

这不是一个简单的问题,但是如果你保持清洁,你可以使你的应用程序更加适应不良网络而不会引入太多复杂性。

And for some related experiences by others go here.

对于其他人的一些相关经验,请到这里。

Good luck.

祝你好运。

#4


0  

As the other responders have pointed out, the basic problem here is that the standard HTTP POST method is not idempotent like the other methods. There is an effort underway to establish a standard for an idempotent POST method known as Post-Once-Exactly, or POE.

正如其他响应者所指出的那样,这里的基本问题是标准的HTTP POST方法与其他方法一样不是幂等的。正在努力为幂等POST方法建立标准,称为Post-Once-Exactly或POE。

Now I'm not saying that this is a perfect solution for everybody in the situation you describe, but if it is the case that you are writing both the server and the client, you may be able to leverage some of the ideas from POE. The draft is here: http://tools.ietf.org/html/draft-nottingham-http-poe-00

现在我并不是说这对于你所描述的情况下的每个人来说都是一个完美的解决方案,但是如果是你正在编写服务器和客户端的情况,你可以利用POE的一些想法。草案在这里:http://tools.ietf.org/html/draft-nottingham-http-poe-00

It isn't a perfect solution, which is probably why it hasn't really taken off in the six years since the draft was submitted. Some of the problems, and some clever alternate options are discussed here: http://tech.groups.yahoo.com/group/rest-discuss/message/7646

这不是一个完美的解决方案,这可能是自提交草案以来六年内没有真正起飞的原因。这里讨论了一些问题和一些聪明的备选方案:http://tech.groups.yahoo.com/group/rest-discuss/message/7646

#5


-1  

HTTP is a stateless protocol, meaning the server can't open an HTTP connection. All connections get initialized by the client. So you can't solve such an error on the server side.

HTTP是无状态协议,意味着服务器无法打开HTTP连接。所有连接都由客户端初始化。所以你无法在服务器端解决这样的错误。

The only solution I can think of: If you know, which client created the product, you can supply it the products it created, if it pulls that information. If the client never contacts you again, you won't be able to transmit information about the new product.

我能想到的唯一解决方案:如果你知道,哪个客户创建了产品,你可以提供它创建的产品,如果它提取了那些信息。如果客户再也没有与您联系,您将无法传输有关新产品的信息。

#1


4  

If it isn't reasonable for duplicate resources to be created (e.g. products with identical titles, descriptions, etc.), then unique identifiers can be generated on the server which can be tracked against created resources to prevent duplicate requests from being processed. Unlike Darrel's suggestion of generating unique IDs on the client, this would also prevent separate users from creating duplicate resources (which you may or may not find desirable). Clients will be able to distinguish between "created" responses and "duplicate" responses by their response codes (201 and 303 respectively, in my example below).

如果创建重复资源是不合理的(例如,具有相同标题,描述等的产品),则可以在服务器上生成唯一标识符,可以针对创建的资源跟踪该唯一标识符以防止重复请求被处理。与Darrel关于在客户端上生成唯一ID的建议不同,这也会阻止单独的用户创建重复的资源(您可能会或可能不会想到这些资源)。客户将能够通过其响应代码区分“已创建”响应和“重​​复”响应(分别在下面的示例中为201和303)。

Pseudocode for generating such an identifier — in this case, a hash of a canonical representation of the request:

用于生成此类标识符的伪代码 - 在这种情况下,是请求的规范表示的哈希:

func product_POST
    // the canonical representation need not contain every field in
    // the request, just those which contribute to its "identity"
    tags = join sorted request.tags
    canonical = join [request.name, request.maker, tags, request.desc]
    id = hash canonical

    if id in products
        http303 products[id]
    else
        products[id] = create_product_from request
        http201 products[id]
    end
end

This ID may or may not be part of the created resources' URIs. Personally, I'd be inclined to track them separately — at the cost of an extra lookup table — if the URIs were going to be exposed to users, as hashes tend to be ugly and difficult for humans to remember.

此ID可能是也可能不是创建的资源URI的一部分。就个人而言,我倾向于单独跟踪它们 - 以额外的查找表为代价 - 如果URI将暴露给用户,因为哈希往往是丑陋的,人类难以记住。

In many cases, it also makes sense to "expire" these unique hashes after some time. For example, if you were to make a money transfer API, a user transferring the same amount of money to the same person a few minutes apart probably indicates that the client never received the "success" response. If a user transfers the same amount of money to the same person once a month, on the other hand, they're probably paying their rent. ;-)

在许多情况下,在一段时间后“过期”这些独特的哈希值也是有意义的。例如,如果您要进行汇款API,则用户将相同数量的资金转移到相隔几分钟的同一个人可能表示客户从未收到过“成功”回复。如果用户每月向同一个人转移相同数量的钱,另一方面,他们可能会支付他们的租金。 ;-)

#2


5  

The client will receive an error when the server does not respond to the POST. The client would then normally re-issue the request as they assume that it has failed. Off the top of my head I can think of two approaches to this problem.

当服务器不响应POST时,客户端将收到错误。然后,客户端通常会重新发出请求,因为他们认为该请求已失败。在我的脑海中,我可以想到两个解决这个问题的方法。

One is that the client can generate some kind of request identifier, such as a guid, which it includes in the request. If the server receives a POST request with a duplicate GUID then it can refuse it.

一个是客户端可以生成某种请求标识符,例如guid,它包含在请求中。如果服务器收到带有重复GUID的POST请求,则它可以拒绝它。

The other approach is to PUT instead of POST to create. If you cannot get the client to generate the URI then you can ask the server to provide a new URI with a GET and then do a PUT to that URI.

另一种方法是PUT而不是POST来创建。如果您无法让客户端生成URI,那么您可以要求服务器提供带有GET的新URI,然后对该URI执行PUT。

If you search for something like "make POST idempotent" you will probably find a bunch of other suggestions on how to do this.

如果您搜索“make POST idempotent”之类的内容,您可能会发现一些关于如何执行此操作的其他建议。

#3


1  

The problem as you describe it boils down to avoiding what are called double-adds. As mentioned by others, you need to make your posts idempotent.

你描述的问题归结为避免所谓的双重添加。正如其他人所说,你需要使你的帖子具有幂等性。

This can be easily implemented at the framework level. The framework can keep a cache of completed responses. The requests have to have a request unique so that any retries are treated as such, and not as new requests.

这可以在框架级别轻松实现。该框架可以保留已完成响应的缓存。请求必须具有唯一的请求,以便任何重试都被视为这样,而不是新请求。

If the successful response gets lost on its way to the client, the client will retry with the same request unique, the server will then respond with its cached response.

如果成功的响应在前往客户端的途中丢失,则客户端将使用相同的请求进行重试,然后服务器将使用其缓存的响应进行响应。

You are left with durability of the cache, how long to keep responses, etc. One approach is to remove responses from the server cache after a given period of time, this will depend on your app domain and traffic and can be left as a configurable step on the framework piece. Another approach is to force the client to sent acknowledgements. The acks can be sent either as separate requests (note that these could be lost too), or as extra data piggy backed on real requests.

您将获得缓存的持久性,保留响应的时间等。一种方法是在给定的时间段后从服务器缓存中删除响应,这取决于您的应用程序域和流量,并且可以保留为可配置踩在框架上。另一种方法是强制客户端发送确认。可以将ack作为单独的请求发送(请注意,这些请求也可能丢失),或者作为实际请求上的额外数据。

Although what I suggest is similar to what others suggest, I strongly encourage you to keep this layer of network resiliency to do only that, deal with drop requests/responses and not allow it to deal with duplicate resources from separate requests which is an application level task. Merging both pieces will mush all functionality and will not leave you with a clear separation of responsibilities.

虽然我的建议与其他人的建议相似,但我强烈建议您保持这层网络弹性,只处理丢弃请求/响应,不允许它处理来自单独请求的重复资源,这是一个应用程序级别任务。合并这两个部分将会影响所有功能,并且不会让您明确分离责任。

Not an easy problem, but if you keep it clean you can make your app much more resilient to bad networks without introducing too much complexity.

这不是一个简单的问题,但是如果你保持清洁,你可以使你的应用程序更加适应不良网络而不会引入太多复杂性。

And for some related experiences by others go here.

对于其他人的一些相关经验,请到这里。

Good luck.

祝你好运。

#4


0  

As the other responders have pointed out, the basic problem here is that the standard HTTP POST method is not idempotent like the other methods. There is an effort underway to establish a standard for an idempotent POST method known as Post-Once-Exactly, or POE.

正如其他响应者所指出的那样,这里的基本问题是标准的HTTP POST方法与其他方法一样不是幂等的。正在努力为幂等POST方法建立标准,称为Post-Once-Exactly或POE。

Now I'm not saying that this is a perfect solution for everybody in the situation you describe, but if it is the case that you are writing both the server and the client, you may be able to leverage some of the ideas from POE. The draft is here: http://tools.ietf.org/html/draft-nottingham-http-poe-00

现在我并不是说这对于你所描述的情况下的每个人来说都是一个完美的解决方案,但是如果是你正在编写服务器和客户端的情况,你可以利用POE的一些想法。草案在这里:http://tools.ietf.org/html/draft-nottingham-http-poe-00

It isn't a perfect solution, which is probably why it hasn't really taken off in the six years since the draft was submitted. Some of the problems, and some clever alternate options are discussed here: http://tech.groups.yahoo.com/group/rest-discuss/message/7646

这不是一个完美的解决方案,这可能是自提交草案以来六年内没有真正起飞的原因。这里讨论了一些问题和一些聪明的备选方案:http://tech.groups.yahoo.com/group/rest-discuss/message/7646

#5


-1  

HTTP is a stateless protocol, meaning the server can't open an HTTP connection. All connections get initialized by the client. So you can't solve such an error on the server side.

HTTP是无状态协议,意味着服务器无法打开HTTP连接。所有连接都由客户端初始化。所以你无法在服务器端解决这样的错误。

The only solution I can think of: If you know, which client created the product, you can supply it the products it created, if it pulls that information. If the client never contacts you again, you won't be able to transmit information about the new product.

我能想到的唯一解决方案:如果你知道,哪个客户创建了产品,你可以提供它创建的产品,如果它提取了那些信息。如果客户再也没有与您联系,您将无法传输有关新产品的信息。