arnaucube
arnaucube/raspberryGPIOhtmlserverPublic Self-hosted repository
raspberryGPIOhtmlserver/node_modules/connect/lib/middleware/staticCache.js
node_modules/connect/lib/middleware/staticCache.jsRaw
  1
  2/*!
  3 * Connect - staticCache
  4 * Copyright(c) 2011 Sencha Inc.
  5 * MIT Licensed
  6 */
  7
  8/**
  9 * Module dependencies.
 10 */
 11
 12var utils = require('../utils')
 13  , Cache = require('../cache')
 14  , fresh = require('fresh');
 15
 16/**
 17 * Static cache:
 18 *
 19 * Enables a memory cache layer on top of
 20 * the `static()` middleware, serving popular
 21 * static files.
 22 *
 23 * By default a maximum of 128 objects are
 24 * held in cache, with a max of 256k each,
 25 * totalling ~32mb.
 26 *
 27 * A Least-Recently-Used (LRU) cache algo
 28 * is implemented through the `Cache` object,
 29 * simply rotating cache objects as they are
 30 * hit. This means that increasingly popular
 31 * objects maintain their positions while
 32 * others get shoved out of the stack and
 33 * garbage collected.
 34 *
 35 * Benchmarks:
 36 *
 37 *     static(): 2700 rps
 38 *     node-static: 5300 rps
 39 *     static() + staticCache(): 7500 rps
 40 *
 41 * Options:
 42 *
 43 *   - `maxObjects`  max cache objects [128]
 44 *   - `maxLength`  max cache object length 256kb
 45 *
 46 * @param {Object} options
 47 * @return {Function}
 48 * @api public
 49 */
 50
 51module.exports = function staticCache(options){
 52  var options = options || {}
 53    , cache = new Cache(options.maxObjects || 128)
 54    , maxlen = options.maxLength || 1024 * 256;
 55
 56  console.warn('connect.staticCache() is deprecated and will be removed in 3.0');
 57  console.warn('use varnish or similar reverse proxy caches.');
 58
 59  return function staticCache(req, res, next){
 60    var key = cacheKey(req)
 61      , ranges = req.headers.range
 62      , hasCookies = req.headers.cookie
 63      , hit = cache.get(key);
 64
 65    // cache static
 66    // TODO: change from staticCache() -> cache()
 67    // and make this work for any request
 68    req.on('static', function(stream){
 69      var headers = res._headers
 70        , cc = utils.parseCacheControl(headers['cache-control'] || '')
 71        , contentLength = headers['content-length']
 72        , hit;
 73
 74      // dont cache set-cookie responses
 75      if (headers['set-cookie']) return hasCookies = true;
 76
 77      // dont cache when cookies are present
 78      if (hasCookies) return;
 79
 80      // ignore larger files
 81      if (!contentLength || contentLength > maxlen) return;
 82
 83      // don't cache partial files
 84      if (headers['content-range']) return;
 85
 86      // dont cache items we shouldn't be
 87      // TODO: real support for must-revalidate / no-cache
 88      if ( cc['no-cache']
 89        || cc['no-store']
 90        || cc['private']
 91        || cc['must-revalidate']) return;
 92
 93      // if already in cache then validate
 94      if (hit = cache.get(key)){
 95        if (headers.etag == hit[0].etag) {
 96          hit[0].date = new Date;
 97          return;
 98        } else {
 99          cache.remove(key);
100        }
101      }
102
103      // validation notifiactions don't contain a steam
104      if (null == stream) return;
105
106      // add the cache object
107      var arr = [];
108
109      // store the chunks
110      stream.on('data', function(chunk){
111        arr.push(chunk);
112      });
113
114      // flag it as complete
115      stream.on('end', function(){
116        var cacheEntry = cache.add(key);
117        delete headers['x-cache']; // Clean up (TODO: others)
118        cacheEntry.push(200);
119        cacheEntry.push(headers);
120        cacheEntry.push.apply(cacheEntry, arr);
121      });
122    });
123
124    if (req.method == 'GET' || req.method == 'HEAD') {
125      if (ranges) {
126        next();
127      } else if (!hasCookies && hit && !mustRevalidate(req, hit)) {
128        res.setHeader('X-Cache', 'HIT');
129        respondFromCache(req, res, hit);
130      } else {
131        res.setHeader('X-Cache', 'MISS');
132        next();
133      }
134    } else {
135      next();
136    }
137  }
138};
139
140/**
141 * Respond with the provided cached value.
142 * TODO: Assume 200 code, that's iffy.
143 *
144 * @param {Object} req
145 * @param {Object} res
146 * @param {Object} cacheEntry
147 * @return {String}
148 * @api private
149 */
150
151function respondFromCache(req, res, cacheEntry) {
152  var status = cacheEntry[0]
153    , headers = utils.merge({}, cacheEntry[1])
154    , content = cacheEntry.slice(2);
155
156  headers.age = (new Date - new Date(headers.date)) / 1000 || 0;
157
158  switch (req.method) {
159    case 'HEAD':
160      res.writeHead(status, headers);
161      res.end();
162      break;
163    case 'GET':
164      if (utils.conditionalGET(req) && fresh(req.headers, headers)) {
165        headers['content-length'] = 0;
166        res.writeHead(304, headers);
167        res.end();
168      } else {
169        res.writeHead(status, headers);
170
171        function write() {
172          while (content.length) {
173            if (false === res.write(content.shift())) {
174              res.once('drain', write);
175              return;
176            }
177          }
178          res.end();
179        }
180
181        write();
182      }
183      break;
184    default:
185      // This should never happen.
186      res.writeHead(500, '');
187      res.end();
188  }
189}
190
191/**
192 * Determine whether or not a cached value must be revalidated.
193 *
194 * @param {Object} req
195 * @param {Object} cacheEntry
196 * @return {String}
197 * @api private
198 */
199
200function mustRevalidate(req, cacheEntry) {
201  var cacheHeaders = cacheEntry[1]
202    , reqCC = utils.parseCacheControl(req.headers['cache-control'] || '')
203    , cacheCC = utils.parseCacheControl(cacheHeaders['cache-control'] || '')
204    , cacheAge = (new Date - new Date(cacheHeaders.date)) / 1000 || 0;
205
206  if ( cacheCC['no-cache']
207    || cacheCC['must-revalidate']
208    || cacheCC['proxy-revalidate']) return true;
209
210  if (reqCC['no-cache']) return true;
211
212  if (null != reqCC['max-age']) return reqCC['max-age'] < cacheAge;
213
214  if (null != cacheCC['max-age']) return cacheCC['max-age'] < cacheAge;
215
216  return false;
217}
218
219/**
220 * The key to use in the cache. For now, this is the URL path and query.
221 *
222 * 'http://example.com?key=value' -> '/?key=value'
223 *
224 * @param {Object} req
225 * @return {String}
226 * @api private
227 */
228
229function cacheKey(req) {
230  return utils.parseUrl(req).path;
231}