Cache-Control not returning 304

2.4k views Asked by At

I have a website with static assets that don't change (js, images, etc). Each of these assets has the cache-control header set with the following property:

cache-control: public, max-age=31536000, immutable

However, when I reload the page I'm still seeing 200 responses from the server instead of a 304 response. The browser is indicating that the asset is being served from memory or disk cache, but it is still making the request and downloading content. This used to work before and I'm leaning towards this being a browser bug, but I'm not entirely sure.

5

There are 5 answers

0
Dipen Shah On

What is discussed in comments and as mentioned by Kevin, you seems to have misunderstood conditional get request.

There are bunch of questions that you might want to get answer for:

1. Why browser is getting HTTP 200 status instead of 304?

When browser loads any document, request for static resource will go through cache handler and if resource exists in local cache, request will be server by cache handler and response will have HTTP 200 status as opposed to sending request to server.

For example, for this stackoverflow page, when I refresh the page I also see something similar in the network tab for jQuery for example.

enter image description here

but there aren't any request made to fetch the resource. Looking at the fiddler, there is a request to establish HTTPS connection, but nothing to fetch jQuery.

enter image description here

2. When will browser send conditional request?

When cache expires, user agent will send conditional get request to verify it the content is modified or not, if resource is not modified, server will send HTTP 304 response and along with it is will share new expiry date for the content.

enter image description here

For our jQuery example, you can send following request from postman:

GET https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js
Header:
Cache-Control: public
If-Modified-Since: Tue, 03 Mar 2020 19:15:00 GMT

and you will get following response with updated expiry date:

HTTP 304
Date: Fri, 16 Oct 2020 08:58:03 GMT
Expires: Sat, 16 Oct 2021 08:58:03 GMT
Age: 32066

Without going into details, caching can be summarized as:

1. User agent sends request to server
2. Server responds resource along with "Last-Modified" date
3. Next time same resource is requested, browser will use "Last-Modified" date (aka validator) to check if resource is stale or not
    3.1 If resource is not stale, it will be served from cache
    3.2 If resource is stale, browser will use "Last-Modified" date in header and **send conditional get** to server
        3.2.1 server can resource and send HTTP 200 is resource is updated
        3.2.2 In case resource is not modified, server will send HTTP 304 along with updated `Expiry` date

3. Why it was working before?

To be honest, I am not sure why it was working before. There could be multiple reasons:

  1. may be your browser cache was old and had old value for Last-Modified header for cached resource due to which conditional get request was sent from your user agent and server returned updated Last-Modified date on the cached resource (Expires response header).
  2. May be caching was disabled by browser (incognito mode or explicitly using no-cache)
  3. May be server did not support caching earlier and did not send Last-Modified in response and so on.

Reference:

  1. https://web.dev/http-cache/
  2. https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
  3. https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
0
Kevin Christopher Henry On

What you are describing is simply how Chrome dev tools shows a response served from the cache. There are other StackOverflow questions (like this one) that confirm this.

The status code is displayed as 200 because that's what it is. The response is cached, and part of the response is the status code. There is no special status code for a cached response.

You've misunderstood what 304 is used for. That is used in reply to a conditional request. It represents an actual response with that status code, received over the network, not a cached response.

To confirm that the response is indeed served from the cache, and that no new request is being sent to the server, you can look at your server logs.

0
Vasco Fortuna On

Try removing the tag immutable and using:

Cache-Control: public, max-age=31536000;

You can learn more on Mozilla's Cache-Control documentation.

0
Ron On

When a URL is retrieved, the web server will return the resource's current representation along with its corresponding ETag value, which is placed in an HTTP response header "ETag" field.

The client may then decide to cache the representation, along with its ETag.

Later, if the client wants to retrieve the same URL resource again, it will first determine whether the local cached version of the URL has expired (through the Cache-Control and the Expire headers). If the URL has not expired, it will retrieve the local cached resource. If it determined that the URL has expired (is stale), then the client will contact the server and send its previously saved copy of the ETag along with the request in a "If-None-Match" field. (Source: https://en.wikipedia.org/wiki/HTTP_ETag)

But even when the expire time for an asset is set in future, a browser can still reach the server for a conditional GET using ETag as per the Vary header.

Details on 'vary' header: https://www.fastly.com/blog/best-practices-using-vary-header/ :

So now there's an object in the cache that has a little flag on it that says "only to be used for requests that have no Accept-Encoding in the request."

0
José Ricardo Júnior On

This may not answer the question but can be helpful to others.

I was having the same issue, where many of my assets were not being properly cached by Chrome. Worked fine on all other browsers, but not on Chrome. I realized that one of the difference was the 304 response i was getting on Firefox but not on Chrome. Also, some files were returned by Memory Cache and Disk Cache, but sometime it just would not.

I tried modifying the Cache-Control headers and also the ETag, but still no solution.

Then i realized that it was working fine on QA and production environments, which have valid certificates. But on dev (localhost), it was not. So i found this Chrome configuration:

chrome://flags/#allow-insecure-localhost

Enabling it just fixed by issue. Hopefully it can help others.