2

I have a requirement where I need to support downgrading of my server code.

I have the following line in my nginx configuration file, indicating that the browser can cache the page but has to validate with server to check if the file changed.

add_header Cache-Control "no-cache";

This setup works perfectly fine for me with all the upgrades done on my server code.

But when it comes to downgrading a resource to an older version, when the browser tries to validate the resource change, nginx says that the resource hasn't changed, so the browser shows the cached(newer) resource instead of the downgraded(older) resource.

As a workaround, I could use the following setting to disable cache completely, but its not efficient, and I would like to have caching.

add_header Cache-Control "no-store";

So how would I make nginx recognize downgrades??

Schu
  • 123
  • 3
  • Just to add mode retails. With "no-cache" setting, when a resource is upgraded, browser gets **200 OK** status and when a resource is downgraded, browser get **304 NOT MODIFIED** status. – Schu Jan 07 '12 at 21:08
  • I believe nginx uses timestamps to determine modification - try to 'touch' the file - update the timestamp - and see if it fixes your issue. (I haven't quite managed to figure out your scenario - but I am presuming you have older static files, you upgrade them (keeping the old files), and then at some point change your links to point back to the older files). Add some detail to your question describing whether these are static or dyanmic files, a sample url (do you use query strings), and something about this 'downgrade' process. (Also see if you have any luck with adding 'must-revalidate'). – cyberx86 Jan 08 '12 at 02:52
  • By upgrade I mean, replace the server code(includes static resources) with newer version from source control, By downgrade I mean the opposite, replace the code with older version from source control. At any point of time, there is only one set of files on the server. No I dont have any query strings. All references are from html files to javascript files using script tags. – Schu Jan 09 '12 at 04:05
  • I use dpkg for upgrade/downgrade process and it also probably uses timestamps to detect code change. I fear 'touch'ing the file would mess it up. I will try it though. – Schu Jan 09 '12 at 04:09
  • I tried must-revalidate, it behaves identical to no-cache, so it doesn't work. I tried touching the files and it work fine, i still have to verify its impact on dpkg though. – Schu Jan 09 '12 at 14:44
  • Good to know you are having some success. There is 'arguably' another way that comes to mind - not the most efficient, but it may work. You can manually set the 'Last Modified' header in your nginx config, which will over-ride the default. So essentially, when you roll out old code, set the date with add_headers (you don't need headers_more() for this one). The downside is that if you legitimately modify a file, the header won't be updated to reflect that. – cyberx86 Jan 09 '12 at 15:03
  • The way our server system is setup, there should not be any file modifications outside upgrade/downgrade. So theoretically this should work as long as we update the config file setting on upgrade/downgrade and this would also not mess with file data so no complications for dpkg. I will try it. Thank you very much for the help. – Schu Jan 09 '12 at 16:23
  • Perfect! updating Last-Modified forces cache refresh. Thanks again. Can you put this as the answer? – Schu Jan 09 '12 at 16:52
  • Glad it worked out for you - I have posted the answer. It may be possible to use Nginx's perl module to script something which compares the timestamp on the file to a timestamp you have specified, and sets Last-Modified to the later of the file timestamp or your specified date. You may also be able to script the comparison of $http_if_modified_since and your specified time to determine what to set the Last-Modified header to. Both of these approaches though, add more complexity and overhead to every request. – cyberx86 Jan 09 '12 at 17:19

2 Answers2

1

In a no-cache scenario, the browser will usually issue an If-Modified-Since header with a GET request, to see if the file in question has changed from the cached copy.

The server will either:

  1. return a 304 - Not Modified response
  2. return another code:
    • 200 - OK if the modified date on the server is later than that passed with the header
    • Other - any other code that might be returned (4xx, 5xx, etc)

Nginx will use the modified timestamp on the file it is serving to generate the Last Modified header and for comparison against If-Modified-Since.

Due to this, updating the timestamp of a file with touch /path/to/myfile.ext will cause nginx to identify it as having been modified after the If-Modified-Since date and will allow nginx to serve the file.

Alternatively, you should be able to force a re-fetch by explicitly specifying a 'Last Modified' header in your nginx config that is after the anticipated date of If-Modified-Since. In your scenario, this would essentially entail:

  1. Downgrade your code
  2. Modify nginx config with current date (e.g.): add_header Last-Modified Mon, 09 Jan 2012 17:07:00 GMT
  3. Reload nginx

An important point of mention is that if your static assets change after this, the hard-coded header will not reflect the change (i.e. even when you 'upgrade' your code, you will still have to manually alter the config.

cyberx86
  • 20,620
  • 1
  • 60
  • 80
  • Actually in the scenario of static file being updated after the Last-Modified being set on the config file, the server issues a 200-OK, given we also have the no-cache setting. This means that even if one of Cache-Control or Last-Modified says the file is modified, the final server response would be 200 OK. – Schu Jan 09 '12 at 17:41
  • @Schu: Interesting, no-cache only requires that the browser revalidate all requests with the origin server. I would expect that if Last-Modified can override an 'older' timestamp, that it should also do the same for a newer one (and therefore return 304). I think there is something additional going on - as I usually would not expect setting a response header to alter the way a request header is processed. – cyberx86 Jan 09 '12 at 17:53
  • I was thinking of it as "override" as well, but its the server response that led me to the conclusion of "either-or". Assuming older version is in cache, i get the following responses 1. just no-cache - 200 ;2. just last-modified - 304; 3. both no-cache and last-modified - 200 – Schu Jan 09 '12 at 20:27
0

First of all, "no-cache" doesn't necessarily mean what you think it means in practice. I suspect you really want to use "Cache-Control: max-age=0, must-revalidate". Some browsers (Firefox) incorrectly treat "no-cache" as an equivalent of "no-store", and don't cache anything on disk at all. They simply re-fetch all content from the source at every page load, which is a terrible waste of bandwidth and sucks for the user experience.

Secondly, your particular use case is exactly what ETags were designed for, rather than the simple forward-only date logic that is used without them. (The entire linked site is great reading on caching, not just on e-tags).

Finally, you should consider naming your resources with some form of date identifier (or better yet content hash) so that you never have to worry about this sort of thing. You can then set cache-control to 1 year. This requires workflow changes and build scripts though for many sites to rename files and replace links. But that is what Google and the other big boys do...

rmalayter
  • 3,744
  • 19
  • 27
  • Thanks for site link. There's lot of stuff there for me to know. I was testing on chrome and its doing it right ie., can cache but re-validate. But you are right, I have to avoid no-cache for efficiency on browsers with no-store interpretation. Coming to Etags, unfortunately the nginx I use doesn't generate them, so all I can do is put a static ETag which in essence would be equal to a LastModified setting. – Schu Jan 11 '12 at 22:11