http-micro
Version:
Micro-framework on top of node's http module
135 lines (134 loc) • 5.37 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils = require("./utils");
const MicroServerKey = "micro";
const ShutdownKey = "shutdown";
const ClientErrorKey = "clientError";
function attachServerExtensions(server, socketErrorHandler) {
let s = server;
let micro = s[MicroServerKey];
if (micro)
throw new Error("server is already attached to a micro app");
let sm = new ShutdownManager(server);
let clientErrorHandler = socketErrorHandler || utils.defaultClientSocketErrorHandler;
s[MicroServerKey] = {
shutdownManager: sm,
clientErrorHandler
};
s[ShutdownKey] = sm.shutdown.bind(sm);
server.on(ClientErrorKey, clientErrorHandler);
return server;
}
exports.attachServerExtensions = attachServerExtensions;
function detachServerExtensions(server) {
let s = server;
const microKey = "micro";
let micro = s[microKey];
if (micro) {
micro.shutdownManager.destroy();
server.removeListener(ClientErrorKey, micro.clientErrorHandler);
delete s[microKey];
delete s[ShutdownKey];
}
}
exports.detachServerExtensions = detachServerExtensions;
const ConnectionKey = "connection";
const SecureConnectionKey = "secureConnection";
const RequestKey = "request";
const CloseKey = "close";
const FinishKey = "finish";
class ShutdownManager {
constructor(server) {
this.server = server;
this.socketRequestCountMap = new Map();
this.shutdownRequested = false;
this._shutdownListeners = null;
this._onConnectionBound = this._onConnection.bind(this);
this._onRequestBound = this._onRequest.bind(this);
this._destroySocketsBound = this._destroySockets.bind(this);
server.on(ConnectionKey, this._onConnectionBound);
server.on(SecureConnectionKey, this._onConnectionBound);
server.on(RequestKey, this._onRequestBound);
}
destroy() {
this.server.removeListener(ConnectionKey, this._onConnectionBound);
this.server.removeListener(SecureConnectionKey, this._onConnectionBound);
this.server.removeListener(RequestKey, this._onRequestBound);
this.socketRequestCountMap.clear();
this._shutdownListeners = null;
}
shutdown(gracePeriodMs = Infinity, callback) {
// allow request handlers to update state before we act on that state
setImmediate(() => {
this.server.close(() => {
if (callback) {
// Execute callback immediately is no request is pending, or schedule it.
if (this.socketRequestCountMap.size === 0)
callback(false);
else
this._pushListener(callback);
}
});
this.shutdownRequested = true;
if (gracePeriodMs < Infinity) {
setTimeout(this._destroySocketsBound, gracePeriodMs).unref();
}
this.socketRequestCountMap.forEach((reqNum, socket) => {
// End all idle connections (keep-alive, etc)
if (reqNum === 0)
socket.end();
});
});
}
_onConnection(socket) {
this.socketRequestCountMap.set(socket, 0);
socket.once(CloseKey, () => this._onSocketClose(socket));
}
_onSocketClose(socket) {
let socketRequestCountMap = this.socketRequestCountMap;
socketRequestCountMap.delete(socket);
if (this.shutdownRequested && socketRequestCountMap.size === 0) {
this._invokeAndResetShutdownListeners(false);
}
}
_invokeAndResetShutdownListeners(isForcedExit) {
let listeners = this._shutdownListeners;
if (listeners) {
// reset shutdown listeners.
this._shutdownListeners = null;
let len = listeners.length;
for (let i = 0; i < len; i++)
listeners[i](isForcedExit);
}
}
_onRequest(req, res) {
let socket = req.socket;
let socketRequestCountMap = this.socketRequestCountMap;
socketRequestCountMap.set(socket, socketRequestCountMap.get(socket) + 1);
res.once(FinishKey, () => this._onResponseFinished(socket));
}
_onResponseFinished(socket) {
let socketRequestCountMap = this.socketRequestCountMap;
let pending = socketRequestCountMap.get(socket) - 1;
socketRequestCountMap.set(socket, pending);
if (this.shutdownRequested && pending === 0) {
socket.end();
}
}
_destroySockets() {
// attempt to shutdown gracefully first.
this.socketRequestCountMap.forEach((reqs, socket) => socket.end());
// then destory them.
setImmediate(() => {
this.socketRequestCountMap.forEach((reqs, socket) => socket.destroy());
// ensure that any pending callbacks are called.
this._invokeAndResetShutdownListeners(true);
});
}
_pushListener(listener) {
if (!this._shutdownListeners)
this._shutdownListeners = [];
this._shutdownListeners.push(listener);
}
}
exports.ShutdownManager = ShutdownManager;