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