Adam Wise
Adam Wise

Reputation: 2290

Best practice to detect when a REST PUT operation receives a stale or old version of an object

I understand that in REST, a PUT operation completely updates an object/record on the server. It seems like a common issue would be a scenario like the following:

  1. Bob makes a GET request for an object. For this example, lets say its a document that is incomplete/almost overdue, and Bob is a manager who might officially mark a document as 'overdue'.
  2. Alice makes a GET request for this same document.
  3. Alice finishes the document and updates it on the server by making a PUT call.
  4. Bob makes a PUT call, updating the document. This overwrites Alice's new work. Worse yet, Bob has added comments that only apply to the old version and perhaps has marked this version as officially overdue.

It seems like there should be a standard practice for rejecting the PUT call at step 4, by indicating that the PUT request is updating a stale version of the object. This could be done by including a timestamp or version on the received object during 'GET' operations. (The timestamp or version would need to only indicate the last edited version as provided by the server - e.g. a timestamp indicating the time of retrieval would break the idempotent rule. It would also be important that the client never update this timestamp/version in a PUT call, otherwise Bob could still unjustly overwrite Alice's document.)

But providing a version of an object does not seem to be a standard practice for GET/PUT REST calls. Is it actually a common practice in REST to ignore potential conflicts like this, or is there an established pattern to make sure Bob doesn't unjustly reject and overwrite Alice's document?

Thanks!

EDIT: The answer is that it is in RFC 9110. Basically step 4 can be protected by using an "If-Match" header field or similar:

https://www.rfc-editor.org/rfc/rfc9110.html#field.if-match:~:text=If%2DMatch%20is,Match%20field%20value.

If-Match is most often used with state-changing methods (e.g., POST, PUT, DELETE) to prevent accidental overwrites when multiple user agents might be acting in parallel on the same resource (i.e., to prevent the "lost update" problem). In general, it can be used with any method that involves the selection or modification of a representation to abort the request if the selected representation's current entity tag is not a member within the If-Match field value.

Also, for those of us using Spring, there's a simple annotation that can be added to support the If-Match validation automatically:

https://docs.spring.io/spring-data/rest/docs/current/reference/html/#conditional.etag

Upvotes: 1

Views: 348

Answers (1)

VoiceOfUnreason
VoiceOfUnreason

Reputation: 57279

It seems like there should be a standard practice for rejecting the PUT call at step 4, by indicating that the PUT request is updating a stale version of the object.

Good news: we have one.

It appears as two parts; in the responses that contain representations of our resources, we include validator fields that are specific to the representation.

In the requests to modify a resource, we use a conditional request - we use standardized fields (aka headers) in our request to identify the version that we are attempting to replace.

Depending on the current state of the resource, the server then either processes the request normally, or returns a 412 Precondition Failed response.

Upvotes: 2

Related Questions