Search code examples
node.jsresponseoffline-cachinghttp-cachingimage-caching

node.js: browser image caching with correct headers


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.


Solution

  • 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