File: include/http/router.js
'use strict';
//dependencies
var url = require('url');
var util = require('util');
var domain = require('domain');
module.exports = function (pb) {
//pb dependencies
var RequestHandler = pb.RequestHandler;
/**
* Responsible for routing a request through the registered middleware to serve up a response
* @class Router
* @constructor
* @param {Request} req
* @param {Response} res
*/
class Router {
constructor(req, res) {
/**
* Represents the current position of the middleware that is currently executing
* @property index
* @type {number}
*/
this.index = 0;
/**
* @property req
* @type {Request}
*/
this.req = req;
/**
* @property res
* @type {Response}
*/
this.res = res;
}
/**
* Starts the execution of the middleware pipeline against the specified request/response pair
* @method handle
*/
handle() {
//set reference to the handler
this.req.handler = new RequestHandler(null, this.req, this.res);
this.req.router = this;
return this._handle(this.req, this.res);
}
/**
* Handles the incoming request by executing each of the middleware in the pipeline
* @private
* @method _handle
* @param {Request} req
* @param {Response} res
*/
_handle (req, res) {
var resolve, reject;
var promise = new Promise(function(reso, rej) { resolve = reso; reject = rej; });
// initialize completion function
var self = this;
var done = function (err) {
if (!util.isError(err)) {
return resolve();
}
req.handler.serveError(err, { handler: function(data) {
req.controllerResult = data;
self.continueAfter('render')
.then(resolve, reject);
}});
};
//create execution loop
var execute = function () {
if (self.index >= Router.middleware.length) {
return done();
}
//execute the next task
var sync = true;
var action = Router.middleware[self.index].action;
action(req, res, function (err) {
if (err) {
return done(err);
}
// delay by a tick when reaching here synchronously otherwise just proceed
self.index++;
if (sync) {
process.nextTick(function() {
execute();
});
}
else {
execute();
}
});
sync = false;
};
domain.create()
.once('error', function(err) {
pb.log.error('Router: An unhandled error occurred after calling middleware "%s": %s', Router.middleware[self.index].name, err.stack);
reject(err);
})
.run(function() {
process.nextTick(execute);
});
return promise;
}
/**
* Instructs the router to continue pipeline execution after the specified middleware.
* @method continueAfter
* @param {string} middlewareName
*/
continueAfter (middlewareName) {
var index = Router.indexOfMiddleware(middlewareName);
return this.continueAt(index + 1);
}
/**
* Instructs the router to continue processing at the specified position in the set of middleware being executed
* @method continueAt
* @param {number} index
*/
continueAt (index) {
this.index = index;
return this._handle(this.req, this.res);
}
/**
* Causes a redirect result to be created and set off of the Request object as the controllerResult.
* The pipeline is then instructed to continue after the "render" middleware
* @static
* @method redirect
* @param {string} location The location to redirect to
* @param {number} httpStatusCode The integer that represents the status code to be returned
*/
redirect (location, httpStatusCode) {
this.req.controllerResult = {
redirect: location,
code: httpStatusCode
};
return this.continueAfter('render');
}
/**
* Removes the specified middleware from the pipeline
* @static
* @method removeMiddleware
* @param {string} name
* @returns {boolean}
*/
static removeMiddleware(name) {
var index = Router.indexOfMiddleware(name);
if (index >= 0) {
Router.middleware.splice(index, 1);
return true;
}
return false;
}
/**
* Replaces the middleware with the specified name at its current position in the middleware pipeline
* @static
* @method replaceMiddleware
* @param {string} name
* @param {object} middleware
* @param {string} middleware.name
* @param {function} middleware.action
* @returns {boolean}
*/
static replaceMiddleware(name, middleware) {
var index = Router.indexOfMiddleware(name);
if (index >= 0) {
Router.middleware.splice(index, 1, middleware);
return true;
}
return false;
}
/**
* Adds middleware after the middleware with the specified name
* @static
* @method addMiddlewareAfter
* @param {string} name
* @param {object} middleware
* @param {string} middleware.name
* @param {function} middleware.action
* @returns {boolean}
*/
static addMiddlewareAfter(name, middleware) {
var index = Router.indexOfMiddleware(name);
if (index >= 0) {
return Router.addMiddlewareAt(index + 1, middleware);
}
return false;
}
/**
* Adds middleware after all other registered middleware
* @static
* @method addMiddlewareAfterAll
* @param {object} middleware
* @param {string} middleware.name
* @param {function} middleware.action
* @returns {boolean}
*/
static addMiddlewareAfterAll(middleware) {
return Router.addMiddlewareAt(Router.middleware.length, middleware);
}
/**
* Adds middleware before the middleware with the specified name
* @static
* @method addMiddlewareBefore
* @param {string} name
* @param {object} middleware
* @param {string} middleware.name
* @param {function} middleware.action
* @returns {boolean}
*/
static addMiddlewareBefore(name, middleware) {
var index = Router.indexOfMiddleware(name);
if (index >= 0) {
return Router.addMiddlewareAt(index, middleware);
}
return false;
}
/**
* Adds middleware before all other registered middleware
* @static
* @method addMiddlewareBeforeAll
* @param {object} middleware
* @param {string} middleware.name
* @param {function} middleware.action
* @returns {boolean}
*/
static addMiddlewareBeforeAll(middleware) {
return Router.addMiddlewareAt(0, middleware);
}
/**
* Adds middleware at the specified index
* @static
* @method addMiddlewareAt
* @param {number} index
* @param {object} middleware
* @param {string} middleware.name
* @param {function} middleware.action
* @returns {boolean} TRUE if added, FALSE if the middleware already exists in the pipeline
*/
static addMiddlewareAt(index, middleware) {//TODO add check to ensure you can't add middleware with the same name, valid name, valid action
Router.middleware.splice(index, 0, middleware);
return true;
}
/**
* Determines the position in the middleware pipeline where the middleware executes.
* @static
* @method indexOfMiddleware
* @param {string} name
* @returns {number} The position of the middleware or -1 when not found
*/
static indexOfMiddleware(name) {
for (var i = 0; i < Router.middleware.length; i++) {
if (Router.middleware[i].name === name) {
return i;
}
}
return -1;
}
}
/**
* @static
* @property middleware
* @type {Array}
*/
Router.middleware = [];
return Router;
};