Improving your application's performance using HTTP caching response headers

Posted by Maarten De Raedemaeker on 2017-10-20

Today’s blogpost will be about how you can improve your web application’s performance by adding caching using HTTP headers.
These headers allow us to limit the number of requests clients have to make, reducing load and improving client speed.

Cache-control

The main header we’ll be discussing is the cache-control header. This header is well supported. It allows us to instruct how proxies and the end user’s browser should handle a resource, cache-wise.
Since there are a few bugs around with “immutable”, “stale-while-revalidate” and “stale-if-error”, I’ll focus on the more basic options.

  • The first thing to discuss is cacheability
    • Public: response can be cached by any cache
    • Private: response should not be cached by a shared cache, only by end user.
    • No-Cache: can be cached, but each request has to be revalidated. This means that a shared cache can still hold on to a resource, but checks with the server before returning the cached content.
    • No-Store: disables caching.
  • The second part is about expiration.
    Important options here are:
    • max-age=[numberofseconds]: this option defines how long both local and shared cached can cache a response.
    • s-max-age=[numberofseconds]: this option does the same as max-age, but excludes private (local) caches, so it only works for shared caches.

Combined these could form the following headers:

Example 1: local / private caching for one hour, not on shared proxy.

1
Cache-Control: max-age=3600 private

Example 2: can be cached both locally and on shared proxies for 24 hours.

1
Cache-Control: max-age=86400 public

Vary

Allows deciding if a response needs to be cached based on if a set of request headers differ.
You could use it to make sure content-negotiated content is cached correctly. For example, an XML response is already cached, you want to make sure to get another response when you ask for JSON by letting the caching vary on the “Accept-Encoding” headers.

1
Vary: Accept-Encoding

Validation

There are two main ways of validating if a resource has changed.

Strong validation: ETags

If the browser sends back an ETag header with a specific value, the client can later pass on the ETag in a following request to check if the content has changed.

For example:

Client

1
GET /someResource

Server

1
2
3
4
200 OK
ETag: "abcdef"

// SOME CONTENT

Client

1
2
GET /someResource
If-None-Match: "abcdef"

Server

1
304 Not Modified

Weak validation: Last-Modified

This is a similar principle, though it depends on a date instead of a unique value.

Server

1
Last-Modified Sun, 05 Feb 2017 10:34:56 UTC

Client

1
If-Not-Modified-Since: Sun, 05 Feb 2017 10:34:56 UTC

Server

1
304 Not Modified

Revalidation

  • immutable: this tells the agent requesting the content that this resource will never change. The browser would be allowed to keep the resource, even after a hard refresh.
  • must-revalidate: this forces the client to check if the content has changed each time using the If-Modified-Since or If-None-Match mechanisms.
  • proxy-revalidate: this forces the proxy to check if the content has changed each time using the If-Modified-Since or If-None-Match mechanisms.

    Conclusion

    These headers give you some tools to get some nice performance gains for your web application using HTTP caching. Combined with a reverse proxy service such as Cloudflare, these can take a great load of your servers.

Sources & further reading


Comments: