Skip to content

Commit dacc84d

Browse files
committed
added caching functionality
1 parent c5c13bc commit dacc84d

File tree

8 files changed

+196
-86
lines changed

8 files changed

+196
-86
lines changed

config/development.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ module.exports = {
88
secret: process.env.SECRET || 'lakikihdgdfdjjjdgd67264664vdjhjdyncmxuei8336%%^#%gdvdhj????jjhdghduue',
99
mongoURL: process.env.MONGOLAB_URL || 'mongodb://192.168.99.100/snipe',
1010
noFrontendCaching: process.env.NO_CACHE || 'yes',
11+
frontendCacheExpiry: process.env.FRONTEND_CACHE_EXPIRY || '90',
12+
backendCacheExpiry: process.env.BACKEND_CACHE_EXPIRY || '90',
1113
rateLimit: process.env.RATE_LIMIT || '1800',
1214
rateLimitExpiry: process.env.RATE_LIMIT_EXPIRY || '3600000',
1315
redisURL: process.env.REDIS_URL || 'redis://192.168.99.100:6379/1',

config/production.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ module.exports = {
88
secret: process.env.SECRET || 'lakikihdgdfdjjjdgd67264660okjnbgtrdxswerfgytg373745ei8336%%^#%gdvdhj????jjhdghduue',
99
mongoURL: process.env.MONGOLAB_URL || 'mongodb://192.168.99.100/snipe',
1010
noFrontendCaching: process.env.NO_CACHE || 'no',
11+
frontendCacheExpiry: process.env.FRONTEND_CACHE_EXPIRY || '90',
12+
backendCacheExpiry: process.env.BACKEND_CACHE_EXPIRY || '90',
1113
rateLimit: process.env.RATE_LIMIT || '1800',
1214
rateLimitExpiry: process.env.RATE_LIMIT_EXPIRY || '3600000',
1315
redisURL: process.env.REDIS_URL || 'redis://192.168.99.100:6379/1',

favicon.ico

-14.7 KB
Binary file not shown.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"body-parser": "^1.17.1",
4242
"bugsnag": "^1.9.1",
4343
"cacheman": "^2.2.1",
44+
"cacheman-redis": "^1.1.2",
4445
"cluster": "^0.7.7",
4546
"cors": "^2.8.3",
4647
"crypto": "0.0.3",

routes/index.js

Lines changed: 87 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ var me = require('../package.json');
99
var initialize = require('./initialize');
1010
var config = require('../config');
1111
var helmet = require('helmet');
12-
var client = require('../services/database/redis');
13-
var limiter = require('express-limiter')(router, client);
12+
var redisClient = require('../services/database/redis');
13+
var limiter = require('express-limiter')(router, redisClient);
1414
var _ = require('lodash');
1515
var bodyParser = require('body-parser');
1616
var cors = require('cors');
@@ -20,45 +20,93 @@ var MAX_CONTENT_LENGTH_ACCEPTED = config.maxContentLength * 1;
2020
var url = require('url');
2121
var fnv = require('fnv-plus');
2222
var RequestLogs = require('../models/RequestLogs');
23+
var Cacheman = require('cacheman');
24+
var EngineRedis = require('cacheman-redis');
2325

2426
router._sanitizeRequestUrl = function(req) {
2527
var requestUrl = url.format({
2628
protocol: req.protocol,
2729
host: req.hostname,
2830
pathname: req.originalUrl || req.url,
2931
query: req.query
30-
});
32+
});
3133

3234
return requestUrl.replace(/(password=).*?(&|$)/ig, '$1<hidden>$2');
3335
};
3436

3537
router._allRequestData = function(req,res,next){
36-
var requestData = {};
37-
req.param = function(key, defaultValue){
38-
var newRequestData = _.assignIn(requestData, req.params, req.body, req.query);
39-
if(newRequestData[key]){
40-
return newRequestData[key];
41-
}else if(defaultValue){
42-
return defaultValue;
43-
}else{
44-
return false;
45-
}
46-
};
47-
next();
38+
var requestData = {};
39+
req.param = function(key, defaultValue){
40+
var newRequestData = _.assignIn(requestData, req.params, req.body, req.query);
41+
if(newRequestData[key]){
42+
return newRequestData[key];
43+
}else if(defaultValue){
44+
return defaultValue;
45+
}else{
46+
return false;
47+
}
48+
};
49+
next();
4850
};
4951

5052
router._enforceUserIdAndAppId = function(req,res,next){
51-
var userId = req.param('userId');
52-
var appId = req.param('appId');
53-
if(!userId){
54-
return res.badRequest(false,'No userId parameter was passed in the payload of this request. Please pass a userId.');
55-
}else if(!appId){
56-
return res.badRequest(false,'No appId parameter was passed in the payload of this request. Please pass an appId.');
57-
}else{
58-
req.userId = userId;
59-
req.appId = appId;
53+
var userId = req.param('userId');
54+
var appId = req.param('appId');
55+
if(!userId){
56+
return res.badRequest(false,'No userId parameter was passed in the payload of this request. Please pass a userId.');
57+
}else if(!appId){
58+
return res.badRequest(false,'No appId parameter was passed in the payload of this request. Please pass an appId.');
59+
}else{
60+
req.userId = userId;
61+
req.appId = appId;
62+
next();
63+
}
64+
};
65+
66+
router._APICache = function(req,res,next){
67+
var cache = new EngineRedis(redisClient);
68+
var APICache = new Cacheman(me.name, {engine: cache, ttl: config.backendCacheExpiry});
69+
req.cache = APICache;
70+
// Tell Frontend to Cache responses
71+
res.set({'Cache-Control':'private, max-age='+config.frontendCacheExpiry+''});
72+
73+
var key = [];
74+
key.push(req.url);
75+
key.push(req.ip);
76+
key.push(req.get('user-agent'));
77+
if(req.userId){
78+
key.push(req.userId);
79+
}
80+
if(req.appId){
81+
key.push(req.appId);
82+
}
83+
req.cacheKey = key;
84+
// Remember to delete cache when you get a POST call
85+
// Only cache GET calls
86+
if(req.method === 'GET'){
87+
// if record is not in cache, set cache else get cache
88+
req.cache.get(req.cacheKey)
89+
.then(function(resp){
90+
if(!resp){
91+
// Will be set on successful response
6092
next();
93+
}else{
94+
res.ok(resp, true);
95+
}
96+
})
97+
.catch(function(err){
98+
log.error('Failed to get cached data: ', err);
99+
// Don't block the call because of this failure.
100+
next();
101+
});
102+
}else{
103+
if(req.method === 'POST' || req.method === 'PUT' || req.method === 'PUSH'){
104+
req.cache.del(req.cacheKey)
105+
.then(); // No delays
61106
}
107+
next();
108+
}
109+
62110
};
63111

64112
// Log requests here
@@ -77,11 +125,11 @@ router.use(function(req,res,next){
77125
user: req.userId,
78126
device: req.get('user-agent'),
79127
createdAt: new Date()
80-
};
128+
};
81129
// ToDo: Move this to a queue. Not good for performance
82130
RequestLogs.create(reqLog)
83131
.then(function(res){
84-
return _.identity(res);
132+
return _.identity(res);
85133
})
86134
.catch(function(err){
87135
log.error(err);
@@ -94,10 +142,6 @@ RequestLogs.create(reqLog)
94142
});
95143

96144
router.use(helmet());
97-
// no client side caching
98-
if(config.noFrontendCaching === 'yes'){
99-
router.use(helmet.noCache());
100-
}
101145
router.use(cors());
102146
router.options('*', cors());
103147
router.use(bodyParser.urlencoded({ extended: false }));
@@ -119,19 +163,26 @@ limiter({
119163
expire: config.rateLimitExpiry * 1,
120164
onRateLimited: function (req, res, next) {
121165
next({ message: 'Rate limit exceeded', statusCode: 429 });
122-
}
166+
}
123167
});
124168

125169
router.use(response);
126170
router.use(expressValidator());
127171

172+
// no client side caching
173+
if(config.noFrontendCaching === 'yes'){
174+
router.use(helmet.noCache());
175+
}else{
176+
router.use(router._APICache);
177+
}
178+
128179
router.get('/', function (req, res) {
129-
res.ok({name: me.name, version: me.version});
180+
res.ok({name: me.name, version: me.version});
130181
});
131182

132183
// Let's Encrypt Setup
133184
router.get(config.letsencryptSSLVerificationURL, function(req,res){
134-
res.send(config.letsencryptSSLVerificationBody);
185+
res.send(config.letsencryptSSLVerificationBody);
135186
});
136187

137188
router.use('/', initialize);
@@ -149,9 +200,11 @@ router.use(router._enforceUserIdAndAppId);
149200
//
150201

151202
router.use(function(req, res, next) { // jshint ignore:line
152-
res.notFound();
203+
res.notFound();
153204
});
154205

155206
router.use(log.errorHandler);
156207

157208
module.exports = router;
209+
210+
// ToDo: Implement a Queue system

services/response/ok.js

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ var debug = require('debug')('response');
66
var RequestLogs = require('../../models/RequestLogs');
77
var _ = require('lodash');
88

9-
module.exports = function(data){
9+
module.exports = function(data, cache){
1010
debug("sending ok response");
1111
var req = this.req;
1212
var res = this;
1313
// ToDo: Move this to a queue. Not good for performance
14-
RequestLogs.update({RequestId: req.requestId},{response: {status: 'success', data: data}})
14+
RequestLogs.update({RequestId: req.requestId},{response: {status: 'success', data: data, cached: cache}})
1515
.then(function(res){
1616
return _.identity(res);
1717
})
@@ -29,20 +29,40 @@ module.exports = function(data){
2929
debug("about to call encryption method");
3030
encryption.encrypt(text, key)
3131
.then(function(resp){
32-
debug("got response from encryption method: ",resp);
33-
log.info('Sending ok response: ', data);
34-
res.status(200).json({status: 'success', data: resp, secure: true});
35-
})
32+
debug("got response from encryption method: ",resp);
33+
log.info('Sending ok response: ', data);
34+
res.status(200).json({status: 'success', data: resp, secure: true});
35+
})
3636
.catch(function(err){
37-
debug("got error from encryption method: ", err);
38-
res.serverError(err,'Error encrypting response.');
39-
});
37+
debug("got error from encryption method: ", err);
38+
res.serverError(err,'Error encrypting response.');
39+
});
4040
}else{
4141
log.info('Sending ok response: ', data);
4242
if(data){
43-
res.status(200).json({status: 'success', data: data});
44-
}else{
45-
res.status(200).json({status: 'success'});
46-
}
43+
// Only cache GET calls
44+
if(req.method === 'GET'){
45+
46+
// If this is a cached response, show response else cache the response and show response.
47+
if(cache){
48+
res.status(200).json({status: 'success', data: data, cached: true});
49+
}else{
50+
// req.cacheKey
51+
req.cache.set(req.cacheKey,data)
52+
.then(function(resp){
53+
res.status(200).json({status: 'success', data: data});
54+
})
55+
.catch(function(err){
56+
log.error('Failed to cache data: ', err);
57+
// This error shouldn't stop our response
58+
res.status(200).json({status: 'success', data: data});
59+
});
60+
}
61+
}else{
62+
res.status(200).json({status: 'success', data: data});
63+
}
64+
}else{
65+
res.status(200).json({status: 'success'});
66+
}
4767
}
4868
};

test/routes/index.js

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,45 @@ app.use('/',router);
2121
var agent = request.agent(app);
2222
var requestId;
2323

24+
var res = {};
25+
var req = {};
26+
27+
var nextChecker = false;
28+
var next = function(){
29+
if(arguments.length > 0){
30+
console.log(arguments[0]);
31+
}else{
32+
nextChecker = true;
33+
}
34+
35+
return nextChecker;
36+
};
37+
res.json = function(data){
38+
return res;
39+
};
40+
41+
res.badRequest = sinon.spy();
42+
43+
res.status = function(status){
44+
return res;
45+
};
46+
47+
var header = {};
48+
res.set = function(key, value){
49+
header[key] = value;
50+
return header[key];
51+
};
52+
req.get = function(key){
53+
return header[key];
54+
};
55+
56+
header.set = function(data){
57+
header.temp = data;
58+
return header.temp;
59+
};
60+
61+
req.method = '';
62+
2463
describe('Test rate limiting', function(){
2564

2665
it('should reach request rate limit', function(done){
@@ -104,45 +143,6 @@ it('should save rate limit error on request log', function(done){
104143

105144
});
106145

107-
var res = {};
108-
var req = {};
109-
110-
var nextChecker = false;
111-
var next = function(){
112-
if(arguments.length > 0){
113-
console.log(arguments[0]);
114-
}else{
115-
nextChecker = true;
116-
}
117-
118-
return nextChecker;
119-
};
120-
res.json = function(data){
121-
return res;
122-
};
123-
124-
res.badRequest = sinon.spy();
125-
126-
res.status = function(status){
127-
return res;
128-
};
129-
130-
var header = {};
131-
res.set = function(key, value){
132-
header[key] = value;
133-
return header[key];
134-
};
135-
req.get = function(key){
136-
return header[key];
137-
};
138-
139-
header.set = function(data){
140-
header.temp = data;
141-
return header.temp;
142-
};
143-
144-
req.method = '';
145-
146146
it('should contain a param function', function(done){
147147
router._allRequestData(req, res, next);
148148
nextChecker.should.be.true; /* jslint ignore:line */
@@ -171,3 +171,18 @@ it('should enforce UserId', function(done){
171171
});
172172

173173
});
174+
175+
176+
describe('Cache Test', function(){
177+
it('should initialize the API cache', function(done){
178+
res.set = sinon.spy();
179+
router._APICache(req, res, next);
180+
nextChecker.should.be.true; /* jslint ignore:line */
181+
nextChecker = false;
182+
req.cache.should.be.a('object');
183+
req.cacheKey.should.be.a('array');
184+
res.set.should.be.called.once; /* jslint ignore:line */
185+
res.set.should.be.calledWith({'Cache-Control':'private, max-age='+config.frontendCacheExpiry+''});
186+
done();
187+
});
188+
});

0 commit comments

Comments
 (0)