Skip to content
This repository has been archived by the owner on Mar 10, 2023. It is now read-only.

Commit

Permalink
fix(server.js): terminate http server process gracefully (#313)
Browse files Browse the repository at this point in the history
* fix(server.js): refuse new request and finish the current one before exit(0)
* fix: hmr and cleanup
* fix: use http-graceful-shutdown to terminate server process
* fix: set timeout before SIGKILL to 15 seconds
  • Loading branch information
alepee committed Apr 5, 2017
1 parent c1206cf commit e6defbc
Show file tree
Hide file tree
Showing 8 changed files with 36 additions and 15 deletions.
4 changes: 2 additions & 2 deletions config/utils/killProcess.js
@@ -1,14 +1,14 @@
const ALLOWED_SIGNALS = ['SIGTERM', 'SIGINT', 'SIGQUIT'];

export default (process, { signal = 'SIGTERM', timeout = 4000 } = {}) => {
export default (process, { signal = 'SIGTERM', timeout = 15000 } = {}) => {
if (!ALLOWED_SIGNALS.includes(signal)) {
throw new Error(`Invalid signal: "${signal}", expecting one of ${ALLOWED_SIGNALS.join()}`);
}

return new Promise((resolve) => {
const killTimeout = setTimeout(() => {
// eslint-disable-next-line no-console
console.warn('[Vitamin] Server timeout: sending SIGKILL...');
console.warn('[Vitamin] Process timeout: sending SIGKILL...');
process.kill('SIGKILL');
}, timeout);

Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -68,6 +68,7 @@
"file-loader": "^0.11.1",
"friendly-errors-webpack-plugin": "^1.6.1",
"history": "^3.2.1",
"http-graceful-shutdown": "^1.0.6",
"isomorphic-style-loader": "^1.1.0",
"js-string-escape": "^1.0.1",
"json-loader": "^0.5.4",
Expand Down
File renamed without changes.
2 changes: 2 additions & 0 deletions src/server/components/DivLayout.jsx
@@ -1,3 +1,5 @@
/* eslint react/no-danger: 0 */

import { PropTypes } from 'react';

const propTypes = {
Expand Down
2 changes: 2 additions & 0 deletions src/server/components/HTMLLayout.jsx
@@ -1,3 +1,5 @@
/* eslint react/no-danger: 0 */

import { PropTypes } from 'react';

const HelmetHeadPropTypes = PropTypes.shape({
Expand Down
2 changes: 1 addition & 1 deletion src/server/middlewares/errorHandler.jsx
Expand Up @@ -47,13 +47,13 @@ const onError = (errorContext) => {
console.error(chalk.red.bold(errorContext.error.message));
console.error(chalk.grey(errorContext.error.stack));
}
/* eslint-enable no-console */
try {
userOnError(errorContext);
} catch (err) {
console.error(chalk.red('An error occured while calling the onError function'));
console.error(err);
}
/* eslint-enable no-console */
};

const errorToErrorContext = (ctx, error) => ({
Expand Down
33 changes: 21 additions & 12 deletions src/server/server.js
Expand Up @@ -6,29 +6,30 @@ import express from 'express';
import chalk from 'chalk';
import fetch from 'node-fetch';
import readline from 'readline';
import httpGracefulShutdown from 'http-graceful-shutdown';

import app from './app';
import appMiddleware from './appMiddleware';
import config from '../../config';
import webpackClientConfig from '../../config/build/webpack.config.client';

global.fetch = fetch;

let currentApp = app;
let currentApp = appMiddleware;
function appServer() {
const server = new Koa();
server.use(
const app = new Koa();
app.use(
process.env.NODE_ENV === 'production' ? currentApp
// ecapsulate app for hot reload
: (ctx, next) => currentApp(ctx, next),
);
return server.callback();
return app.callback();
}

const mountedServer = express();

if (process.env.NODE_ENV !== 'production' && module.hot) {
const hotReloadServer = () => {
const server = express();
const app = express();
const webpack = require('webpack');
const clientBuildConfig = webpackClientConfig({
hot: true,
Expand All @@ -39,7 +40,7 @@ if (process.env.NODE_ENV !== 'production' && module.hot) {
const compiler = webpack(clientBuildConfig);
let clientBuilt = false;
const parsedPublicPath = parseUrl(config.publicPath).pathname || '';
server.use(require('webpack-dev-middleware')(compiler, {
app.use(require('webpack-dev-middleware')(compiler, {
quiet: true,
noInfo: true,
publicPath: parsedPublicPath,
Expand All @@ -56,19 +57,19 @@ if (process.env.NODE_ENV !== 'production' && module.hot) {
}));

const hmrPath = `${parsedPublicPath}/__webpack_hmr`;
server.use(require('webpack-hot-middleware')(compiler, {
app.use(require('webpack-hot-middleware')(compiler, {
log: () => {},
path: hmrPath,
reload: true,
}));

return server;
return app;
};

mountedServer.use(hotReloadServer());
module.hot.accept('./app', () => {
module.hot.accept('./appMiddleware', () => {
try {
currentApp = require('./app').default;
currentApp = require('./appMiddleware').default;
} catch (e) {
console.error(e);
}
Expand All @@ -78,7 +79,7 @@ if (process.env.NODE_ENV !== 'production' && module.hot) {
const { port, host } = config.server;
mountedServer.use(config.basePath, appServer());

mountedServer.listen(process.env.PORT || port, process.env.HOST || host, () => {
const server = mountedServer.listen(process.env.PORT || port, process.env.HOST || host, () => {
readline.clearLine(process.stdout);
readline.cursorTo(0, process.stdout);
process.stdout.write(`\x1b[0G${chalk.green('\u2713')} Server listening on: ${
Expand All @@ -92,3 +93,11 @@ mountedServer.listen(process.env.PORT || port, process.env.HOST || host, () => {
}
});

httpGracefulShutdown(server, {
signals: 'SIGINT SIGTERM SIGQUIT',
timeout: 15000,
development: process.env.NODE_ENV !== 'production',
callback: () => {
process.stdout.write('Server gracefully terminated.');
},
});
7 changes: 7 additions & 0 deletions yarn.lock
Expand Up @@ -2442,6 +2442,13 @@ http-errors@~1.5.0:
setprototypeof "1.0.2"
statuses ">= 1.3.1 < 2"

http-graceful-shutdown@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/http-graceful-shutdown/-/http-graceful-shutdown-1.0.6.tgz#26314e3e1ca812f1acba243d05cbc0965a8f17f6"
dependencies:
debug "^2.2.0"
lodash "^4.1.0"

http-signature@~1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
Expand Down

0 comments on commit e6defbc

Please sign in to comment.