Skip to content

Commit 8f279fa

Browse files
committed
Enable Fastboot and run its Node server from Procfile
USE_FASTBOOT environment variable is used to enable Fastboot. The number of workers configurable through WEB_CONCURRENCY. https://devcenter.heroku.com/articles/node-memory-use#running-multiple-processes
1 parent 21e04f3 commit 8f279fa

File tree

8 files changed

+109
-7
lines changed

8 files changed

+109
-7
lines changed

Procfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
release: bin/diesel migration run
2-
web: bin/start-nginx ./target/release/server
2+
web: ./script/start-web.sh
33
background_worker: ./target/release/background-worker

config/environment.js

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ module.exports = function(environment) {
2222
// Here you can pass flags/options to your application instance
2323
// when it is created
2424
},
25+
26+
fastboot: {
27+
hostWhitelist: [ 'crates.io', /^localhost:\d+$/, /\.herokuapp\.com$/ ]
28+
}
2529
};
2630

2731
if (environment === 'development') {

config/nginx.conf.erb

+5
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ http {
6464
proxy_pass http://app_server;
6565
}
6666

67+
# Just in case, only forward "/policies" to Ember for a moment
68+
location = /policies {
69+
proxy_pass http://localhost:9000;
70+
}
71+
6772
location ~ ^/api/v./crates/new$ {
6873
proxy_pass http://app_server;
6974

fastboot.js

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/* eslint-disable no-console */
2+
3+
'use strict';
4+
5+
const fs = require('fs');
6+
const os = require('os');
7+
const FastBootAppServer = require('fastboot-app-server');
8+
9+
// because fastboot-app-server uses cluster, but it might change in future
10+
const cluster = require('cluster');
11+
12+
class LoggerWithoutTimestamp {
13+
constructor() {
14+
this.prefix = cluster.isMaster ? 'master' : 'worker';
15+
}
16+
writeLine() {
17+
this._write('info', Array.prototype.slice.apply(arguments));
18+
}
19+
20+
writeError() {
21+
this._write('error', Array.prototype.slice.apply(arguments));
22+
}
23+
24+
_write(level, args) {
25+
args[0] = `[${level}][${this.prefix}] ${args[0]}`;
26+
console.log.apply(console, args);
27+
}
28+
}
29+
30+
function writeAppInitializedWhenReady(logger) {
31+
let timeout;
32+
33+
timeout = setInterval(function() {
34+
logger.writeLine('waiting backend');
35+
if (fs.existsSync('/tmp/backend-initialized')) {
36+
logger.writeLine('backend is up. let heroku know the app is ready');
37+
fs.writeFileSync('/tmp/app-initialized', 'hello');
38+
clearInterval(timeout);
39+
} else {
40+
logger.writeLine('backend is still not up');
41+
}
42+
}, 1000);
43+
}
44+
45+
var logger = new LoggerWithoutTimestamp();
46+
47+
logger.writeLine(`${os.cpus().length} cores available`);
48+
49+
let workerCount = process.env.WEB_CONCURRENCY || 1;
50+
51+
let server = new FastBootAppServer({
52+
distPath: 'dist',
53+
port: 9000,
54+
ui: logger,
55+
workerCount: workerCount,
56+
});
57+
58+
if (!cluster.isWorker) {
59+
writeAppInitializedWhenReady(logger);
60+
}
61+
62+
server.start();

package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
"lint:js": "eslint . --cache",
2222
"lint:deps": "ember dependency-lint",
2323
"prettier": "prettier --write '{app,tests,mirage}/**/*.js'",
24-
"start": "FASTBOOT_DISABLED=true ember serve",
25-
"start:live": "FASTBOOT_DISABLED=true ember serve --proxy https://crates.io",
26-
"start:local": "FASTBOOT_DISABLED=true ember serve --proxy http://127.0.0.1:8888",
27-
"start:staging": "FASTBOOT_DISABLED=true ember serve --proxy https://staging-crates-io.herokuapp.com",
24+
"start": "./script/ember.sh serve",
25+
"start:live": "./script/ember.sh serve --proxy https://crates.io",
26+
"start:local": "./script/ember.sh serve --proxy http://127.0.0.1:8888",
27+
"start:staging": "./script/ember.sh serve --proxy https://staging-crates-io.herokuapp.com",
2828
"test": "ember exam --split=2 --parallel",
2929
"test-coverage": "COVERAGE=true npm run test && ember coverage-merge && rm -rf coverage_* coverage/coverage-summary.json && nyc report"
3030
},

script/ember.sh

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#! /bin/sh
2+
set -ue
3+
4+
export FASTBOOT_DISABLED
5+
6+
if [ "${USE_FASTBOOT:-0}" = '1' ]; then
7+
unset FASTBOOT_DISABLED
8+
else
9+
FASTBOOT_DISABLED=1
10+
fi
11+
12+
ember "$@"

script/start-web.sh

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#! /bin/sh
2+
set -eu
3+
4+
if [ "${USE_FASTBOOT:-0}" = 1 ]; then
5+
export USE_FASTBOOT=1
6+
node --optimize_for_size --max_old_space_size=200 fastboot.js &
7+
bin/start-nginx ./target/release/server &
8+
wait -n
9+
else
10+
unset USE_FASTBOOT
11+
bin/start-nginx ./target/release/server
12+
fi

src/bin/server.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ fn main() {
3939
boot::categories::sync(categories_toml).unwrap();
4040

4141
let heroku = dotenv::var("HEROKU").is_ok();
42+
let fastboot = dotenv::var("USE_FASTBOOT").is_ok();
43+
4244
let port = if heroku {
4345
8888
4446
} else {
@@ -102,8 +104,13 @@ fn main() {
102104
// Creating this file tells heroku to tell nginx that the application is ready
103105
// to receive traffic.
104106
if heroku {
105-
println!("Writing to /tmp/app-initialized");
106-
File::create("/tmp/app-initialized").unwrap();
107+
let path = if fastboot {
108+
"/tmp/backend-initialized"
109+
} else {
110+
"/tmp/app-initialized"
111+
};
112+
println!("Writing to {}", path);
113+
File::create(path).unwrap();
107114
}
108115

109116
// Block the main thread until the server has shutdown

0 commit comments

Comments
 (0)