Skip to content

Commit 33332b4

Browse files
maxbeattymathiasbynens
authored andcommitted
Add caching (#406)
Closes #136.
1 parent dee7e8c commit 33332b4

File tree

8 files changed

+194
-22
lines changed

8 files changed

+194
-22
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ COOKIE_PASS=password-should-be-32-characters
4747
# MYSQL_PORT=3306
4848
# LOGGLY_TOKEN=
4949
# LOGGLY_SUBDOMAIN=
50+
# REDIS_HOST=
51+
# REDIS_PORT=
52+
# REDIS_PASSWORD=
5053
```
5154

5255
### Start

config.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
require('dotenv').config();
22
const Joi = require('joi');
33

4+
const prodOptional = {
5+
is: 'production',
6+
then: Joi.string().required(),
7+
otherwise: Joi.string().optional()
8+
};
9+
410
const envSchema = Joi.object().keys({
511
SCHEME: Joi.string().valid('http', 'https').optional().default('http'),
612
NODE_ENV: Joi.string().required(),
@@ -17,16 +23,15 @@ const envSchema = Joi.object().keys({
1723
MYSQL_USER: Joi.string().required(),
1824
MYSQL_PASSWORD: Joi.string().required(),
1925
MYSQL_DATABASE: Joi.string().required(),
20-
LOGGLY_TOKEN: Joi.string().when('NODE_ENV', {
26+
LOGGLY_TOKEN: Joi.string().when('NODE_ENV', prodOptional),
27+
LOGGLY_SUBDOMAIN: Joi.string().when('NODE_ENV', prodOptional),
28+
REDIS_HOST: Joi.string().when('NODE_ENV', prodOptional),
29+
REDIS_PORT: Joi.number().when('NODE_ENV', {
2130
is: 'production',
22-
then: Joi.string().required(),
23-
otherwise: Joi.string().optional()
31+
then: Joi.number().required(),
32+
otherwise: Joi.number().optional()
2433
}),
25-
LOGGLY_SUBDOMAIN: Joi.string().when('NODE_ENV', {
26-
is: 'production',
27-
then: Joi.string().required(),
28-
otherwise: Joi.string().optional()
29-
})
34+
REDIS_PASSWORD: Joi.string().when('NODE_ENV', prodOptional)
3035
}).unknown(true); // allow other keys in process.env not defined here
3136

3237
const result = Joi.validate(process.env, envSchema);
@@ -83,6 +88,11 @@ var config = {
8388
loggly: {
8489
token: result.value.LOGGLY_TOKEN,
8590
subdomain: result.value.LOGGLY_SUBDOMAIN
91+
},
92+
cache: {
93+
host: result.value.REDIS_HOST,
94+
port: result.value.REDIS_PORT,
95+
password: result.value.REDIS_PASSWORD
8696
}
8797
};
8898

manifest.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,17 @@ var manifest = {
145145
}
146146
}
147147
},
148+
{
149+
plugin: {
150+
register: './server/lib/cache',
151+
options: {
152+
host: config.get('/cache/host'),
153+
port: config.get('/cache/port'),
154+
password: config.get('/cache/password'),
155+
partition: 'jsperf'
156+
}
157+
}
158+
},
148159
{ plugin: './server/repositories/comments' },
149160
{ plugin: './server/repositories/pages' },
150161
{ plugin: './server/repositories/tests' },

package-lock.json

Lines changed: 37 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
"bell": "^8.4.0",
3535
"blipp": "2.3.0",
3636
"boom": "5.1.0",
37+
"catbox": "^7.1.4",
38+
"catbox-redis": "^3.0.1",
3739
"confidence": "3.0.2",
3840
"dotenv": "^4.0.0",
3941
"glue": "4.1.0",

server/lib/cache.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const Catbox = require('catbox');
2+
const CatboxRedis = require('catbox-redis');
3+
4+
exports.register = function (server, options, next) {
5+
if (options.host && options.host.length > 0) {
6+
server.log('info', 'connecting to redis cache');
7+
const client = new Catbox.Client(CatboxRedis, {
8+
partition: options.partition,
9+
host: options.host,
10+
port: options.port,
11+
password: options.password
12+
});
13+
14+
server.expose('get', client.get.bind(client));
15+
server.expose('set', client.set.bind(client));
16+
server.expose('drop', client.drop.bind(client));
17+
18+
client.start(next);
19+
} else {
20+
server.log('info', 'no redis config provided. caching disabled');
21+
server.expose('get', (key, callback) => { callback(null, null); });
22+
server.expose('set', (key, value, ttl, callback) => { callback(null); });
23+
server.expose('drop', () => {});
24+
next();
25+
}
26+
};
27+
28+
exports.register.attributes = {
29+
name: 'cache'
30+
};

server/web/browse/index.js

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,42 @@ exports.register = function (server, options, next) {
2525
slug: 'browse'
2626
}
2727
};
28+
const key = {
29+
segment: 'browse',
30+
id: 'browse'
31+
};
2832

29-
pagesRepo.getLatestVisible(250)
30-
.then(function (rows) {
31-
context.pages = rows;
32-
reply.view('browse/index', context);
33-
})
34-
.catch(function () {
35-
context.genError = 'Sorry. Could not find tests to browse.';
33+
server.plugins.cache.get(key, (err, cached) => {
34+
if (err) {
35+
reply(err);
36+
} else if (cached) {
37+
server.log('debug', 'cache hit on /browse');
38+
// massage data for template
39+
cached.item.forEach(r => {
40+
r.published = new Date(r.published);
41+
r.updated = new Date(r.updated);
42+
});
43+
context.pages = cached.item;
3644
reply.view('browse/index', context);
37-
});
45+
} else {
46+
server.log('debug', 'cache miss on /browse');
47+
pagesRepo.getLatestVisible(250)
48+
.then(function (rows) {
49+
context.pages = rows;
50+
server.plugins.cache.set(key, rows, 60000, (err) => {
51+
if (err) {
52+
throw err;
53+
} else {
54+
reply.view('browse/index', context);
55+
}
56+
});
57+
})
58+
.catch(function () {
59+
context.genError = 'Sorry. Could not find tests to browse.';
60+
reply.view('browse/index', context);
61+
});
62+
}
63+
});
3864
}
3965
});
4066

@@ -116,5 +142,5 @@ exports.register = function (server, options, next) {
116142

117143
exports.register.attributes = {
118144
name: 'web/browse',
119-
dependencies: ['repositories/pages']
145+
dependencies: ['repositories/pages', 'cache']
120146
};

test/unit/server/web/browse/index.js

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,23 @@ MockRepo.register.attributes = {
1919
name: 'repositories/pages'
2020
};
2121

22+
const MockCache = {
23+
register: (server, options, next) => {
24+
server.expose('get', (key, cb) => { cb(null, null); });
25+
server.expose('set', (key, val, ttl, cb) => { cb(null); });
26+
next();
27+
}
28+
};
29+
30+
MockCache.register.attributes = {
31+
name: 'cache'
32+
};
33+
2234
const lab = exports.lab = Lab.script();
2335
let request, server, getLatestVisibleStub, getLatestVisibleForAuthorStub;
2436

2537
lab.beforeEach(function (done) {
26-
const plugins = [ MockRepo, BrowsePlugin ];
38+
const plugins = [ MockRepo, MockCache, BrowsePlugin ];
2739
server = new Hapi.Server();
2840
server.connection();
2941
server.register(require('vision'), () => {
@@ -78,6 +90,51 @@ lab.experiment('browse', function () {
7890
done();
7991
});
8092
});
93+
94+
lab.test('it responds with error from cache get', (done) => {
95+
const testErr = new Error('testing get');
96+
sinon.stub(server.plugins.cache, 'get').callsArgWith(1, testErr);
97+
98+
server.inject(request, function (response) {
99+
Code.expect(response.statusCode).to.equal(500);
100+
101+
done();
102+
});
103+
});
104+
105+
lab.test('it responds with items from cache get', (done) => {
106+
sinon.stub(server.plugins.cache, 'get').callsArgWith(1, null, {
107+
item: [
108+
{
109+
url: 'test',
110+
title: 'Test',
111+
testCount: 2,
112+
revisionCount: 1,
113+
updated: new Date().toISOString(),
114+
published: new Date().toISOString()
115+
}
116+
]
117+
});
118+
119+
server.inject(request, function (response) {
120+
Code.expect(response.statusCode).to.equal(200);
121+
122+
done();
123+
});
124+
});
125+
126+
lab.test('it responds with generic error when cache set errors', (done) => {
127+
getLatestVisibleStub.returns(Promise.resolve([]));
128+
const testErr = new Error('testing set');
129+
sinon.stub(server.plugins.cache, 'set').callsArgWith(3, testErr);
130+
131+
server.inject(request, function (response) {
132+
Code.expect(response.statusCode).to.equal(200);
133+
Code.expect(response.result).to.contain('Sorry. Could not find tests to browse.');
134+
135+
done();
136+
});
137+
});
81138
});
82139

83140
lab.experiment('atom', function () {

0 commit comments

Comments
 (0)