Web APIs and the lost update problem

In the world of web API development there is always a concern for the lost update problem. This is when two users access the same record and try to perform an update concurrently.

For example:

1. Julio performs a

   GET request on /users/1
   
2. Maria also performs a 
   
   GET request on /users/1
   
3. Julio then performs an edit to update the record

4. Maria also performs an edit, but since she requested /users/1 before Julio updated the record, she would override any changes Julio did. 

Julio's edits would be lost :(

There are a couple of ways around this, but I want to show you how to use precondition HTTP headers and entity tags to solve the problem.

An entity tag is a string used to identify the version of a resource. The ETag HTTP header carries an entity tag as a value and typically looks something like:

ETag: "686897696a7c876b7e"

Precondition HTTP headers are the headers that start with If, for example, If-Match and If-None-Match. You can set these in your HTTP request, and if the conditions are not met the request will fail. Browsers may use these headers to decide if they should use a resource that is stored locally or if the server should return a new version of a resource.

For example:

1. Browser sends GET request to http://example.com/users/1

curl -I "http://example.com/users/1"

2. Server responds with

HTTP/1.1 200 OK
Etag: "0c4aeda935"
(OTHER HEADERS)

2. Browser caches the response locally and assigns the ETag value to it

3. Browser needs that resource (/users/1) again so it will send a GET request with a IF-None-Matches header that has the ETag value.

curl -I "http://example.com/users/1" --header 'If-None-Match: "0c4aeda935"'

4. If there is a record with that URL /users/1 and it has the same ETag value, the server will respond with

HTTP/1.1 304 Not Modified

With this 304 Not Modified response the browser knows that the resource has not changed, so it can just reuse the local copy.

Now knowing that we can set precondition to our HTTP request, lets revisit our lost update problem.

1. Julio performs a GET

Client Request
curl -I "http://example.com/users/1"

Server Response
HTTP/1.1 200 OK
Etag: "0c4aeda935300c"
(OTHER HEADERS)

2. Maria also performs a GET

Client Request
curl -I "http://example.com/users/1"

Server Response
HTTP/1.1 200 OK
Etag: "0c4aeda935300c"
(OTHER HEADERS)

3. Julio then performs an edit to the record and sends the request

Client Request
curl -X PUT "http://example.com/users/1"

4. Maria also performs edit. Again she doesn’t have the latest copy of the resource with Julio’s edits.

Client Request
curl -X PUT "http://example.com/users/1" --header 'If-Match: "0c4aeda935300c"'

Server Response
HTTP/1.1 412 Precondition Failed.

In this new version of our example, the request by Maria to update /users/1 is also sending the If-Match header. This is telling the server to perform this update ONLY if the resource at the specified URL has the same ETag. In other words, only update the resourse, if the resource hasn't changd. Because Julio edited the resource the ETag value will NOT match. In response Maria gets the HTTP 412 Precondition Failed message.

At this point the client can be creative and provide a solution for merging the edits. In the end the goal was to not lose Julio’s edits and it didn’t.