Skip to content

Commit be2703a

Browse files
committed
graceful shutdown and typescript gulpfile
1 parent 11b9489 commit be2703a

File tree

14 files changed

+407
-93
lines changed

14 files changed

+407
-93
lines changed

gulpfile.js renamed to gulpfile.ts

+28-16
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
const {src, dest, series, watch, parallel} = require('gulp');
2-
const {join} = require('path');
3-
const {spawn} = require('child_process');
4-
const webpack = require('webpack');
5-
const browserSync = require('browser-sync-webpack-plugin');
6-
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
1+
import {src, dest, series, watch, parallel} from 'gulp';
2+
import {join} from 'path';
3+
import {spawn, ChildProcess} from 'child_process';
4+
import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer';
5+
import {createProject} from 'gulp-typescript';
6+
import * as webpack from 'webpack';
7+
8+
const browserSync = require('browser-sync-webpack-plugin');
9+
710
const webpackConfig = require('./webpack.config');
8-
const ts_project = require('gulp-typescript').createProject('./src/server/tsconfig.json');
9-
let server_proc;
11+
const ts_project= createProject('./src/server/tsconfig.json');
12+
let server_proc: ChildProcess;
1013

1114
function compileNode() {
1215
return src('./src/server/**/*.ts')
@@ -16,14 +19,21 @@ function compileNode() {
1619

1720
function startServer(cb) {
1821
if (server_proc) {
19-
server_proc.kill();
20-
server_proc = undefined;
22+
server_proc.kill('SIGTERM');
23+
server_proc.once('exit', (code, signal) => {
24+
server_proc = spawn('node', ['--inspect=5858', 'dist/server/app.js'], {
25+
cwd: __dirname,
26+
stdio: [0, 1, 2, 'ipc']
27+
});
28+
return cb();
29+
});
30+
} else {
31+
server_proc = spawn('node', ['--inspect=5858', 'dist/server/app.js'], {
32+
cwd: __dirname,
33+
stdio: [0, 1, 2, 'ipc']
34+
});
35+
return cb();
2136
}
22-
server_proc = spawn('node', ['--inspect=5858', 'dist/server/app.js'], {
23-
cwd: __dirname,
24-
stdio: [0, 1, 2, 'ipc']
25-
});
26-
return cb();
2737
}
2838

2939
function watchServer() {
@@ -118,11 +128,13 @@ function watchWebpack(done) {
118128
}
119129

120130
const build = parallel(compileNode, runWebpack);
131+
const devServer = series(compileNode, startServer, watchServer);
121132

122133
exports.compileNode = compileNode;
123134
exports.startServer = series(compileNode, startServer);
124135
exports.webpack = runWebpack;
125136
exports.analyze = analyzeWebpack;
126137
exports.build = build;
127-
exports.watch = parallel(watchWebpack, series(compileNode, startServer, watchServer));
138+
exports.devServer = devServer;
139+
exports.watch = parallel(watchWebpack, devServer);
128140
exports.default = build;

karma.conf.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@ module.exports = function(config) {
2121
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
2222
preprocessors: {
2323
'src/client/tests/index.ts': ['webpack', 'sourcemap'],
24+
// 'src/client/**/*.ts': ['coverage']
2425
},
2526
proxies: {
2627
'/assets': '/base/src/client/assets',
2728
},
2829
webpack: require('./webpack.config.test'),
29-
coverageIstanbulReporter: {
30-
reports: [ 'lcov', 'text-summary' ],
31-
dir: 'reports/client/coverage',
32-
fixWebpackSourcePaths: true,
30+
coverageReporter: {
31+
type : 'lcov',
32+
dir : 'reports/client/coverage',
33+
includeAllSources: true
3334
},
3435
webpackMiddleware: {
3536
noInfo: true,
@@ -40,7 +41,7 @@ module.exports = function(config) {
4041
// test results reporter to use
4142
// possible values: 'dots', 'progress'
4243
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
43-
reporters: ['mocha', 'coverage-istanbul'],
44+
reporters: ['mocha', 'coverage'],
4445
// web server port
4546
port: 9876,
4647
// enable / disable colors in the output (reporters and logs)

package-lock.json

+72
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
"start": "node dist/server/app.js",
1717
"prebuild": "rimraf dist",
1818
"predev": "rimraf dist",
19+
"predev-server": "rimraf dist/server",
1920
"build": "gulp",
2021
"dev": "gulp watch",
22+
"dev-server": "gulp devServer",
2123
"clean": "rimraf dist",
2224
"karma": "karma",
2325
"test": "concurrently npm:test-client npm:test-server",
@@ -89,6 +91,7 @@
8991
"istanbul-instrumenter-loader": "3.0.1",
9092
"karma": "6.1.0",
9193
"karma-chrome-launcher": "3.1.0",
94+
"karma-coverage": "2.0.3",
9295
"karma-coverage-istanbul-reporter": "3.0.3",
9396
"karma-mocha": "2.0.1",
9497
"karma-mocha-reporter": "2.2.5",

src/server/app.ts

+47-44
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {join} from 'path';
2-
import {createServer} from 'http';
2+
import {createServer, Server} from 'http';
33
import {cpus} from 'os';
44
import * as cluster from 'cluster';
55
import * as spdy from 'spdy';
@@ -17,67 +17,59 @@ import {SessionManager} from './services/session';
1717
import {HelpersService} from './services/helpers';
1818
import {LoggingService} from './services/logger';
1919
import {AuthService} from './services/auth';
20+
import { HealthService } from './services/health';
21+
22+
dotenv.config({});
23+
const loggingService = new LoggingService();
2024

21-
dotenv.config({
22-
debug: false // enable to see logs for set vars
23-
});
2425
const APP_CONFIG: Config = {
2526
environment: process.env.ENVIRONMENT || 'dev',
2627
cookie_name: process.env.COOKIE_NAME || '__cookie_name',
27-
cookie_secret: process.env.COOKIE_SECRET || 'cookie_secret',
2828
port: (+process.env.NODE_PORT) || 3000,
2929
log_level: process.env.MORGAN_LOG_LEVEL || 'short',
3030
client_root: process.env.CLIENT_ROOT || join(__dirname, '../client/'),
3131
max_workers: +(process.env.MAX_WORKERS || cpus().length),
32+
logger: loggingService,
33+
healthService: new HealthService(loggingService),
3234
};
3335

36+
APP_CONFIG.healthService.init();
37+
38+
3439
if (cluster.isMaster) {
3540
const numCPUs = Math.max(2, Math.min(cpus().length, APP_CONFIG.max_workers));
36-
const workers: cluster.Worker[] = [];
37-
console.log('[ master ]: App starting on port', APP_CONFIG.port);
38-
console.log(`[ master ]: Spinning up ${numCPUs - 1} workers`);
39-
for (let i=1; i < numCPUs; i++) {
41+
loggingService.log('[ master ]: App starting on port', APP_CONFIG.port);
42+
loggingService.log(`[ master ]: Spinning up ${numCPUs - 1} workers`);
43+
for (let i = 1; i < numCPUs; i++) {
4044
const worker = HelpersService.forkWorker();
41-
workers.push(worker);
4245
}
4346

44-
cluster.on('listening', (worker) => {
45-
console.log(`[ worker ${worker.id} ]: Ready and listening!`);
46-
});
47-
48-
cluster.on('message', (worker, messages, handle) => {
49-
if (Array.isArray(messages) && messages.shift() === 'console') {
50-
console.log(`[ worker ${worker.id} ]:`, ...messages);
51-
}
52-
});
53-
5447
cluster.on('exit', (worker, code, signal) => {
55-
const deadIndex = workers.findIndex(w => w.id === worker.id);
56-
if (deadIndex >= 0) {
57-
workers.splice(deadIndex, 1);
58-
}
48+
APP_CONFIG.healthService.checkClusterHealth();
5949
if (!worker.exitedAfterDisconnect) {
60-
console.log(`[ master ]: replacing crashed worker ${worker.id}`);
50+
loggingService.log(`[ master ]: replacing crashed worker ${worker.id}`);
6151
const newWorker = HelpersService.forkWorker();
62-
workers.push(newWorker);
6352
}
6453
});
6554

66-
process.on('exit', () => {
67-
console.log('[ master ]: killing workers');
68-
workers.forEach((worker) => worker.kill());
55+
cluster.on('listening', (worker) => {
56+
loggingService.log(`[ worker ${worker.id} ]: Ready and Listening`);
57+
APP_CONFIG.healthService.setHealthy(true);
6958
});
7059

71-
} else {
72-
const loggingService = new LoggingService();
73-
APP_CONFIG.logger = loggingService;
7460

61+
// process.on('exit', () => {
62+
// console.log('[ master ]: killing workers');
63+
// workers.forEach((worker) => worker.kill());
64+
// });
65+
66+
} else {
7567
const app = express();
7668
app.use(compress());
7769
app.use(userAgent.express());
7870
app.use(bodyParser.json({limit: '100mb'}));
7971
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
80-
app.use(cookieParser(APP_CONFIG.cookie_secret));
72+
app.use(cookieParser(process.env.COOKIE_SECRET || 'cookie_secret'));
8173
app.use(
8274
morgan(
8375
APP_CONFIG.log_level || ((tokens, req, res) => LoggingService.customLogger(tokens, req, res)),
@@ -87,6 +79,13 @@ if (cluster.isMaster) {
8779
)
8880
);
8981

82+
app.use((req, res, next) => {
83+
if (!APP_CONFIG.healthService.isHealthy()) {
84+
res.set('connection', 'close');
85+
}
86+
return next();
87+
})
88+
9089
// redirect http to https
9190
app.use(require('./middleware/httpredir')(APP_CONFIG));
9291

@@ -101,7 +100,8 @@ if (cluster.isMaster) {
101100
return next();
102101
});
103102

104-
let server;
103+
let server: Server;
104+
let redir;
105105
if (process.env.HTTPS) {
106106
let ssl_config = {
107107
key: (process.env.SSLKEY ? HelpersService.tryLoad(process.env.SSLKEY) : undefined),
@@ -110,21 +110,25 @@ if (cluster.isMaster) {
110110
pfx: (process.env.SSLPFX ? HelpersService.tryLoad(process.env.SSLPFX) : undefined)
111111
};
112112
server = spdy.createServer(ssl_config, app);
113-
let redir = express();
114-
redir.get('*', (req, res, next) => {
115-
let httpshost = `https://${req.headers.host}${req.url}`;
116-
return res.redirect(httpshost);
113+
const redirApp = express();
114+
redirApp.get('*', (req, res, next) => {
115+
let httpshost = `https://${req.headers.host}${req.url}`;
116+
return res.redirect(httpshost);
117117
});
118-
redir.listen(80);
118+
APP_CONFIG.healthService.registerServer(redir);
119+
redir = redirApp.listen(80);
119120
} else {
120121
server = createServer(app);
121122
}
122123

123124
/*-------- Services --------*/
124125
const db = new DatabaseService();
125126
APP_CONFIG.db = db;
127+
APP_CONFIG.healthService.registerService(db);
128+
126129
const sessionManager = new SessionManager(db);
127130
APP_CONFIG.sessionManager = sessionManager;
131+
128132
const authService = new AuthService(db, loggingService);
129133
APP_CONFIG.authService = authService;
130134

@@ -151,9 +155,8 @@ if (cluster.isMaster) {
151155
return res.status(404).send({Message: '404 UNKNOWN ROUTE'});
152156
});
153157

154-
server.listen(APP_CONFIG.port);
155-
156-
if (cluster.isMaster) {
157-
console.log('APP LISTENING ON PORT', APP_CONFIG.port);
158-
}
158+
APP_CONFIG.healthService.registerServer(server);
159+
server.listen(APP_CONFIG.port, () => {
160+
APP_CONFIG.healthService.setHealthy(true);
161+
});
159162
}

src/server/middleware/auth.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ module.exports = (APP_CONFIG: Config) => {
2828
sig = authParts[1];
2929
}
3030
if (body.length && sig.length) {
31-
const hmac = createHmac('sha512', APP_CONFIG.cookie_secret);
31+
const hmac = createHmac('sha512', process.env.COOKIE_SECRET || 'cookie_secret');
3232
const sigCompare = hmac.update(body).digest('base64');
3333
if (sigCompare === sig) {
3434
accessToken = body;

src/server/models/config.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import {DatabaseService} from '../services/db';
22
import {SessionManager} from '../services/session';
33
import {LoggingService} from '../services/logger';
44
import {AuthService} from '../services/auth';
5+
import { HealthService } from '../services/health';
56

67
export interface Config {
78
environment: string;
89
cookie_name: string;
9-
cookie_secret: string;
1010
port: number;
1111
log_level: string;
1212
client_root: string;
1313
max_workers: number;
1414
logger?: LoggingService;
15+
healthService: HealthService;
1516
db?: DatabaseService;
1617
sessionManager?: SessionManager;
1718
authService?: AuthService;

0 commit comments

Comments
 (0)