175 lines · 4.1 KB · JavaScript
RawDownload
 /*! * Connect - staticCache * Copyright(c) 2011 Sencha Inc. * MIT Licensed */ /** * Module dependencies. */ var http = require('http')  , utils = require('../utils')  , Cache = require('../cache')  , url = require('url')  , fs = require('fs'); /** * Enables a memory cache layer on top of * the `static()` middleware, serving popular * static files. * * By default a maximum of 128 objects are * held in cache, with a max of 256k each, * totalling ~32mb. * * A Least-Recently-Used (LRU) cache algo * is implemented through the `Cache` object, * simply rotating cache objects as they are * hit. This means that increasingly popular * objects maintain their positions while * others get shoved out of the stack and * garbage collected. * * Benchmarks: * *     static(): 2700 rps *     node-static: 5300 rps *     static() + staticCache(): 7500 rps * * Options: * *   - `maxObjects`  max cache objects [128] *   - `maxLength`  max cache object length 256kb * * @param {Type} name * @return {Type} * @api public */ module.exports = function staticCache(options){  var options = options || {}    , cache = new Cache(options.maxObjects || 128)    , maxlen = options.maxLength || 1024 * 256;   return function staticCache(req, res, next){    var path = url.parse(req.url).pathname      , ranges = req.headers.range      , hit = cache.get(path)      , hitCC      , uaCC      , header      , age;     // cache static    req.on('static', function(stream){      var headers = res._headers        , cc = utils.parseCacheControl(headers['cache-control'] || '')        , contentLength = headers['content-length']        , hit;       // ignore larger files      if (!contentLength || contentLength > maxlen) return;       // dont cache items we shouldn't be      if ( cc['no-cache']        || cc['no-store']        || cc['private']        || cc['must-revalidate']) return;       // if already in cache then validate      if (hit = cache.get(path)){        if (headers.etag == hit[0].etag) {          hit[0].date = new Date;          return;        } else {          cache.remove(path);        }      }       // validation notifiactions don't contain a steam      if (null == stream) return;       // add the cache object      var arr = cache.add(path);      arr.push(headers);       // store the chunks      stream.on('data', function(chunk){        arr.push(chunk);      });       // flag it as complete      stream.on('end', function(){        arr.complete = true;      });    });     // cache hit, doesnt support range requests    if (hit && hit.complete && !ranges) {      header = utils.merge({}, hit[0]);      header.Age = age = (new Date - new Date(header.date)) / 1000 | 0;      header.date = new Date().toUTCString();       // parse cache-controls      hitCC = utils.parseCacheControl(header['cache-control'] || '');      uaCC = utils.parseCacheControl(req.headers['cache-control'] || '');       // check if we must revalidate(bypass)      if (hitCC['no-cache'] || uaCC['no-cache']) return next();       // check freshness of entity      if (isStale(hitCC, age) || isStale(uaCC, age)) return next();       // conditional GET support      if (utils.conditionalGET(req)) {        if (!utils.modified(req, res, header)) {          header['content-length'] = 0;          res.writeHead(304, header);          return res.end();        }      }       // HEAD support      if ('HEAD' == req.method) {        header['content-length'] = 0;        res.writeHead(200, header);        return res.end();      }       // respond with cache      res.writeHead(200, header);       // backpressure      function write(i) {        var buf = hit[i];        if (!buf) return res.end();        if (false === res.write(buf)) {          res.once('drain', function(){            write(++i);          });        } else {          write(++i);        }      }       return write(1);    }     next();  }}; /** * Check if cache item is stale * * @param {Object} cc * @param {Number} age * @return {Boolean} * @api private */ function isStale(cc, age) {  return cc['max-age'] && cc['max-age'] <= age;}