66

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?

brimble2010
  • 17,796
  • 7
  • 28
  • 45
core
  • 32,451
  • 45
  • 138
  • 193
  • 3
    Who is setting the expires header? According to [this page](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html), the expires header should not be further into the future than one year. On the other hand, according to [this other page](http://blog.httpwatch.com/2007/12/10/two-simple-rules-for-http-caching/) the maximum supported date is `Sun, 17-Jan-2038 19:14:07 GMT since that’s the maximum value supported by the 32 bit Unix time/date format`. Your date is past that by a couple of days, maybe that is the reason... – user1429080 Oct 10 '14 at 12:04
  • 2
    @user1429080 the RFC you've linked to is obsolete (note the top banner at https://tools.ietf.org/html/rfc2616; you should always check RFCs for obsolescence on tools.ietf.org before assuming they represent current spec). However, your point remains relevant. While the currently relevant spec, [RFC 7234](https://tools.ietf.org/html/rfc7234), has removed the advice against setting expiry dates beyond 1 year in the future, it *does* warn that dates in the really distant future ought to be avoided to prevent overflows. – Mark Amery Oct 11 '14 at 10:57
  • @MarkAmery Thanks for the pointer to `tools.ietf.org`, I'll have to add that as a bookmark. About the expires header: I would be surprised if the future date was actually the cause of the issue, but since I found it I thought I should mention it... – user1429080 Oct 13 '14 at 06:01
  • I have the opposite question. I want the browser to check for a new version. So many problems arise due to browser caching when it does not, apparently due to some mysterious algorithm. Though I hadn't consider that the `Expires` header may play a part (along with `Prama`). Even if it does, what happens when you have a new version? How will the browser find out if it never asks? Do you want to employ millions of call-centre staff to repeatly tell users to clear their browser cache? – Jake Feb 23 '23 at 04:12

5 Answers5

100

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.

Community
  • 1
  • 1
Mark Amery
  • 143,130
  • 81
  • 406
  • 459
  • I guess, the HTML5 Application Cache is, what Chris is looking for - great answer – Quicker Oct 15 '14 at 06:46
  • 2
    This is a really good answer. These days you could also mention ServiceWorker as an option to have better control over your cached resources. – fabiomcosta Mar 04 '16 at 21:37
  • 1
    While this all makes sense, anyone know why I'm seeing something different? I have a javascript file that at first, requires a normal request to download the file (200). Subsequent requests would then give a 304 (refreshing page) or a locally cached version (following links or hitting Enter on URL bar). At random times though, hitting Enter would do a 304 revalidation. So chrome randomly shuffles between 304 and cached for me – georaldc Aug 15 '16 at 16:24
0

Expires: or Cache-Control: max-age= should work. Have you confirmed in the server logs that the browser is actually making network calls? I've found that firebug, for example, has confusing output that suggests you are making remote calls when you are actually hitting the cache.

James Scriven
  • 7,784
  • 1
  • 32
  • 36
0

If you're in my boat and have an Angular app deployed to IIS, then make sure your web.config is configured to correctly rewrite the url, as follows:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <httpErrors errorMode="Detailed" />
        <rewrite>
            <rules>
                <rule name="Angular Routes" stopProcessing="true">
                    <match url=".*" />
                    <conditions logicalGrouping="MatchAll">
                        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                    </conditions>
                    <action type="Rewrite" url="/NameOfYourApp_UnderDefaultWebSite/" />
                </rule>
            </rules>
        </rewrite>
  </system.webServer>
</configuration>

Specifically note the value of url in <action type="Rewrite" url="/NameOfYourApp_UnderDefaultWebSite/" />

Adam Cox
  • 3,341
  • 1
  • 36
  • 46
0

I ran into a situation like this after adding must-revalidate to the caching policy of a specific file that previously only had max-age set. The problem was that the browser had retained the old If-Modified-Since value from a previous request so was always checking the resource and getting a 304 response. After clearing my cache the new policy was applied and now the file is served directly from memory/disk cache as expected.

Besworks
  • 4,123
  • 1
  • 18
  • 34
  • A good reason to make sure your `Last-Modified` time is correct in the first place; i.e. don't fake a time to circumvent a problem. – Jake Feb 23 '23 at 04:16
-2

There is a rule that setting an Expires header more than one year in the future violates the HTTP 1.1 RFC.

So, HTTP response header here is invalid (Expires: Tue, 19 Jan 2038 03:14:07 GMT). Fixing this may solve the problem!

Giannis Grivas
  • 3,374
  • 1
  • 18
  • 38
  • 4
    As of this year, RFC 2616 is obsolete, as shown in the top bar at https://tools.ietf.org/html/rfc2616. I recommend always reading RFCs on tools.ietf.org in order to check for obsolescence, although in this case W3 also note the obsolescence at http://www.w3.org/Protocols/rfc2616/rfc2616. Caching is now covered by https://tools.ietf.org/html/rfc7234, and the changes appendix there notes that *The one-year limit on Expires header field values has been removed*. – Mark Amery Oct 11 '14 at 10:54