UNPKG

http-micro

Version:

Micro-framework on top of node's http module

135 lines (134 loc) 5.37 kB
"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;