postal.js/example/node/node_modules/express/lib/router/index.js

419 lines
7.9 KiB
JavaScript

/*!
* Express - Router
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Route = require( './route' )
, Collection = require( './collection' )
, utils = require( '../utils' )
, parse = require( 'url' ).parse
, toArray = utils.toArray;
/**
* Expose `Router` constructor.
*/
exports = module.exports = Router;
/**
* Expose HTTP methods.
*/
var methods = exports.methods = require( './methods' );
/**
* Initialize a new `Router` with the given `app`.
*
* @param {express.HTTPServer} app
* @api private
*/
function Router( app ) {
var self = this;
this.app = app;
this.routes = {};
this.params = {};
this._params = [];
this.middleware = function ( req, res, next ) {
self._dispatch( req, res, next );
};
}
/**
* Register a param callback `fn` for the given `name`.
*
* @param {String|Function} name
* @param {Function} fn
* @return {Router} for chaining
* @api public
*/
Router.prototype.param = function ( name, fn ) {
// param logic
if ( 'function' == typeof name ) {
this._params.push( name );
return;
}
// apply param functions
var params = this._params
, len = params.length
, ret;
for ( var i = 0; i < len; ++i ) {
if ( ret = params[i]( name, fn ) ) {
fn = ret;
}
}
// ensure we end up with a
// middleware function
if ( 'function' != typeof fn ) {
throw new Error( 'invalid param() call for ' + name + ', got ' + fn );
}
(this.params[name] = this.params[name] || []).push( fn );
return this;
};
/**
* Return a `Collection` of all routes defined.
*
* @return {Collection}
* @api public
*/
Router.prototype.all = function () {
return this.find( function () {
return true;
} );
};
/**
* Remove the given `route`, returns
* a bool indicating if the route was present
* or not.
*
* @param {Route} route
* @return {Boolean}
* @api public
*/
Router.prototype.remove = function ( route ) {
var routes = this.routes[route.method]
, len = routes.length;
for ( var i = 0; i < len; ++i ) {
if ( route == routes[i] ) {
routes.splice( i, 1 );
return true;
}
}
};
/**
* Return routes with route paths matching `path`.
*
* @param {String} method
* @param {String} path
* @return {Collection}
* @api public
*/
Router.prototype.lookup = function ( method, path ) {
return this.find( function ( route ) {
return path == route.path
&& (route.method == method
|| method == 'all');
} );
};
/**
* Return routes with regexps that match the given `url`.
*
* @param {String} method
* @param {String} url
* @return {Collection}
* @api public
*/
Router.prototype.match = function ( method, url ) {
return this.find( function ( route ) {
return route.match( url )
&& (route.method == method
|| method == 'all');
} );
};
/**
* Find routes based on the return value of `fn`
* which is invoked once per route.
*
* @param {Function} fn
* @return {Collection}
* @api public
*/
Router.prototype.find = function ( fn ) {
var len = methods.length
, ret = new Collection( this )
, method
, routes
, route;
for ( var i = 0; i < len; ++i ) {
method = methods[i];
routes = this.routes[method];
if ( !routes ) {
continue;
}
for ( var j = 0, jlen = routes.length; j < jlen; ++j ) {
route = routes[j];
if ( fn( route ) ) {
ret.push( route );
}
}
}
return ret;
};
/**
* Route dispatcher aka the route "middleware".
*
* @param {IncomingMessage} req
* @param {ServerResponse} res
* @param {Function} next
* @api private
*/
Router.prototype._dispatch = function ( req, res, next ) {
var params = this.params
, self = this;
// route dispatch
(function pass( i, err ) {
var paramCallbacks
, paramIndex = 0
, paramVal
, route
, keys
, key
, ret;
// match next route
function nextRoute( err ) {
pass( req._route_index + 1, err );
}
// match route
req.route = route = self._match( req, i );
// implied OPTIONS
if ( !route && 'OPTIONS' == req.method ) {
return self._options( req, res );
}
// no route
if ( !route ) {
return next( err );
}
// we have a route
// start at param 0
req.params = route.params;
keys = route.keys;
i = 0;
// param callbacks
function param( err ) {
paramIndex = 0;
key = keys[i++];
paramVal = key && req.params[key.name];
paramCallbacks = key && params[key.name];
try {
if ( 'route' == err ) {
nextRoute();
} else if ( err ) {
i = 0;
callbacks( err );
} else if ( paramCallbacks && undefined !== paramVal ) {
paramCallback();
} else if ( key ) {
param();
} else {
i = 0;
callbacks();
}
} catch ( err ) {
param( err );
}
}
;
param( err );
// single param callbacks
function paramCallback( err ) {
var fn = paramCallbacks[paramIndex++];
if ( err || !fn ) {
return param( err );
}
fn( req, res, paramCallback, paramVal, key.name );
}
// invoke route callbacks
function callbacks( err ) {
var fn = route.callbacks[i++];
try {
if ( 'route' == err ) {
nextRoute();
} else if ( err && fn ) {
if ( fn.length < 4 ) {
return callbacks( err );
}
fn( err, req, res, callbacks );
} else if ( fn ) {
fn( req, res, callbacks );
} else {
nextRoute( err );
}
} catch ( err ) {
callbacks( err );
}
}
})( 0 );
};
/**
* Respond to __OPTIONS__ method.
*
* @param {IncomingMessage} req
* @param {ServerResponse} res
* @api private
*/
Router.prototype._options = function ( req, res ) {
var path = parse( req.url ).pathname
, body = this._optionsFor( path ).join( ',' );
res.send( body, { Allow : body } );
};
/**
* Return an array of HTTP verbs or "options" for `path`.
*
* @param {String} path
* @return {Array}
* @api private
*/
Router.prototype._optionsFor = function ( path ) {
var self = this;
return methods.filter(
function ( method ) {
var routes = self.routes[method];
if ( !routes || 'options' == method ) {
return;
}
for ( var i = 0, len = routes.length; i < len; ++i ) {
if ( routes[i].match( path ) ) {
return true;
}
}
} ).map( function ( method ) {
return method.toUpperCase();
} );
};
/**
* Attempt to match a route for `req`
* starting from offset `i`.
*
* @param {IncomingMessage} req
* @param {Number} i
* @return {Route}
* @api private
*/
Router.prototype._match = function ( req, i ) {
var method = req.method.toLowerCase()
, url = parse( req.url )
, path = url.pathname
, routes = this.routes
, captures
, route
, keys;
// pass HEAD to GET routes
if ( 'head' == method ) {
method = 'get';
}
// routes for this method
if ( routes = routes[method] ) {
// matching routes
for ( var len = routes.length; i < len; ++i ) {
route = routes[i];
if ( captures = route.match( path ) ) {
keys = route.keys;
route.params = [];
// params from capture groups
for ( var j = 1, jlen = captures.length; j < jlen; ++j ) {
var key = keys[j - 1]
, val = 'string' == typeof captures[j]
? decodeURIComponent( captures[j] )
: captures[j];
if ( key ) {
route.params[key.name] = val;
} else {
route.params.push( val );
}
}
// all done
req._route_index = i;
return route;
}
}
}
};
/**
* Route `method`, `path`, and one or more callbacks.
*
* @param {String} method
* @param {String} path
* @param {Function} callback...
* @return {Router} for chaining
* @api private
*/
Router.prototype._route = function ( method, path, callbacks ) {
var app = this.app
, callbacks = utils.flatten( toArray( arguments, 2 ) );
// ensure path was given
if ( !path ) {
throw new Error( 'app.' + method + '() requires a path' );
}
// create the route
var route = new Route( method, path, callbacks, {
sensitive : app.enabled( 'case sensitive routes' ), strict : app.enabled( 'strict routing' )
} );
// add it
(this.routes[method] = this.routes[method] || [])
.push( route );
return this;
};