event_request
Version:
A Backend Server
379 lines (315 loc) • 9.28 kB
JavaScript
// Dependencies
const Route = require( './route' );
const PluginInterface = require( './../../plugins/plugin_interface' );
const RouterCache = require( './router_cache' );
/**
* @brief Handler used to return all the needed middleware for the given event
*/
class Router extends PluginInterface {
/**
* @brief Initializes the router with an empty middleware object
*/
constructor() {
super( 'er_router', {} );
this.middleware = [];
this.globalMiddlewares = {};
this.cachingIsEnabled = true;
this.cache = new RouterCache();
this.setUpHttpMethodsToObject( this );
}
/**
* @brief Enables or disables caching
*
* @param {Boolean} [enable=true]
*/
enableCaching( enable = true ) {
this.cachingIsEnabled = enable;
}
/**
* @brief Sets the caching key limit
*/
setKeyLimit( ...args ) {
this.cache.setKeyLimit.apply( this.cache, args );
}
/**
* @brief Defines a middleware to be used globally
*
* @param {String} middlewareName
* @param {Function} middleware
*
* @return {Router}
*/
define( middlewareName, middleware ) {
if (
typeof middlewareName !== 'string'
|| typeof middleware !== 'function'
|| typeof this.globalMiddlewares[middlewareName] !== 'undefined'
) {
throw new Error( 'app.er.routing.cannotDefineMiddleware' );
}
this.globalMiddlewares[middlewareName] = middleware;
return this;
}
/**
* @brief Attaches methods to the server on runtime
*
* @param {Server} server
*/
setServerOnRuntime( server ) {
this.setUpHttpMethodsToObject( server );
/**
* @brief Function that adds a middleware to the block chain of the router
*
* @returns {Server}
*/
server.add = ( ...args ) => {
this.add.apply( this, args );
return server;
};
}
/**
* @details If route is a function then the handler will be treated as middlewares
*/
setUpHttpMethodsToObject( object ) {
const methods = ['POST', 'PUT', 'GET', 'DELETE', 'HEAD', 'PATCH', 'COPY'];
const isGlobalMiddleware = ( argument ) => {
return typeof argument === 'function' || typeof argument === 'string' || Array.isArray( argument );
};
methods.forEach(( method ) => {
object[method.toLocaleLowerCase()] = ( ...args ) => {
const firstArgument = args[0];
let route = null;
let handler = null;
let middlewares = null;
switch ( true ) {
case typeof firstArgument === 'function':
route = '';
handler = firstArgument;
middlewares = [];
break;
case ( typeof firstArgument === 'string' || firstArgument instanceof RegExp ) && isGlobalMiddleware( args[1] ) && typeof args[2] === 'function':
route = firstArgument;
handler = args[2];
middlewares = args[1];
break;
case ( typeof firstArgument === 'string' || firstArgument instanceof RegExp ) && typeof args[1] === 'function':
route = firstArgument;
handler = args[1];
middlewares = [];
break;
case isGlobalMiddleware( firstArgument ) && typeof args[1] === 'function':
route = '';
handler = args[1];
middlewares = firstArgument;
break;
default:
throw new Error( 'app.er.routing.invalidMiddlewareAdded' );
}
this.add({
method,
route,
handler,
middlewares
});
return object;
};
});
}
/**
* @brief Function that adds a middleware to the block chain of the router
*
* @details Accepts an object with 4 arguments:
* - handler - Function - Handler to be called - ! REQUIRED
* - method - String|Array - The methods(s) to be matched for the route - optional
* - route - String|RegExp - The route to match - optional
* - middlewares - Array|String - Handler to be called - ! optional
* ( { method: '', route: '', handler:() => {}, middlewares: '' } )
*
* Accepts an instance of router ( Router router )
*
* Accepts a route and an instance of a router ( String route, Router router )
*
* @returns {Router}
*/
add( ...args ) {
let first = args[0];
let second = args[1];
switch ( args.length ) {
case 1:
if ( first instanceof Router )
return this._concat( first );
if ( typeof first === 'function' )
first = new Route( { handler: first } );
if ( ! ( first instanceof Route ) )
first = new Route( first );
this.middleware.push( first );
return this;
case 2:
if ( typeof first === 'string' && second instanceof Router )
return this._concat( second, first );
default:
throw new Error( 'app.er.routing.invalidMiddlewareAdded' );
}
}
/**
* @brief This will process the request and return the appropriate block chain
*
* @param {EventRequest} event
*
* @return {Array}
*/
getExecutionBlockForCurrentEvent( event ) {
const blockKey = `${event.path}${event.method}`;
const block = [];
if ( this.cachingIsEnabled ) {
const blockData = this.cache.getBlock( blockKey );
if ( blockData !== null ) {
event.params = Object.assign( event.params, blockData.params );
return blockData.block.slice();
}
}
for ( const route of this.middleware ) {
let params = {};
if ( Router.matchMethod( event.method, route ) && Router.matchRoute( event.path, route, params ) ) {
event.params = { ...event.params, ...params };
// This will only push if there are any middlewares returned
block.push.apply( block, this._getGlobalMiddlewaresForRoute( route ) );
block.push( route.getHandler() );
}
}
if ( this.cachingIsEnabled && ! this.cache.isFull() ) {
const params = { ...event.params };
this.cache.setBlock( blockKey, { block: block.slice(), params } );
}
return block;
}
/**
* @brief Gets all the global middlewares for the given route
*
* @param {Route} route
*
* @private
*
* @return {Array}
*/
_getGlobalMiddlewaresForRoute( route ) {
const middlewares = [];
for ( const middleware of route.getMiddlewares() ) {
switch ( true ) {
case typeof middleware === 'string':
if ( typeof this.globalMiddlewares[middleware] !== 'function' )
throw { code: 'app.er.routing.missingMiddleware', message: middleware };
middlewares.push( this.globalMiddlewares[middleware] );
break;
case typeof middleware === 'function':
middlewares.push( middleware );
break;
default:
throw { code: 'app.er.routing.missingMiddleware', message: middleware };
}
}
return middlewares;
}
/**
*
* @brief Concatenates two routers together
*
* @details If a path is passed, then that path will be used to prefix all the middlewares
*
* @param {Router} router
* @param {String} path
*
* @private
*
* @return {Router}
*/
_concat( router, path = null ) {
let middlewares;
if ( path !== null ) {
middlewares = [];
for ( const originalMiddleware of router.middleware ) {
let route = originalMiddleware.route;
if ( route === '/' )
route = '';
if ( originalMiddleware.route instanceof RegExp )
route = originalMiddleware.route;
else if ( originalMiddleware.route === '' )
route = new RegExp( `^${path}?(.+)` );
else {
route = path + route;
route = route.replace( /\/\//g, '/' );
}
middlewares.push( new Route({
handler : originalMiddleware.getHandler(),
method : originalMiddleware.getMethod(),
middlewares : originalMiddleware.getMiddlewares(),
route
})
);
}
}
else
middlewares = router.middleware;
this.middleware = this.middleware.concat( middlewares );
for ( const [key, value] of Object.entries( router.globalMiddlewares ) )
this.define( key, value );
return this;
}
/**
* @brief Exported for local instances
*
* @return {Boolean}
*/
matchMethod() {
return Router.matchMethod.apply( Router, arguments );
}
/**
* @brief Exported for local instances
*
* @return {Boolean}
*/
matchRoute() {
return Router.matchRoute.apply( Router, arguments );
}
/**
* @brief Matches the requested method with the ones set in the event and returns if there was a match or no
*
* @details If a string or an array is passed, then it will be converted to a Route
*
* @param {String} requestedMethod
* @param {Route|Array|String} method
*
* @return {Boolean}
*/
static matchMethod( requestedMethod, method ) {
let route;
if ( typeof method === 'string' || Array.isArray( method ) )
route = new Route( { method } );
else
route = method;
if ( ! ( route instanceof Route ) )
return false;
return route.matchMethod( requestedMethod );
}
/**
* @brief Match the given route and returns any route parameters passed in the matchedParams argument.
*
* @details Returns bool if there was a successful match
*
* @param {String} requestedRoute
* @param {String|RegExp|Route} route
* @param {Object} [matchedParams={}]
*
* @return {Boolean}
*/
static matchRoute( requestedRoute, route, matchedParams = {} ) {
if ( typeof route === 'string' || route instanceof RegExp )
route = new Route( { route } );
if ( ! ( route instanceof Route ) )
return false;
return route.matchPath( requestedRoute, matchedParams );
}
}
// Export the module
module.exports = Router;
;