In the previous post in this series on Conditional Requests I introduced the topic of validators, their purpose and how they can be constructed. A large chunk of the work that needs to be done to support conditional requests is done by the origin server. This blog post is about that role.
The first job of the origin server is to return a validator header along with responses for resources that want to support conditional requests. The second function it performs is when it receives a conditional request it needs to decide if it is going to fulfill the request or fail. A server can recognize a conditional request because there is a request header that starts with If-. Which if- header is used depends on the type of validator being using and the type of HTTP method being used. The following if- headers are lists in the message headers repository:
- If-Range (rfc7233 – Range Requests)
- If-Schedule-Tag-Match (rfc6638 - CALDAV)
The If-Range and If-Schedule-Tag-Match are slightly different cases that we can save for discussion on a different day.
For safe requests like GET the goal is to only return a response body if it has changed from what has previously been retrieved. Therefore we use the If-none-match header with an Etag or the if-modified-since header with a Last-modified validator. If these pre-conditions fail, then the 304 – Not modified header is returned.
The if-none-match is named the way it is because a client can actually send multiple Etag values. This can be used when a cache is holding multiple different variant representations and it only wants to receive an update if all of its variants are out-of-date.
When comparing validators for the purpose of deciding to read or not, a “weak” comparison is used. This means that both strong and weak validators are compared and are considered equal if the validator values are the same.
For unsafe requests like PUT, POST, PATCH, DELETE, the conditional headers if-match and if-unmodified-since are used to abort the request if the validators do not match. In this case the status code 412 – Precondition Failed should be returned.
When comparing validators before writing, a “strong” comparison is used. This means that only strong validators can be used and must be equal before the request can be performed.
Bring a backup
Although a client only really needs one type of validator value to perform conditional requests, it is recommended that an origin server return both a Etag and a Last-modified header if possible. I can only assume this is to provide support for web caches that only support HTTP/1.0. ETags have been standardized since 1997. It continues to amaze me how seriously the IETF folks take backward compatibility.
The specification makes it clear that for each of the conditional request headers, the origin server must not perform the actual request behaviour if the pre-condition fails. However, it goes on to say that
redirects and failures take precedence over the evaluation of preconditions in conditional requests
This infers that you needs to do just enough of the request processing to know that it will produce a success status code, before testing the the pre-condition and performing the request. This has some significant implications on how you might be able to implement conditional request handling. The specification says,
a recipient cache or origin server MUST evaluate received request preconditions after it has successfully performed its normal request checks and just before it would perform the action associated with the request method.
The part I find interesting about this last quote is the distinction between “request checks” and the “action associated with the request method” It would be valuable to further explore this distinction and understand what checks need to be performed before pre-conditions can be evaluated.
If you consider each of the individual elements of the entire conditional request handling process in isolation, each piece is fairly straightforward. However, the HTTP specification puts no limitations on the ability to send multiple different conditional headers at the same time. In order to produce sane results there specification lays out a set of rules as to the order in which these conditional headers should be evaluated. I have simplified the rules a little by ignoring the possibility of If-Range headers, and the end result is the diagram below.
If you follow the flow chart through and reach the GO symbol, that means that the actual request can be performed.
Don’t fail if you don’t have to
One interesting area that I was not previously aware of until I created this diagram was the step that takes place after an Etag fails to match or the If-Unmodified-Since fails. If some other request has already made the same change that the current conditional request is trying to make, then a server should not return a 412 failure, but simply return a 2XX status.
One last piece of the puzzle
In the first article on this subject we talked about the validator values that are required to make conditional requests. This article covers the role of the server in conditional request handling. The next article will discuss how the client can make conditional requests.