destroyable-server
Version:
A tiny Node.js module to make any server force-closeable
98 lines • 3.85 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeDestroyable = void 0;
/**
* Makes a server 'destroyable': tracks all active server connections, and adds a
* `.destroy()` method to the server, which destroys all active server connections
* and then shuts the server down cleanly.
*
* The server is mutated (adding the new method) and also returned from this method
* for convenience.
*
* `.destroy()` returns a promise, which you can wait on to ensure the server has
* been fully destroyed.
*/
function makeDestroyable(server) {
const connectionDict = {};
const addConnection = (key, conn) => {
if (connectionDict[key]) {
connectionDict[key].push(conn);
}
else {
connectionDict[key] = [conn];
}
};
const removeConnection = (key, conn) => {
const conns = connectionDict[key];
if (!conns)
return;
const index = conns.indexOf(conn);
if (conns.length === 1 && index === 0) {
delete connectionDict[key];
}
else if (index !== -1) {
conns.splice(index, 1);
}
};
const checkDestroyed = (conn) => {
do {
if (conn.destroyed)
return true;
if (!conn._parent)
return false;
conn = conn._parent;
} while (conn);
};
server.on('connection', function (conn) {
const key = conn.remoteAddress + ':' + conn.remotePort;
addConnection(key, conn);
conn.on('close', function () {
removeConnection(key, conn);
});
});
server.on('secureConnection', function (conn) {
const key = conn.remoteAddress + ':' + conn.remotePort;
addConnection(key, conn);
conn.on('close', function () {
removeConnection(key, conn);
});
});
return Object.assign(server, {
destroy: () => {
return new Promise((resolve, reject) => {
server.close((err) => {
if (err)
reject(err);
});
const closePromises = [];
for (let key in connectionDict) {
const connections = connectionDict[key];
// Shut them down in reverse order (most recent first) to try to
// reduce issues with layered connections (like tunnels)
for (let i = connections.length - 1; i >= 0; i--) {
const conn = connections[i];
closePromises.push(new Promise((resolve) => {
if (conn.closed || // Node v20+
// For Node <v18, .closed doesn't exist. For Node v18/19, 'destroyed' isn't always set
// on TLSSockets when the underlying socket is destroyed (so we check parents).
checkDestroyed(conn))
return resolve();
conn.on('close', resolve);
}));
conn.destroy();
}
}
// Wait for all connections to actually close:
Promise.all(closePromises).then(() => {
// We defer this fractionally, so that any localhost sockets have a
// chance to process the other end of this socket closure. Without
// this, you can see client conns still open after this promise
// resolved, which can cause problems for connection reuse.
setImmediate(() => resolve());
});
});
}
});
}
exports.makeDestroyable = makeDestroyable;
//# sourceMappingURL=index.js.map