HTTP Caching

The freshness requirement says the data the API returns may be at most one hour behind the registrar’s system. Read that the other way. A response the API sent less than an hour ago is still fresh. A client that has it does not need to send the request again, and if it does send the request again, the API does not need to build the response again.

Chapter 5 put a cache inside HopPress, between the application and its database. HTTP has caching built into the protocol, and it works on the other side of the server, between the server and its clients. The server says in a response header how long the response stays fresh. Whoever holds the response, the client or a cache between the client and the server, reuses it until then.

Telling the client how long

Here is the response to a request for one course, with one header added:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=3600, must-revalidate

{
  "course_number": "EN.601.226",
  "title": "Data Structures",
  "credits": 4,
  "subject_area": "computer-science"
}

max-age=3600 lets a cache reuse the response for one hour after it was generated or last validated by the server. That number comes straight from the freshness requirement. During that hour, a cached copy can answer requests for the same URL. must-revalidate says that once the hour ends, the cache must check with the server before reusing the copy. public says any cache may store the response, not only the client that requested it. We can say that for every response of this API. The response does not depend on who the client is. Two clients that send the same request get the same response.

Every 200 OK response gets this header. Error responses get Cache-Control: no-store, which tells caches not to store them. Simply leaving out caching headers is not enough: HTTP allows some error responses, including 404, to be cached. We choose not to cache errors so that a client that requests a missing course can find it on its next request after the course is added.

Asking whether the copy is still fresh

After the hour, the client’s copy has expired. The client could send the request again and take the whole response. Most of the time the course has not changed, and sending the same 150 bytes again is a waste. So the server puts a second header on every response, a short string that changes whenever the representation changes:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=3600, must-revalidate
ETag: "a91f"

The ETag is a version tag for the representation. The client keeps it with the copy. When the copy expires, the client sends the request with the tag in a header:

GET /api/courses/EN.601.226 HTTP/1.1
Host: registrar.university.edu
Accept: application/json
If-None-Match: "a91f"

The header says: send me the course only if its tag is no longer "a91f". If the course has not changed, the server sends this:

HTTP/1.1 304 Not Modified
Cache-Control: public, max-age=3600, must-revalidate
ETag: "a91f"

No body. The status code says the copy the client has is still the current one, and the Cache-Control header gives it another hour. If the course has changed, the server sends 200 OK with the new representation and a new tag, the same as a first request.

This is the 3xx status code that the status codes section mentioned. It is not a redirect. It tells the client to reuse the copy it already has.

What this achieves

Three of the quality attributes get something from these headers. Freshness is met, because no cached copy lasts longer than the hour the requirement allows. Performance improves because a client can reuse a fresh local copy without a network request, and a cache between the client and the server can answer without contacting the registrar. Isolation improves because those cache hits avoid work in the registrar’s system. The catalog changes a few times a day and is requested thousands of times an hour, and caching is what keeps most of those requests from reaching the registrar.