API Docs for: 0.8.0
Show:

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;
};