14

In a RESTful SOA, suppose I issue a POST request via AJAX but I don't get a response before the request times out. Further suppose a re-submission of the request would be harmful. POST is not idempotent. For example, maybe I am posting a bank transfer of money. If I don't get a response, I don't know if the server processed the request.

What is the best practice to deal with this, assuming I have control over the client-side and the services side?

My initial thought is to include a nonce (i.e. a pseudo-id; some sort of unique identifier) with each POST request; e.g. perhaps a value in the If-None-Match header. With this approach, the client-side can programmatically re-issue a timed out request with the same pseudo-id and the server can reject it if it contains a repeat value.

4

1 回答 1

19

There are a number of ways I've heard of to try to solve this

  1. Get the current state.

    Design the service so that if a POST fails, the client can issue a GET on the same resource and based on the data returned, they can determine if the POST was successful of not.

    The problem with this approach is in cases where the POST creates a new item in a collection, it could be difficult for the client to determine if the post was successful or not (i.e., did my post add that item or did someone else's?)

  2. If-Match

    Use the If-Match header to prevent the POST from being repeated. e.g., if the POST is adding an item to a collection, and the collection currently has a ETag of 737060cd8c284d8af7ad3082f209582d. If If-Match of 737060cd8c284d8af7ad3082f209582d is used on the POST, then the POST will only succeed if the collection's ETag is still 737060cd8c284d8af7ad3082f209582d, which will add the item and result in a new ETag for the collection. Repeating the POST at this stage will just return 412 Precondition Failed.

    The problem with this approach is the when you get a 412 Precondition Failed, you can't be sure if your POST modified the collection or someone else's.

  3. POST then PUT

    Design your service to never persist data from a POST. Instead the POST creates a temporary resource with the POST content, which is in a "Pending" state. This temporary resource is then "committed" with a PUT. With this approach you don't care if your POST time's out, just request the POST again and this time you'll hopefully get your temporary resource. If the PUT to commit the resource time's out, you don't care either, because the PUT is idempotent.

    The only real downside of this approach is the extra work required to manage the temporary resources and the extra effort required in the client.

    Update

  4. Nonce

    The use of a nonce (a.k.a Message ID, Transaction ID, Request ID, Correlation ID) in the request is a common approach for solving this problem; however it can have scalability issues. If you commit to rejecting all POSTs that use a previous nonce, you then need to scan the existing POST records to determine if the nonce has been used before. Not a problem when you have only a few thousand POSTs, but it can become problematic when you have millions (especially if you haven't stored the nonces in a way that is fast to query).

    You can mitigate this by reducing your commitment to rejecting all POSTs that use a previous nonce within a certain timeframe (e.g., last 24hrs), but if the first POST times out and the client is disconnected for that timeframe, then they are back in the same old position, where they don't know if the first POST was successful and have no way to determine if they should rePOST or not.

于 2012-12-12T11:54:46.740 回答