Skip to content

Commit f576e19

Browse files
committed
Merge pull request #3 from ember-fastboot/caching
Add support for caching layer
2 parents f66e712 + 71f3ca4 commit f576e19

6 files changed

Lines changed: 123 additions & 32 deletions

File tree

lib/express-http-server.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ class ExpressHTTPServer {
77
constructor(options) {
88
options = options || {};
99

10+
this.ui = options.ui;
1011
this.username = options.username;
1112
this.password = options.password;
13+
this.cache = options.cache;
14+
1215
this.app = express();
13-
this.ui = options.ui;
1416
}
1517

1618
serve(middleware) {
@@ -23,6 +25,10 @@ class ExpressHTTPServer {
2325
app.use(basicAuth(username, password));
2426
}
2527

28+
if (this.cache) {
29+
app.get('/*', this.buildCacheMiddleware());
30+
}
31+
2632
app.get('/*', middleware);
2733

2834
return new Promise(resolve => {
@@ -36,6 +42,46 @@ class ExpressHTTPServer {
3642
});
3743
});
3844
}
45+
46+
buildCacheMiddleware() {
47+
return (req, res, next) => {
48+
let path = req.path;
49+
50+
Promise.resolve(this.cache.fetch(path, req))
51+
.then(response => {
52+
if (response) {
53+
this.ui.writeLine(`cache hit; path=${path}`);
54+
res.send(response);
55+
} else {
56+
this.ui.writeLine(`cache miss; path=${path}`);
57+
this.interceptResponseCompletion(path, res);
58+
next();
59+
}
60+
})
61+
.catch(() => next());
62+
};
63+
}
64+
65+
interceptResponseCompletion(path, res) {
66+
let send = res.send.bind(res);
67+
68+
res.send = (body) => {
69+
let ret = send(body);
70+
71+
this.cache.put(path, body)
72+
.then(() => {
73+
this.ui.writeLine(`stored in cache; path=${path}`);
74+
})
75+
.catch(() => {
76+
let truncatedBody = body.replace(/\n/g).substr(0, 200);
77+
this.ui.writeLine(`error storing cache; path=${path}; body=${truncatedBody}...`);
78+
});
79+
80+
res.send = send;
81+
82+
return ret;
83+
};
84+
}
3985
}
4086

4187
module.exports = ExpressHTTPServer;

lib/fastboot-app-server.js

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,47 @@
33
const assert = require('assert');
44
const cluster = require('cluster');
55
const os = require('os');
6-
const path = require('path');
76

8-
const WORKER_PATH = path.join(__dirname, './worker.js');
7+
const Worker = require('./worker');
98

109
class FastBootAppServer {
1110
constructor(options) {
1211
options = options || {};
1312

14-
this.downloader = options.downloader;
1513
this.distPath = options.distPath;
14+
this.downloader = options.downloader;
1615
this.notifier = options.notifier;
17-
this.workerCount = options.workerCount || os.cpus().length;
16+
this.cache = options.cache;
1817
this.ui = options.ui;
1918

2019
if (!this.ui) {
2120
let UI = require('./ui');
2221
this.ui = new UI();
23-
this.propagateUI();
2422
}
2523

26-
assert(this.distPath || this.downloader, "FastBootAppServer must be provided with either a distPath or a downloader option.");
27-
assert(!(this.distPath && this.downloader), "FastBootAppServer must be provided with either a distPath or a downloader option, but not both.");
24+
this.propagateUI();
25+
26+
if (cluster.isWorker) {
27+
this.worker = new Worker({
28+
ui: this.ui,
29+
distPath: this.distPath || process.env.FASTBOOT_DIST_PATH,
30+
cache: this.cache
31+
});
32+
33+
this.worker.start();
34+
} else {
35+
this.workerCount = options.workerCount ||
36+
(process.env.NODE_ENV === 'test' ? 1 : null) ||
37+
os.cpus().length;
38+
39+
assert(this.distPath || this.downloader, "FastBootAppServer must be provided with either a distPath or a downloader option.");
40+
assert(!(this.distPath && this.downloader), "FastBootAppServer must be provided with either a distPath or a downloader option, but not both.");
41+
}
2842
}
2943

3044
start() {
45+
if (cluster.isWorker) { return; }
46+
3147
return this.initializeApp()
3248
.then(() => this.subscribeToNotifier())
3349
.then(() => this.forkWorkers())
@@ -44,6 +60,7 @@ class FastBootAppServer {
4460
propagateUI() {
4561
if (this.downloader) { this.downloader.ui = this.ui; }
4662
if (this.notifier) { this.notifier.ui = this.ui; }
63+
if (this.cache) { this.cache.ui = this.ui; }
4764
}
4865

4966
initializeApp() {
@@ -118,10 +135,6 @@ class FastBootAppServer {
118135
}
119136

120137
forkWorker() {
121-
cluster.setupMaster({
122-
exec: WORKER_PATH
123-
});
124-
125138
let env = this.buildWorkerEnv();
126139
let worker = cluster.fork(env);
127140

lib/worker.js

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@
33
const FastBoot = require('fastboot');
44
const fastbootMiddleware = require('fastboot-express-middleware');
55
const ExpressHTTPServer = require('./express-http-server');
6-
const UI = require('./ui');
76

87
class Worker {
98
constructor(options) {
109
this.distPath = options.distPath;
1110
this.httpServer = options.httpServer;
1211
this.ui = options.ui;
12+
this.cache = options.cache;
1313

1414
if (!this.httpServer) {
15-
this.httpServer = new ExpressHTTPServer();
16-
this.httpServer.ui = this.ui;
15+
this.httpServer = new ExpressHTTPServer({
16+
ui: this.ui,
17+
cache: this.cache
18+
});
1719
}
1820
}
1921

@@ -75,9 +77,4 @@ class Worker {
7577
}
7678
}
7779

78-
let worker = new Worker({
79-
distPath: process.env.FASTBOOT_DIST_PATH || false,
80-
ui: new UI()
81-
});
82-
83-
worker.start();
80+
module.exports = Worker;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "A production-ready app server for running Ember FastBoot apps",
55
"main": "lib/fastboot-app-server.js",
66
"scripts": {
7-
"test": "mocha"
7+
"test": "NODE_ENV=test mocha"
88
},
99
"repository": {
1010
"type": "git",

test/app-server-test.js

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
"use strict";
22

3+
const path = require('path');
4+
const fork = require('child_process').fork;
35
const expect = require('chai').expect;
46
const FastBootAppServer = require('../lib/fastboot-app-server');
57
const request = require('request-promise').defaults({ simple: false, resolveWithFullResponse: true });
68

9+
let server;
10+
711
describe("FastBootAppServer", function() {
812
this.timeout(3000);
913

14+
afterEach(function() {
15+
if (server) {
16+
server.kill();
17+
}
18+
});
19+
1020
it("throws if no distPath or downloader is provided", function() {
1121
expect(() => {
1222
new FastBootAppServer();
@@ -23,16 +33,7 @@ describe("FastBootAppServer", function() {
2333
});
2434

2535
it("serves an HTTP 500 response if the app can't be found", function() {
26-
let server = new FastBootAppServer({
27-
workerCount: 1,
28-
downloader: {
29-
download() {
30-
return Promise.resolve();
31-
}
32-
}
33-
});
34-
35-
return server.start()
36+
return runServer('not-found-server')
3637
.then(() => request('http://localhost:3000'))
3738
.then(response => {
3839
expect(response.statusCode).to.equal(500);
@@ -41,3 +42,24 @@ describe("FastBootAppServer", function() {
4142
});
4243

4344
});
45+
46+
function runServer(name) {
47+
return new Promise((res, rej) => {
48+
let serverPath = path.join(__dirname, 'fixtures', `${name}.js`);
49+
server = fork(serverPath, {
50+
silent: true
51+
});
52+
53+
server.on('error', rej);
54+
55+
server.stdout.on('data', data => {
56+
if (data.toString().match(/HTTP server started/)) {
57+
res();
58+
}
59+
});
60+
61+
server.stderr.on('data', data => {
62+
console.log(data.toString());
63+
});
64+
});
65+
}

test/fixtures/not-found-server.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
3+
const FastBootAppServer = require('../../lib/fastboot-app-server.js');
4+
5+
let server = new FastBootAppServer({
6+
downloader: {
7+
download() {
8+
return Promise.resolve();
9+
}
10+
}
11+
});
12+
13+
server.start();

0 commit comments

Comments
 (0)