Как отключить мой экспресс-сервер изящно, когда его процесс убит?
Когда я запускаю свое приложение Express на производстве, я хочу изящно закрыть сервер, когда его процесс будет убит (т.е. отправлен SIGTERM или SIGINT).
Вот упрощенная версия моего кода:
const express = require('express');
const app = express();
app.get('/', (req, res) => res.json({ ping: true }));
const server = app.listen(3000, () => console.log('Running…'));
setInterval(() => server.getConnections(
(err, connections) => console.log('${connections} connections currently open')
), 1000);
process.on('SIGTERM', shutDown);
process.on('SIGINT', shutDown);
function shutDown() {
console.log('Received kill signal, shutting down gracefully');
server.close(() => {
console.log('Closed out remaining connections');
process.exit(0);
});
setTimeout(() => {
console.error('Could not close connections in time, forcefully shutting down');
process.exit(1);
}, 10000);
}
Когда я запустил его и вызываю URL-адрес http://localhost: 3000/ в браузере, оператор журнала в функции setInterval будет печатать "1 соединение в настоящее время открыто", пока я фактически не закрою окно браузера. По-видимому, даже закрытие вкладки будет оставаться открытым.
Итак, я убил свой сервер, нажав Ctrl + C, он запустится в таймаут и распечатает "Не удалось закрыть соединения" через 10 секунд, продолжая печатать "1 соединение открыто".
Только если я закрою окно браузера, прежде чем убить процесс, я получаю сообщение "закрыть оставшиеся соединения".
Что мне здесь не хватает? Каков правильный способ изящного закрытия Express-сервера?
Ответы
Ответ 1
В случае, если кто-то заинтересован, я нашел решение самостоятельно (хотел бы услышать отзывы в комментариях).
Я добавил слушателя для открытия соединений на сервере, сохраняя ссылки на эти соединения в массиве. Когда соединения закрыты, они удаляются из массива.
Когда сервер убит, каждое соединение закрывается, вызывая его методы end
. Для некоторых браузеров (например, Chrome) этого недостаточно, поэтому после таймаута я вызываю destroy
для каждого соединения.
const express = require('express');
const app = express();
app.get('/', (req, res) => res.json({ ping: true }));
const server = app.listen(3000, () => console.log('Running…'));
setInterval(() => server.getConnections(
(err, connections) => console.log('${connections} connections currently open')
), 1000);
process.on('SIGTERM', shutDown);
process.on('SIGINT', shutDown);
let connections = [];
server.on('connection', connection => {
connections.push(connection);
connection.on('close', () => connections = connections.filter(curr => curr !== connection));
});
function shutDown() {
console.log('Received kill signal, shutting down gracefully');
server.close(() => {
console.log('Closed out remaining connections');
process.exit(0);
});
setTimeout(() => {
console.error('Could not close connections in time, forcefully shutting down');
process.exit(1);
}, 10000);
connections.forEach(curr => curr.end());
setTimeout(() => connections.forEach(curr => curr.destroy()), 5000);
}
Ответ 2
Проблема, с которой вы столкнулись, заключается в том, что все современные браузеры используют одно соединение для нескольких запросов. Это называется связью keep-alive.
Правильный способ справиться с этим - следить за всеми новыми подключениями и запросами и отслеживать состояние каждого соединения (сейчас он бездействует или активен). Затем вы можете принудительно закрыть все незанятые соединения и не забудьте закрыть активные соединения после обработки текущего запроса.
Я реализовал модуль @moebius/http-graceful-shutdown, специально разработанный для изящного отключения приложений Express и серверов узлов в целом. К сожалению, ни Express, ни сам узел не имеют встроенной функции.
Здесь, как это можно использовать с любым приложением Express:
const express = require('express');
const GracefulShutdownManager = require('@moebius/http-graceful-shutdown').GracefulShutdownManager;
const app = express();
const server = app.listen(8080);
const shutdownManager = new GracefulShutdownManager(server);
process.on('SIGTERM', () => {
shutdownManager.terminate(() => {
console.log('Server is gracefully terminated');
});
});
Не забудьте проверить модуль, на странице GitHub есть более подробная информация.
Ответ 3
Существует проект с открытым исходным кодом https://github.com/godaddy/terminus, рекомендованный создателями Express (https://expressjs.com/en/advanced/healthcheck-graceful-shutdown.html).
Основной пример использования терминалов:
const http = require('http');
const express = require('express');
const terminus = require('@godaddy/terminus');
const app = express();
app.get('/', (req, res) => {
res.send('ok');
});
const server = http.createServer(app);
function onSignal() {
console.log('server is starting cleanup');
// start cleanup of resource, like databases or file descriptors
}
async function onHealthCheck() {
// checks if the system is healthy, like the db connection is live
// resolves, if health, rejects if not
}
terminus(server, {
signal: 'SIGINT',
healthChecks: {
'/healthcheck': onHealthCheck,
},
onSignal
});
server.listen(3000);
terminus имеет множество опций, если вам нужны обратные вызовы жизненного цикла сервера (т.е. отменить регистрацию из реестра служб и т.д.):
const options = {
// healtcheck options
healthChecks: {
'/healthcheck': healthCheck // a promise returning function indicating service health
},
// cleanup options
timeout: 1000, // [optional = 1000] number of milliseconds before forcefull exiting
signal, // [optional = 'SIGTERM'] what signal to listen for relative to shutdown
signals, // [optional = []] array of signals to listen for relative to shutdown
beforeShutdown, // [optional] called before the HTTP server starts its shutdown
onSignal, // [optional] cleanup function, returning a promise (used to be onSigterm)
onShutdown, // [optional] called right before exiting
// both
logger // [optional] logger function to be called with errors
};
Ответ 4
Попробуйте модуль экспресс-изящного выключения NPM, изящное завершение позволит любым соединениям, включая вашу БД, закончить, а не разрешать создание новых/новых. Поскольку вы работаете с выражением, которое может быть модулем, который вы ищете, однако быстрый поиск NPM покажет весь список модулей, подходящих для серверов Http и т.д.
Ответ 5
Правильно обрабатывать сигналы ОС: https://www.npmjs.com/package/daemonix
Изящное завершение работы Express: https://www.npmjs.com/package/@stringstack/express https://www.npmjs.com/package/@stringstack/core
Эта комбинация инструментов остановит новые соединения при завершении работы, позволит завершить существующие соединения и, наконец, завершится.