node.js: browser image caching with correct headers

Daniele P. picture Daniele P. · Mar 5, 2014 · Viewed 8.2k times · Source

I'm developing a web application that manages a large amount of images, stores and resizes them.

the request of an image is something like: domain:port/image_id/size

The server takes the image_id and if there isn't yet an image of such size it creates it and stores it on filesystem.

So everything is ok and the server is running but I need to cache those images in browser for at least one day to reduce the server bandwidth consumption.

I did several tests but nothing seems to work.

Here is the code I use to make the response header:

response.writeHead(304, {
          "Pragma": "public",
          "Cache-Control": "max-age=86400",
          "Expires": new Date(Date.now() + 86400000).toUTCString(),
          "Content-Type": contentType});
    response.write(data);
    response.end();

I also tried with response status 200. contentType is always a mime type like "image/jpg" or "image/png" data is the bytes buffer of the image.

Any advice? Thanks a lot.

live long and prosper,

d.

Answer

Daniele P. picture Daniele P. · Mar 6, 2014

I did a lot of tests and I came out with a solution that seems pretty good to manage this caching problem.

Basically what I do is getting the request and check for the request header named "if-modified-since". If I find it and the value (it is a date) is the same as the modified date of the file, the response will be a 304 status with no content. If I don't find this value or it's different from the modified date of the file, I send the complete response with status 200 and the header parameter for further access by the browser.

Here is the complete code of the working test I did:

with "working" I mean that the first request get the file from the server while the next requests get a 304 response and don't send content to the browser, that load it from local cache.

var http    = require("http");
var url     = require("url");
var fs      = require('fs');

function onRequest(request, response) {
    var pathName = url.parse(request.url).pathname;

    if (pathName!="/favicon.ico") {
        responseAction(pathName, request, response);
    } else {
        response.end();
    }
}


function responseAction(pathName, request, response) {
    console.log(pathName);

    //Get the image from filesystem
    var img = fs.readFileSync("/var/www/radar.jpg");

   //Get some info about the file
   var stats = fs.statSync("/var/www/radar.jpg");
   var mtime = stats.mtime;
   var size = stats.size;

   //Get the if-modified-since header from the request
   var reqModDate = request.headers["if-modified-since"];

   //check if if-modified-since header is the same as the mtime of the file 
   if (reqModDate!=null) {
       reqModDate = new Date(reqModDate);
           if(reqModDate.getTime()==mtime.getTime()) {
               //Yes: then send a 304 header without image data (will be loaded by cache)
               console.log("load from cache");
               response.writeHead(304, {
                   "Last-Modified": mtime.toUTCString()
               });

               response.end();
               return true;
        }
    } else {
        //NO: then send the headers and the image
        console.log("no cache");
        response.writeHead(200, {
            "Content-Type": "image/jpg",
            "Last-Modified": mtime.toUTCString(),
            "Content-Length": size
        });

        response.write(img);
        response.end();
        return true;
    }

    //IF WE ARE HERE, THERE IS A PROBLEM...
    response.writeHead(200, {
        "Content-Type": "text/plain",
    });

    response.write("ERROR");
    response.end();
    return false;
}

http.createServer(onRequest).listen(8889);
console.log("Server has started.");

Of course, I don't want to reinvent the wheel, this is a benchmark for a more complex server previously developed in php and this script is a sort of "porting" of this PHP code:

http://us.php.net/manual/en/function.header.php#61903

I hope this will help!

Please, if you find any errors or anything that could be improved let me know!

Thanks a lot, Daniele