!function RouterWrapper() {
let core; let mod; let o = {}; const pipelines = function() {};
/**
* Blackrock Router Module
*
* @public
* @class Server.Modules.Router
* @augments Server.Modules.Core.Module
* @param {Server.Modules.Core} coreObj - The Core Module Singleton
* @return {Server.Modules.Router} module - The Router Module Singleton
*
* @description This is the Router Module of the Blackrock Application Server.
* It is responsible for accepting incoming requests from any of the interfaces
* and then routing these requests on to the relevant app + controller. It
* then routes the response back from the controller to the originating interface.
*
* @example
* Tbc...
*
* @author Darren Smith
* @copyright Copyright (c) 2021 Darren Smith
* @license Licensed under the LGPL license.
*/
module.exports = function RouterModule(coreObj) {
if (mod) return mod;
core = coreObj; mod = new core.Mod('Router'); o.log = core.module('logger').log;
o.log('debug', 'Router > Initialising...', {module: mod.name}, 'MODULE_INIT');
o.routerCount = 0; o.routers = {};
process.nextTick(function() {
pipelines.init();
});
return mod;
};
/**
* (Internal > Pipelines) Pipeline to Create Routers
*
* @private
* @memberof Server.Modules.Router
* @function pipelines.init
* @ignore
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
pipelines.init = function RouterCreatePipeline() {
// noinspection JSUnresolvedFunction
core.lib.rxPipeline({}).pipe(
// Fires once on server initialisation:
pipelines.init.createRouterPrototype,
pipelines.init.attachExternalMethods,
pipelines.init.createRouters,
// Fires once per router on server initialisation:
pipelines.init.initRouter,
pipelines.init.setupReturnErrorMethod,
pipelines.init.setupRouteMethod,
pipelines.init.setupListenerMethod,
// Fires once per incoming request for each router:
pipelines.init.determineNewRequestRoute,
pipelines.init.buildRequestObject,
pipelines.init.buildResponseObject,
pipelines.init.logAnalyticsNotificationForRequest,
pipelines.init.prepareResponseListener,
pipelines.init.routeRequestToController
).subscribe();
};
/**
* (Internal > Stream Methods [1]) Create Router Prototype
*
* @private
* @memberof Server.Modules.Router
* @function createRouterPrototype
* @ignore
* @param {observable} source - The Source Observable
* @return {observable} destination - The Destination Observable
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
pipelines.init.createRouterPrototype = function RouterIPLCreateRouterPrototype(source) {
// noinspection JSUnresolvedFunction
return core.lib.rxOperator(function(observer, evt) {
// noinspection JSValidateTypes
/**
* Router Prototype
*
* @public
* @class Server.Modules.Router.router
* @augments Server.Modules.Core.Module
*
* @description
* This is the Router Class. It is used to generate new Routers.
*
* @example
* Tbc...
*/
evt.Router = new core.Mod().extend({
constructor: function RouterIPLRouterConstructor() {},
});
o.log('debug', 'Router > [1] Router Prototype Created',
{module: mod.name}, 'ROUTER_PROTOTYPE_CREATED');
observer.next(evt);
}, source);
};
/**
* (Internal > Stream Methods [2]) Attach External Methods to Module
*
* @private
* @memberof Server.Modules.Router
* @function attachExternalMethods
* @ignore
* @param {observable} source - The Source Observable
* @return {observable} destination - The Destination Observable
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
pipelines.init.attachExternalMethods = function RouterIPLAttachExternalMethods(source) {
// noinspection JSUnresolvedFunction
return core.lib.rxOperator(function RouterIPLAttachExternalMethodsOp(observer, evt) {
/**
* Return Router List
*
* @public
* @memberof Server.Modules.Router
* @function list
* @return {array} Array of Routers
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
mod.list = function RouterGetInstanceList() {
return Object.keys(o.routers);
};
/**
* Return Specific Router
*
* @public
* @memberof Server.Modules.Router
* @function get
* @return {string} name - Router Name
* @return {Server.Modules.Router.router} Router Object
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
mod.get = function RouterGetInstance(name) {
if (o.routers[name]) {
return o.routers[name];
}
};
/**
* Return Count
*
* @public
* @memberof Server.Modules.Router
* @function count
* @return {number} Number of Active Routers
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
mod.count = function RouterCountInstances() {
return o.routerCount;
};
evt.mod = mod;
o.log('debug',
'Router > [2] External Methods \'get\' and \'count\' Attached to This Module',
{module: mod.name}, 'ROUTER_BOUND_GET_COUNT');
observer.next(evt);
}, source);
};
// noinspection JSUnfilteredForInLoop
/**
* (Internal > Stream Methods [3] - Operator) Create Routers
*
* @private
* @memberof Server.Modules.Router
* @function createRouters
* @ignore
* @param {observable} source - The Source Observable
* @return {observable} destination - The Destination Observable
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
pipelines.init.createRouters = function RouterIPLLCreateRouters(source) {
// noinspection JSUnresolvedFunction
return core.lib.rxOperator(function RouterIPLLCreateRoutersOp(observer, evt) {
o.log('debug', 'Router > [3] Creating Routers...');
if (core.cfg().router.instances) {
for (const instance in core.cfg().router.instances) {
// noinspection JSUnfilteredForInLoop
if (core.cfg().router.instances[instance].apps && core.cfg().router.instances[instance].interfaces) {
evt.instanceName = instance; observer.next(evt); o.routerCount ++;
} else {
o.log('fatal',
'Router > [3a] One or more routers are misconfigured. ' +
'Terminating application server...',
{module: mod.name}, 'ROUTER_MISCONFIGURED');
core.shutdown('Router: One or more routers are misconfigured');
}
}
} else {
o.log('fatal',
'Router > [3a] No routers configured. Terminating application server...',
{module: mod.name}, 'ROUTER_NO_ROUTERS');
core.shutdown('Router: No routers configured');
}
}, source);
};
/**
* (Internal > Stream Methods [4]) Initialise Router
*
* @private
* @memberof Server.Modules.Router
* @function initRouter
* @ignore
* @param {observable} source - The Source Observable
* @return {observable} destination - The Destination Observable
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
pipelines.init.initRouter = function RouterModuleInitRouter(source) {
// noinspection JSUnresolvedFunction
return core.lib.rxOperator(function(observer, evt) {
if (!mod.new) {
/**
* Create New Router
*
* @public
* @memberof Server.Modules.Router
* @function new
* @param {string} name - Router Name
* @return {Server.Modules.Router.router} router - Router
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
mod.new = function RouterModuleNewRouter(name) {
o.routers[name] = new evt.Router();
evt.routers = o.routers;
observer.next(evt);
o.log('debug',
'Blackrock Router Module > [4] New Router (' + name + ') Instantiated',
{module: mod.name}, 'ROUTER_NEW_ROUTER_INIT');
return o.routers[name];
};
}
const name = evt.instanceName;
const routerCfg = core.cfg().router.instances[name];
mod.new(name, routerCfg);
}, source);
};
/**
* (Internal > Stream Methods [5]) Setup Return Error Method
*
*
* @private
* @memberof Server.Modules.Router
* @function setupReturnErrorMethod
* @ignore
* @param {observable} source - The Source Observable
* @return {observable} destination - The Destination Observable
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
pipelines.init.setupReturnErrorMethod = function RouterSetupReturnErrFn(source) {
// noinspection JSUnresolvedFunction
return core.lib.rxOperator(function RouterSetupReturnErrFnOp(observer, evt) {
evt.ReturnError = function RouterSetupReturnError(msgObject, message, statusCode) {
const msg = {
'module': 'Router',
'type': msgObject.type,
'interface': msgObject.interface,
'msgId': msgObject.msgId,
'sessionId': msgObject.sessionId,
'state': 'outgoing',
'directional': msgObject.directional,
'response': {
'body': {'message': message},
'message': message,
'statusCode': statusCode,
},
};
o.log('debug',
'Router > Error ' + statusCode + ': Sending message ' + msgObject.msgId + ' back to originating interface',
{module: mod.name, msg: msg}, 'ROUTER_RES_TO_INTERFACE');
if (msgObject.sessionId) {
core.module(msgObject.type, 'interface')
.get(msgObject.interface).emit('outgoing.' + msgObject.sessionId, msg);
} else {
core.module(msgObject.type, 'interface')
.get(msgObject.interface).emit('outgoing.' + msgObject.msgId, msg);
}
};
o.log('debug',
'Router > [3b] \'ReturnError\' Method Attached To This Router',
{module: mod.name}, 'ROUTER_RETURN_ERROR_BOUND');
observer.next(evt);
}, source);
};
/**
* (Internal > Stream Methods [6]) Setup Route Method
*
* @private
* @memberof Server.Modules.Router
* @function setupRouteMethod
* @ignore
* @param {observable} source - The Source Observable
* @return {observable} destination - The Destination Observable
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
pipelines.init.setupRouteMethod = function RouterSetupRouteFn(source) {
// noinspection JSUnresolvedFunction
return core.lib.rxOperator(function RouterSetupRouteFnOp(observer, evt) {
/**
* Router Route Method
*
* @public
* @memberof Server.Modules.Router.router
* @function route
* @param {string} hostname - Hostname
* @param {string} url - URL
* @param {function} cb - Callback Function
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
o.routers[evt.instanceName].route = evt.Route = function RouterRoute(hostname, url, cb) {
core.module('app-engine').search({
hostname: hostname,
url: url,
apps: core.cfg().router.instances[evt.instanceName].apps,
}, cb);
};
o.log('debug', 'Blackrock Router > [3c] \'Route\' Method Attached To This Router',
{module: mod.name}, 'ROUTER_ROUTE_BOUND');
observer.next(evt);
}, source);
};
/**
* (Internal > Stream Methods [7] - Operator) Setup Listener Method
*
* @private
* @memberof Server.Modules.Router
* @function setupListenerMethod
* @ignore
* @param {observable} source - The Source Observable
* @return {observable} destination - The Destination Observable
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
pipelines.init.setupListenerMethod = function RouterSetupListenerFn(source) {
// noinspection JSUnresolvedFunction
return core.lib.rxOperator(function RouterSetupListenerFn(observer, evt) {
/**
* Router Incoming (Listener) Method
*
* @public
* @memberof Server.Modules.Router.router
* @function incoming
* @param {object} msg - Router Message
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
o.routers[evt.instanceName].incoming = evt.Listener = function RouterListener(msg) {
const message = {};
message.parentEvent = evt;
message.routerMsg = msg;
observer.next(message);
};
o.log('debug',
'Router > [3d] \'Listener\' Method Attached To This Router (Accessible via \'get\')',
{module: mod.name}, 'ROUTER_LISTENER_BOUND');
}, source);
};
/**
* (Internal > Stream Methods [1]) Determine New Request Route
*
* @private
* @memberof Server.Modules.Router
* @function determineNewRequestRoute
* @ignore
* @param {observable} source - The Source Observable
* @return {observable} destination - The Destination Observable
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
pipelines.init.determineNewRequestRoute = function RouterDetermineNewReqRoute(source) {
// noinspection JSUnresolvedFunction
return core.lib.rxOperator(function RouterDetermineNewReqRouteOp(observer, evt) {
o.log('debug', 'Router > Received Incoming Request:',
{module: mod.name, message: evt.routerMsg}, 'ROUTER_RECEIVED_REQUEST');
evt.startTime = core.module('utilities').system.getStartTime();
evt.routerInternals = {};
evt.routerInternals.verb = evt.routerMsg.request.verb.toLowerCase();
// eslint-disable-next-line new-cap
evt.parentEvent.Route(evt.routerMsg.request.host, evt.routerMsg.request.path,
function RouterDetermineNewReqRouteCb(routeResult) {
evt.routerInternals.route = routeResult;
if (!evt.routerInternals.route || !evt.routerInternals.route.match.controller) {
// eslint-disable-next-line new-cap
evt.parentEvent.ReturnError(evt.routerMsg, 'Page Not Found', 404);
o.log('warning',
'Router > [1] Could not resolve endpoint - 404 - ' +
evt.routerMsg.msgId, {module: mod.name, messageId: evt.routerMsg.msgId}, 'ROUTER_404');
} else {
evt.routerInternals.controller = evt.routerInternals.route.match.controller;
o.log('debug',
'Router > [1] Found Route for ' +
evt.routerMsg.request.host + evt.routerMsg.request.path,
{module: mod.name}, 'ROUTER_FOUND_ROUTE');
observer.next(evt);
}
});
}, source);
};
/**
* (Internal > Stream Methods [2]) Build Request Object
*
* @private
* @memberof Server.Modules.Router
* @function buildRequestObject
* @ignore
* @param {observable} source - The Source Observable
* @return {observable} destination - The Destination Observable
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
pipelines.init.buildRequestObject = function RouterBuildReObject(source) {
// noinspection JSUnresolvedFunction
return core.lib.rxOperator(function RouterBuildReObjectOp(observer, evt) {
let util = core.module('utilities'); let coreProxy; let myConfig = core.cfg();
if (util.prop(myConfig, 'app-engine.allow')) {
coreProxy = core.getCoreProxy(myConfig['app-engine'].allow, evt.routerInternals.route.match.app);
} else coreProxy = {};
const Req = require('./_support/req');
evt.routerInternals.req = new Req;
evt.routerInternals.req.init(coreProxy, {
msgId: evt.routerMsg.msgId,
type: evt.routerMsg.type,
interface: evt.routerMsg.interface,
router: evt.parentEvent.instanceName,
path: evt.routerMsg.request.path,
host: evt.routerMsg.request.host,
headers: evt.routerMsg.request.headers,
port: evt.routerMsg.request.port,
query: evt.routerMsg.request.query,
params: evt.routerInternals.route.param,
cookies: evt.routerMsg.request.cookies,
ip: evt.routerMsg.request.ip,
ipv6: evt.routerMsg.request.ipv6,
verb: evt.routerMsg.request.verb,
secure: evt.routerMsg.request.secure,
app: core.module('app-engine').app(evt.routerInternals.route.match.app),
appName: evt.routerInternals.route.match.app,
body: evt.routerMsg.request.body,
internal: evt.routerMsg.request.internal,
log: o.log,
});
o.log('debug', 'Router > [2] Built Request Object',
{module: mod.name}, 'ROUTER_REQ_OBJ_BUILT');
observer.next(evt);
}, source);
};
/**
* (Internal > Stream Methods [3]) Build Response Object
*
* @private
* @memberof Server.Modules.Router
* @function buildResponseObject
* @ignore
* @param {observable} source - The Source Observable
* @return {observable} destination - The Destination Observable
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
pipelines.init.buildResponseObject = function RouterBuildResObject(source) {
// noinspection JSUnresolvedFunction
return core.lib.rxOperator(function RouterBuildResObjectOp(observer, evt) {
const Res = require('./_support/res');
evt.routerInternals.res = new Res;
evt.routerInternals.res.init(core, {
msgId: evt.routerMsg.msgId,
app: evt.routerInternals.route.match.app,
type: evt.routerMsg.type,
interface: evt.routerMsg.interface,
router: evt.parentEvent.instanceName,
});
o.log('debug', 'Router > [3] Built Response Object',
{module: mod.name}, 'ROUTER_RES_OBJ_BUILT');
observer.next(evt);
}, source);
};
/**
* (Internal > Stream Methods [4]) Log Analytics Notification With Logger
*
* @private
* @memberof Server.Modules.Router
* @function logAnalyticsNotificationForRequest
* @ignore
* @param {observable} source - The Source Observable
* @return {observable} destination - The Destination Observable
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
pipelines.init.logAnalyticsNotificationForRequest = function RouterLogReqAnalyticsNote(source) {
// noinspection JSUnresolvedFunction
return core.lib.rxOperator(function RouterLogReqAnalyticsNoteOp(observer, evt) {
o.log('debug', 'Router > [4] Logging Analytics Notification',
{module: mod.name}, 'ROUTER_LOGGING_ANALYTICS_NOTE');
let reqSize = '';
const reqProps = ["host", "port", "path", "query", "headers", "cookies", "body"];
for (let i = 0; i < reqProps.length - 1; i++) {
reqSize += JSON.stringify(evt.routerMsg.request[reqProps[i]]) || '';
}
reqSize = reqSize.length;
const analyticsObject = {
'msgs': {'reqSize': reqSize, 'avgMemUsed': core.module('utilities').system.getMemoryUse()},
};
core.module('utilities').system.getCpuLoad(function RouterGetCpuLoadReqCallback(load) {
analyticsObject.msgs.avgCpuLoad = load;
core.module('logger').analytics.log(analyticsObject);
});
observer.next(evt);
}, source);
};
/**
* (Internal > Stream Methods [5]) Prepare Response Listener
*
* @private
* @memberof Server.Modules.Router
* @function prepareResponseListener
* @ignore
* @param {observable} source - The Source Observable
* @return {observable} destination - The Destination Observable
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
pipelines.init.prepareResponseListener = function RouterPrepareResListener(source) {
// noinspection JSUnresolvedFunction
return core.lib.rxOperator(function RouterPrepareResListenerOp(observer, evt) {
evt.routerInternals.responseListener = function RouterResListener(msg) {
o.log('debug',
'Router > [7] Received response from controller - ' +
'Routing back to original interface. Message ID: ' +
msg.msgId, {module: mod.name, message: msg}, 'ROUTER_RES_FROM_CTRL');
let resSize = (JSON.stringify(msg.response.body) || '') +
(JSON.stringify(msg.response.headers) || '') +
(JSON.stringify(msg.response.cookies) || '');
resSize = resSize.length;
if (msg.view) {
const fs = require('fs'); const stats = fs.statSync(core.fetchBasePath('apps') +
'/' + msg.app + '/views/' + msg.view);
resSize += stats['size'];
}
const endTime = core.module('utilities').system.getEndTime(evt.startTime);
const analyticsObject = {
'msgs': {
'resSize': resSize,
'avgMemUsed': core.module('utilities').system.getMemoryUse(),
'avgProcessingTime': endTime,
},
};
core.module('utilities').system.getCpuLoad(function RouterGetCpuLoadResCallback(load) {
analyticsObject.msgs.avgCpuLoad = load;
core.module('logger').analytics.log(analyticsObject);
});
if (msg.type === 'apps') core.module('app-engine').emit('outgoing.' + msg.msgId, msg);
else {
core.module(msg.type, 'interface').get(msg.interface).emit('outgoing.' + msg.msgId, msg);
}
o.routers[evt.parentEvent.instanceName]
.removeListener('router.' + msg.msgId, evt.routerInternals.responseListener);
};
o.routers[evt.parentEvent.instanceName].on('router.' + evt.routerMsg.msgId, evt.routerInternals.responseListener);
o.log('debug',
'Router > [5] Attached Response Listener (Specific to this Router Message) To This Router',
{module: mod.name}, 'ROUTER_ATTACHED_RES_LISTENER');
observer.next(evt);
}, source);
};
/**
* (Internal > Stream Methods [6]) Route Request To Controller
*
* @private
* @memberof Server.Modules.Router
* @function routeRequestToController
* @ignore
* @param {observable} source - The Source Observable
* @return {observable} destination - The Destination Observable
*
* @description
* Tbc...
*
* @example
* Tbc...
*/
pipelines.init.routeRequestToController = function RouterRouteReqToCtrl(source) {
// noinspection JSUnresolvedFunction
return core.lib.rxOperator(function RouterRouteReqToCtrlOp(observer, evt) {
const verbs = ['get', 'post', 'put', 'delete', 'update', 'patch', 'head', 'options', 'trace'];
if (evt.routerMsg.request.verb && evt.routerInternals.controller &&
evt.routerInternals.controller[evt.routerInternals.verb] && verbs.includes(evt.routerInternals.verb)) {
const app = core.module('app-engine').app(evt.routerInternals.route.match.app);
if (app.middleware.count() === 0) {
o.log('debug',
'Router > [6] Routing This Request To The Target Controller Without Middleware',
{module: mod.name}, 'ROUTER_ROUTED_TO_CTRL_NO_MW');
evt.routerInternals.controller[evt.routerInternals.verb](evt.routerInternals.req, evt.routerInternals.res);
} else {
o.log('debug',
'Router > [6] Routing This Request To The Target Controller With Middleware',
{module: mod.name}, 'ROUTER_ROUTED_TO_CTRL_MW');
app.middleware(evt.routerInternals.req,
evt.routerInternals.res, evt.routerInternals.controller[evt.routerInternals.verb]);
}
} else {
o.log('error',
'Router > [6] Controller Function Cannot Be Found - ' +
JSON.stringify(evt.routerInternals.controller), evt.routerMsg, 'ROUTER_INVALID_CTRL_FN');
// eslint-disable-next-line new-cap
evt.parentEvent.ReturnError(evt.routerMsg, 'Internal Server Error', 500);
}
observer.next(evt);
}, source);
};
}();