core core - 8 days ago 5
HTTP Question

How to prevent request that returns 304

When does a browser NOT make a request to the server for a file?

In other words, I have a JavaScript file being served. Its HTTP response header has an

ETag
,
Cache-Control: public
, and
Expires: Tue, 19 Jan 2038 03:14:07 GMT
.

The server is returning a
304
after the browser cache has been primed.

My question is, why is the browser even checking with the server and getting a
304
in the first place? I don't want the browser to go ask if there's a new version—it should load directly from browser cache without checking for modifications with the server serving the script.

What combination of HTTP response headers accomplishes this?

Answer

Firstly, the relevant HTTP spec is RFC 7234. If you look at the spec you will observe two things:

  • The spec never requires, under any circumstances, that a cache serves a cached version of content without revalidating. There are plenty of places where the spec notes that a cache MUST NOT use cached content to satisfy a request, but none at all where it dictates that it MUST do so or that it MUST NOT revalidate. So browser vendors are always free to revalidate if they want to.
  • Secondly, you're doing nothing wrong on your end. Browsers are free to cache responses and use those cached responses given the headers you're returning. The key point is in Section 4, where it is noted that one of the conditions for serving a cached response is that the response is either:

    • fresh (see Section 4.2), or

    • allowed to be served stale (see Section 4.2.4), or

    • successfully validated (see Section 4.3).

    Since you're spitting out an Expires header that is far in the future, and that point in the future has not yet been reached, the response is 'fresh', and therefore revalidation is not required. So you're doing everything that the spec suggests you should be on your end. (Although using Cache-Control: max-age=foo is a more modern way of setting cache expiry times than using the Expires: header.)

So if you want to change the browsers' caching behaviour, you're out of luck.

However, things might not be as bad as you think. You're probably only be seeing a request and 304 because you're refreshing the page in your browser when testing. Browsers handle cached resources differently depending upon how the request for them was triggered.

I ran a simple test in which I created a HTML page that contained a <script> tag pointing to a JS file, an <img> tag pointing to an image, and a <link> tag pointing to a CSS stylesheet. All of these files were hosted on an Apache server configured to serve them with:

  • an E-Tag header,
  • a Last-Modified date,
  • a Cache-Control: max-age=172800 header

Naturally, all resources were served with 200 codes on first page load. Thereafter, testing in Chrome or Firefox installs with default settings, I observed that:

  • If you refresh the page via the F5 key or the Refresh button, the page and all resources revalidate (i.e. a request is made to the server for each resource and a 304 returned).
  • If you return to the page via a link or entering the URL into the URL bar in a new tab, then no revalidation is done (i.e. no requests are made).
  • In Chrome, if you refresh the page by selecting the URL bar and pressing Enter, the page itself revalidates but no other resources do. In Firefox, neither the page nor resources revalidate.

This page indicates that Internet Explorer has the same behaviour:

There are a number of situations in which Internet Explorer needs to check whether a cached entry is valid:

  • The cached entry has no expiration date and the content is being accessed for the first time in a browser session
  • The cached entry has an expiration date but it has expired
  • The user has requested a page update by clicking the Refresh button or pressing F5

In other words, you're usually only going to see these revalidation requests if the user explicitly refreshes the page. Unless you have some very particular requirements about how you want the browser cache to behave, this behaviour seems perfectly reasonable.

Google and Mozilla both have some documentation about HTTP caching (I can't find anything equivalent on MSDN or the Apple Developers site), but neither suggests the existence of any vendor-specific caching headers that can be used to modify the rules the browser uses to choose when to revalidate. What you want to do is simply not possible.

If you truly need more control over this behaviour, you could look into the HTML5 Application Cache or roll your own caching logic using HTML5 Local Storage, like basket.js does.

Comments