How to check if jQuery.ajax() request header Status is “304 Not Modified”?

Without handling cache headers manually, it is not possible. Normally, 304 responses are not made available through the XHR API:

For 304 Not Modified responses that are a result of a user agent generated conditional request the user agent must act as if the server gave a 200 OK response with the appropriate content.

jQuery normally doesn’t know there was a 304 response, because the browser tells polite lies to JavaScript about what is actually happening over the network.

But there is good news (kind of): you can get Ajax to produce a 304 response, but only by manually setting the HTTP cache headers If-Modified-Since or If-None-Match in the request:

The user agent must allow setRequestHeader() to override automatic cache validation by setting request headers (e.g., If-None-Match, If-Modified-Since), in which case 304 Not Modified responses must be passed through.

So, you can use code like:

var xhr = new XMLHttpRequest();
xhr.open("GET", "foo.html");
xhr.setRequestHeader("If-Modified-Since", "Fri, 15 Feb 2013 13:43:19 GMT");
xhr.send();

One fundamental difficulty is how do you know what last-modified date or ETag to send? The browser has cache information that it uses for sending requests, but it won’t share that information with JavaScript. Fortunately, jQuery keeps track of the Last-Modified and ETag headers from Ajax responses, so you can use ifModified:true to have jQuery set those header values the next time it sends a request for that resource.

Two things to note about this:

  • 304 responses do not carry data. This is by design. The assumption is that if you have elected to use caching, you should have a copy of the data already in your cache! If getting no data from the sever is a problem (i.e., because you don’t already have that data) why are you using caching? Caching should be used when you have the old data on hand and only want new data; thus, getting back no data with a 304 should not be a problem.

  • jQuery must have a last-modified date or an ETag (to use with If-None-Match) stored from a previous request. The process goes like this:

    • First fetch: jQuery has no cache information, so it doesn’t send If-Modified-Since or If-None-Match. When the response comes back, the server may announce a last-modified data or an ETag, which jQuery stores for future use.

    • Subsequent fetches: jQuery has cache information from the last fetch and forwards that data to the server. If the resource has not changed, the Ajax request gets a 304 response. If the resource has changed, the Ajax request gets a 200 response, along with new cache information for jQuery to use for its next fetch.

    • jQuery does not persist cache information (e.g., in cookies) between page reloads, however. Therefore, the first fetch of a resource after a page reload will never be a 304, because jQuery has no cache information to send (i.e., we reset back to the “first fetch” case). There is no reason why jQuery couldn’t persist cache information, but at present it doesn’t.

The bottom line here is that you can use cache headers to get a JavaScript 304 response, but you can’t access the browser’s own ETag or last-modified date for a particular resource. So, the browser itself might know caching information about a resource, but your JavaScript code does not. In that case, the browser will use its cache headers to potentially get a real 304 response, but forward a 200 response to your JavaScript code, because JavaScript didn’t send any cache information.

It is not possible to make JavaScript 304 requests align perfectly with actual network 304 responses, because the cache information known by your browser and the cache information known by your JavaScript code may differ in unpredictable ways. However, getting 304 requests correctly most of the time is good enough for most practical development needs.

Example

Here’s a brief server example written in Node.js (but it should be simple enough to port to other langauges):

require("http").createServer(function (req, res) {
  console.log(req.headers["if-modified-since"]);

  // always send Last-Modifed header
  var lastModDate = "Fri, 13 Feb 2013 13:43:19 GMT";
  res.setHeader("Last-Modified", lastModDate);

  // if the request has a If-Modified-Since header,
  //   and it's newer than the last modification,
  //   then send a 304 response; otherwise send 200
  if(req.headers["if-modified-since"] &&
     Date.parse(lastModDate) <= Date.parse(req.headers["if-modified-since"])) {
    console.log("304 -- browser has it cached");
    res.writeHead(304, {'Content-Type': 'text/plain'});
    res.end();
  } else {
    console.log("200 -- browser needs it fresh");
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('some content');
  }

}).listen(8080);

When running this server, you can load the page in your browser and perform two different tests in your browser console:

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://stackoverflow.com/");
xhr.send();
xhr.onload = function() { console.log(xhr.status); }

This script will always see a 200 response, even if the browser supplies a If-Modified-Since request header and gets a 304 (which will happen all requests after the first, after the browser sees the server’s Last-Modifed response header).

By contrast, this script will always see 304 response:

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://stackoverflow.com/");
xhr.setRequestHeader("If-Modified-Since", "Fri, 15 Feb 2013 13:43:19 GMT");
xhr.send();
xhr.onload = function() { console.log(xhr.status); }

The script supplies its own If-Modified-Since request header (two days after the server’s last-modified date); it does not rely on whatever the browser supplies for If-Modified-Since, and therefore is allowed (per the XHR spec) to see 304 responses.

Finally, this script will always see a 200:

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://stackoverflow.com/");
xhr.setRequestHeader("If-Modified-Since", "Fri, 12 Feb 2013 13:43:19 GMT");
xhr.send();
xhr.onload = function() { console.log(xhr.status); }

This is because the script uses a If-Modified-Since that is prior to the server’s last-modified date, so the server always sends a 200. The server won’t send a 304 because it assumes the client doesn’t have a cached copy of the most recent version (i.e., the client announces that it’s seen changes since Feb 12, but there was a change on Feb 13 that the client apparently hasn’t seen).

Leave a Comment