Domenic Labbate

Managing REST API Concurrency with ETag Header

Managing REST API Concurrency with ETag Header
Published: September 23rd, 2024Last Edited: September 23rd, 2024
16 Views

Introduction

Prior to reading this article, I would recommend first reading my blog: Optimistic Concurrency in Databases.

The ETag (or entity tag) HTTP response header is an identifier for a specific version of a resource. In this article, we will discuss how this header is useful to prevent simultaneous updates of a resource from overwriting each other.

Problem

Let's first illustrate the problem by considering a REST API that does not leverage ETag headers.

Consider a blog website, where multiple users can make edits to the same blog post. Here is a simplified representation of the REST API:

GET /blogs/{blogId}
PUT /blogs/{blogId}
GET /blogs/{blogId}
PUT /blogs/{blogId}

Now imagine that two users (User A and User B) both make edits at the same time:

REST API Without ETag Headers
REST API Without ETag Headers

As illustrated in the diagram above, without ETag it is possible to overwrite changes made by another user!

REST APIs without ETag headers are at risk of overwriting changes made by other users.

Solution

This problem can be resolved by leveraging ETag and If-Match headers.

What is the ETag Header?

An ETag is a version identifier for a resource, ensuring updates are made only when the current version is matched.

(Definition) ETag Header
The ETag (or entity tag) HTTP response header is an identifier for a specific version of a resource.

This is returned as a header in the GET endpoint of a resource.

What is the If-Match Header?

The If-Match header is used in HTTP requests to ensure that a resource is updated only if it matches a specific version. In more detail, it contains the ETag which can be retrieved by the GET endpoint of a resource.

(Definition) If-Match header
The If-Match header is used in HTTP requests to ensure that a resource is updated only if it matches a specific version.

ETag formats (Weak, Strong)

Weak

Weak ETag values of two representations of the same resources might be semantically equivalent, but not byte-for-byte identical.

ETag: W/"0815"
ETag: W/"0815"

Strong

Strong ETag values uniquely represent the requested resource. It is a string of ASCII characters placed between double quotes, like "675af34563dc-tr34". The ETag can be generated as a hash of the content, a hash of the last modification timestamp, or just a version number.

ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

ETag in Concurrency Control

The image below illustrates a modified example where ETag and If-Match headers are used to prevent simultaneous updates from overwriting each other.

UML sequence diagram of a REST API that leverages ETag and If-Match headers
REST API with ETag and If-Match Headers

When performing the update via the PUT /blogs/{blogId}, an If-Match header is passed (with the ETag value). If the version of the resource does not match, a 412 (Precondition Failed) response will be returned.

As a result, User B will not overwrite the changes made by User A!

As a result, User B will not overwrite the changes made by User A!
412 (Precondition Failed)
412 (Precondition Failed) response is returned by the server if the provided ETag value does not match that of the resource.

What Happens Without If-Match Header

UML sequence diagram of a REST API that leverages ETag and If-Match headers
REST API With ETag Headers

In the case that no If-Match header is provided to the PUT /blogs/{blogId}, the server can respond with 428 (Precondition Required) response.

428 (Precondition Required)
428 (Precondition Required) response is returned by the server if no If-Match header is provided in the PUT operation.

Conclusion

  • The ETag (or entity tag) HTTP response header is an identifier for a specific version of a resource.
  • It is useful to prevent simultaneous updates of a resource from overwriting each other by returning a 412 (Precondition Failed) response when the If-Match header does not match the expected value.

References