src/middleware/HttpServer.js
// Import the necessary modules.
// @flow
import cluster from 'cluster'
/** @external {http~Server} https://nodejs.org/dist/latest/docs/api/http.html#http_class_http_server */
import http from 'http'
import { cpus } from 'os'
import type Database from './Database'
/**
* Class for starting the API.
* @type {Server}
*/
export default class HttpServer {
/**
* the http server object.
* @type {http~Server}
* @see https://nodejs.org/api/http.html#http_http_createserver_requestlistener
*/
server: Server
/**
* The port on which the API will run on. Default is `5000`.
* @type {number}
*/
serverPort: number
/**
* The amount of workers on the cluster.
* @type {number}
*/
workers: number
/**
* Create a new Server object.
* @param {!PopApi} PopApi - The PopApi instance to bind the server to.
* @param {!Object} options - The options for the server.
* @param {!Express} options.app - The application instance to create a
* server for.
* @param {!number} [options.serverPort=process.env.PORT] - The port the API
* will run on.
* @param {!number} [options.workers=2] - The amount of workers to fork.
* @throws {TypeError} - 'app' is a required option for the HttpServer
* middleware!
*/
constructor(PopApi: any, {
app,
serverPort = process.env.PORT || 5000,
workers = 2
}: Object): void {
const { name: debugName } = this.constructor
PopApi.debug(`Registering ${debugName} middleware with options: %o`, {
serverPort,
workers
})
if (!app) {
throw new TypeError('\'app\' is a required option for the HttpServer middleware!')
}
/**
* The amount of workers on the cluster.
* @type {number}
*/
this.server = typeof app === 'function' ? http.createServer(app) : app
/**
* The port on which the API will run on. Default is `5000`.
* @type {number}
*/
this.serverPort = serverPort
/**
* The amount of workers on the cluster.
* @type {number}
*/
this.workers = workers
this.setupApi(app)
PopApi.server = this
}
/**
* For the workers.
* @returns {undefined}
*/
forkWorkers(): void {
for (let i = 0; i < Math.min(cpus().length, this.workers); i++) {
cluster.fork()
}
}
/**
* Handle the errors for workers.
* @returns {undefined}
*/
workersOnExit(): void {
cluster.on('exit', ({ process }) => {
const msg = `Worker '${process.pid}' died, spinning up another!`
logger.error(msg)
cluster.fork()
})
}
/**
* Method to setup the cron job.
* @param {!Express} app - The application instance to create a server for.
* @returns {undefined}
*/
setupApi(app: Object): void {
if (cluster.isWorker || this.workers === 0) {
this.server = app.listen(this.serverPort)
}
if (cluster.isMaster || this.workers === 0) {
this.forkWorkers()
this.workersOnExit()
logger.info(`API started on port: ${this.serverPort}`)
}
}
/**
* Method to stop the API from running.
* @param {!Database} database - The database connection to close.
* @param {?Function} [done=() => {}] - Function to exit the API.
* @returns {undefined}
*/
closeApi(database: Database, done: Function = () => {}): void {
this.server.close(() => {
database.disconnect().then(() => {
logger.info('Closed out remaining connections.')
done()
})
})
}
}