UNPKG

@whook/whook

Version:

Build strong and efficient REST web services.

126 lines (123 loc) 4.68 kB
import { name, autoProvider, location } from 'knifecycle'; import http from 'node:http'; import ms from 'ms'; import { YError } from 'yerror'; function noop() { return undefined; } const DEFAULT_ENV = {}; const DEFAULT_HTTP_SERVER_OPTIONS = { maxHeadersCount: 800, requestTimeout: ms('5m'), headersTimeout: ms('1m'), maxRequestsPerSocket: 0, timeout: ms('2m'), keepAliveTimeout: ms('5m'), }; export default location(name('httpServer', autoProvider(initHTTPServer)), import.meta.url); /* Architecture Note #2.12: HTTP Server The Whook's `httpServer` service is responsible for instantiating the NodeJS HTTP Server and handling its start/shutdown. It can be easily replaced by any other HTTP server (an HTTPS one for instance if you cannot use a gateway or a proxy to handle HTTPS connections). The server takes in charge graceful shutdown by awaiting connections to be closed before shutting down which can take a long time (basically if a browser is still maintaining an open socket with it). You can short circuit this behavior, basically for development, by setting the `DESTROY_SOCKETS=1` environment variable. */ /** * Initialize an HTTP server * @name initHTTPServer * @function * @param {Object} services * The service dependencies * @param {Object} [services.ENV] * The process environment variables * @param {String} services.ENV.DESTROY_SOCKETS * Whether the server sockets would be destroyed or if the * server should wait while sockets are kept alive * @param {Object} [services.HTTP_SERVER_OPTIONS] * See https://nodejs.org/docs/latest/api/http.html#class-httpserver * @param {String} services.HOST * The server host * @param {Number} services.PORT * The server port * @param {Function} services.httpRouter * The function to run with the req/res tuple * @param {Function} [services.log=noop] * A logging function * @return {Promise<HTTPServer>} * A promise of an object with a NodeJS HTTP server * in its `service` property. */ async function initHTTPServer({ ENV = DEFAULT_ENV, HTTP_SERVER_OPTIONS = DEFAULT_HTTP_SERVER_OPTIONS, HOST, PORT, httpRouter, log = noop, }) { const FINAL_HTTP_SERVER_OPTIONS = { ...DEFAULT_HTTP_SERVER_OPTIONS, ...HTTP_SERVER_OPTIONS, }; const sockets = ENV.DESTROY_SOCKETS ? new Set() : undefined; /** @typedef HTTPServer */ const httpServer = http.createServer(httpRouter); const listenPromise = new Promise((resolve) => { httpServer.listen(PORT, HOST, () => { log('warning', `🎙️ - HTTP Server listening at "http://${HOST}:${PORT}".`); resolve(httpServer); }); }); const fatalErrorPromise = new Promise((_, reject) => { httpServer.once('error', (err) => reject(YError.wrap(err, 'E_HTTP_SERVER_ERROR'))); }); httpServer.maxHeadersCount = FINAL_HTTP_SERVER_OPTIONS.maxHeadersCount; httpServer.requestTimeout = FINAL_HTTP_SERVER_OPTIONS.requestTimeout; httpServer.headersTimeout = FINAL_HTTP_SERVER_OPTIONS.headersTimeout; httpServer.maxRequestsPerSocket = FINAL_HTTP_SERVER_OPTIONS.maxRequestsPerSocket; httpServer.timeout = FINAL_HTTP_SERVER_OPTIONS.timeout; httpServer.keepAliveTimeout = FINAL_HTTP_SERVER_OPTIONS.keepAliveTimeout; if (typeof FINAL_HTTP_SERVER_OPTIONS.maxConnections === 'number') { httpServer.maxConnections = FINAL_HTTP_SERVER_OPTIONS.maxConnections; } if (ENV.DESTROY_SOCKETS) { httpServer.on('connection', (socket) => { sockets.add(socket); socket.on('close', () => { sockets.delete(socket); }); }); } return Promise.race([listenPromise, fatalErrorPromise]).then(() => ({ service: httpServer, fatalErrorPromise, dispose: async () => { await new Promise((resolve, reject) => { log('debug', '✅ - Closing HTTP server.'); // Avoid to keepalive connections on shutdown httpServer.timeout = 1; httpServer.keepAliveTimeout = 1; httpServer.close((err) => { if (err) { reject(err); return; } log('debug', '✔️ - HTTP server closed!'); resolve(); }); if (ENV.DESTROY_SOCKETS) { for (const socket of sockets.values()) { socket.destroy(); } } }); }, })); } //# sourceMappingURL=httpServer.js.map