esrol-server-app
Version:
A wrapper of all Esrol server components for creating performance efficient, well structured - by following 'convention over configuration' approach, but also configurable, server-side applications.
345 lines (327 loc) • 11 kB
JavaScript
/**
* @author Ivaylo Ivanov
* @private
* @class Dependency
* @description Combines esrol-servers, esrol-middlewares and esrol-router
* in application aka Express.js
* @requires os
* @requires esrol-servers
* @requires esrol-router
* @requires esrol-middlewares
* @requires debug
*/
;
const Servers = require('esrol-servers');
const Router = require('esrol-router');
const Middelwares = require('esrol-middlewares');
const debug = require('debug')('esrol-server-app:components:servers');
let cores = require('os').cpus().length;
module.exports = class ServersComponent {
/**
* @private
* @method constructor
* @description Call
* 1) _resetServersCounter
* 2) _setCallback
* 3) _createServers
* @param {object} config - from config.json
* @param {object} routes - {app: {path: route}, namespaces: [path]}
* @param {object} sockets - {app: {path: socket}, namespaces: [path]}
* @param {object} middlewares -{app: {path: middleware}, namespaces: [path]}
* @param {function} callback - called on server(s) listening
* @throws {error} error // if thrown by some of the modules
*/
constructor(config, routes, sockets, middlewares, callback) {
this._resetServersCounter();
this._setCallback(callback);
this._createServers(config, routes, sockets, middlewares);
}
/**
* @private
* @method _resetServersCounter
* @description Set _waitingForServers to 0
*/
_resetServersCounter() {
this._waitingForServers = 0;
}
/**
* @private
* @method _setCallback
* @description Set _callback, called on server(s) listening
* @param {function} callback - called on server(s) listening
*/
_setCallback(callback) {
this._callback = callback;
}
/**
* @private
* @method _createServers
* @description Call appropriate method for each server type if enabled and
* enable cluster if needed
* @param {object} config - from config.json
* @param {object} routes - {httpRoutes: {object},httpsRoutes: {object}}.....
* @param {object} sockets - {app: {object}}....
* @param {object} middlewares - {http-middlewares: {object}, udp-..}....
* @throws {error} error // if thrown by some of the modules
*/
_createServers(config, routes, sockets, middlewares) {
let conf = config.servers;
this._onServerListening = this._onServerListening.bind(this);
if (config.cluster.enabled) {
if (config.cluster.cores === 'auto') {
config.cluster.cores = cores;
}
}
if (conf.http.enabled) {
this._createHTTPServer(
conf.http,
config.cluster,
routes.httpRoutes,
middlewares.app
);
}
if (conf.http.webSockets) {
this._enableHTTPWebSocket(sockets.app['http-websocket']);
}
if (conf.tcp.enabled) {
this._createTCPServer(conf.tcp, config.cluster, sockets.app.tcp);
}
if (conf.udp.enabled) {
this._createUDPServer(conf.udp, config.cluster, sockets.app.udp);
}
if (config.cluster.enabled) {
Servers.cluster(config.cluster.cores);
}
}
/**
* @private
* @method _enableHTTPWebSocket
* @description Create http websocket
* @param {object} socket - {index: {onRequest: function(socket) {}}}
* @throws {error} error - if wrong argument passed
*/
_enableHTTPWebSocket(socket) {
debug('enable http websocket');
if (!socket ||
!socket.index ||
typeof socket.index.onRequest !== 'function'
) {
let e = `In order to use http websocket you need to export class
with static "onRequest" method from /sockets/http-websocket/index.js`;
throw new Error(e);
}
let sockets = Servers.createHTTPWebSocket(Servers.getHTTPServerInstance());
sockets.on('connection', socket.index.onRequest);
}
/**
* @private
* @method _createHTTPServer
* @description Create http server
* @param {object} http config - from config.json
* @param {object} cluster config - {enabled: true / false, cores: INT}
* @param {object} routes - {app: {path: route}}.....
* @param {object} middlewares - {http-middlewares: {object}, udp-..}....
* @throws {error} error // if thrown by some of the modules
* or wrong args passed
*/
_createHTTPServer(config, cluster, routes, middlewares) {
debug('creating http server');
if (!routes || !Object.keys(routes.app).length) {
let e = `You don't have any http routes, but http server is enabled.
Please see the documentation in order to use http server ->
https://github.com/esrol/todo-this
`;
throw new Error(e);
}
let router = new Router();
let middleware = new Middelwares();
this._registerMiddlewares(middleware, middlewares['http-middlewares']);
this._setHTTPRouterMiddleware(router, middleware);
this._setRouterMethods(router, config.methods);
this._setHTTPNamespace(router, config.namespace);
this._setRouterRoutes(router, routes.app);
let settings = {
router: router.onRequest,
onListening: this._onServerListening,
port: config.port,
webSocket: config.webSockets,
cluster: cluster.enabled
};
Servers.createHTTPServer(settings);
this._onServerCreated();
}
/**
* @private
* @method _setHTTPNamespace
* @description Set server namespace.
* @param {object} router - instance of esrol-router class
* @param {string} namespace - eg 'v1' which will evaluate www.example.com/v1
*/
_setHTTPNamespace(router, namespace) {
router.setNamespace(namespace);
}
/**
* @private
* @method _setHTTPRouterMiddleware
* @description Set a middleware function to the router. This middleware
* take the request and iterate through all middlewares if there is such
* @param {object} router - instance of esrol-router class
* @param {object} middleware - instance of esrol-middlewares class
* @see {@link https://github.com/esrol/esrol-router/blob/master/example/dummy.js}
* @see {@link https://github.com/esrol/esrol-middlewares/blob/master/example/dummy.js}
*/
_setHTTPRouterMiddleware(router, middleware) {
router.setMiddleware((req, res, route, scope) => {
middleware.onRequest(req, res, route, scope);
});
}
/**
* @private
* @method _registerMiddlewares
* @description Register all middlewares
* @param {object} middleware - instance of esrol-middlewares class
* @param {object} middlewares - {path: middleware}...
* @throws {error} error // if wrong structured middleware was exported
*/
_registerMiddlewares(middleware, middlewares) {
for (let x in middlewares) {
try {
middleware.registerMiddleware({
priority: middlewares[x].priority,
middleware: middlewares[x].onRequest
});
} catch (e) {
let error = {
error: 'Middleware must be a class with a static "priority" property' +
' and static method "onRequest"',
originalError: e,
stack: e.stack
};
throw new Error(JSON.stringify(error, null, 2));
}
}
}
/**
* @private
* @method _onServerCreated
* @description Called when server is created. Increment _waitingForServers
*/
_onServerCreated() {
this._waitingForServers++;
}
/**
* @private
* @method _setRouterRoutes
* @description Iterate through the routes and register each
* @param {object} router - instance of esrol-router class
* @param {object} routes - {path: route}..
* @throws {error} error // if wrong structured route was exported
*/
_setRouterRoutes(router, routes) {
for (let route in routes) {
try {
if (routes[route].url) {
router.registerRoute(routes[route]);
} else {
this._setRouterRoutes(router, routes[route]);
}
} catch (e) {
let error = {
error: 'Route must be a class with a static "url" property' +
' and must have at least one http method',
originalError: e,
stack: e.stack
};
throw new Error(JSON.stringify(error, null, 2));
}
}
}
/**
* @private
* @method _setRouterMethods
* @description Set http enabled methods for http server: GET, POST etc..
* @param {object} router - instance of esrol-router class
* @param {object} routes - {path: route}..
* @throws {error} error // if thrown by router
*/
_setRouterMethods(router, methods) {
router.setSupportedHttpMethods(methods);
}
/**
* @private
* @method _createUDPServer
* @description Create udp server
* @param {object} udp config - from config.json
* @param {object} cluster config - {enabled: true / false, cores: INT}
* @param {object} socket - {index: {onRequest: function(msg, info) {}}}
* @throws {error} error // if wrong args passed
*/
_createUDPServer(config, cluster, socket) {
debug('creating udp server');
if (!socket ||
!socket.index ||
typeof socket.index.onRequest !== 'function'
) {
let e = `In order to use udp server, you need to export class with static
"onRequest" method, placed in /app/sockets/udp/index.js`;
throw new Error(e);
}
let udpSettings = {
port: config.port,
type: config.type,
cluster: cluster.enabled,
router: socket.index.onRequest,
onListening: this._onServerListening
};
Servers.createUDPServer(udpSettings);
this._onServerCreated();
}
/**
* @private
* @method _createTCPServer
* @description Create tcp server
* @param {object} tcp config - from config.json
* @param {object} cluster - {enabled: true / false, cores: INT}
* @param {object} socket - {index: {onRequest: function(msg, info) {}}}
* @throws {error} error // if wrong args passed
*/
_createTCPServer(config, cluster, socket) {
debug('creating tcp server');
if (!socket ||
!socket.index ||
typeof socket.index.onRequest !== 'function'
) {
let e = `In order to use tcp server, you need to export class with static
"onRequest" method, placed in /app/sockets/tcp/index.js`;
throw new Error(e);
}
let tcpSettings = {
port: config.port,
cluster: cluster.enabled,
router: socket.index.onRequest,
onListening: this._onServerListening
};
let tcpOptions = {
allowHalfOpen: config.allowHalfOpen,
pauseOnConnect: config.pauseOnConnect
};
Servers.createTCPServer(tcpSettings, tcpOptions);
this._onServerCreated();
}
/**
* @private
* @method _onServerListening
* @description Called when server is listening. If all enabled servers are
* listening and if a callback was setted, call it
*/
_onServerListening() {
this._waitingForServers--;
/* istanbul ignore else */
if (this._waitingForServers === 0) {
/* istanbul ignore else */
if (this._callback) {
this._callback();
}
}
}
};