123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- /**
- * Module dependencies.
- */
- var Route = require('./route')
- , utils = require('../utils')
- , methods = require('methods')
- , debug = require('debug')('express:router')
- , parse = require('connect').utils.parseUrl;
- /**
- * Expose `Router` constructor.
- */
- exports = module.exports = Router;
- /**
- * Initialize a new `Router` with the given `options`.
- *
- * @param {Object} options
- * @api private
- */
- function Router(options) {
- options = options || {};
- var self = this;
- this.map = {};
- this.params = {};
- this._params = [];
- this.caseSensitive = options.caseSensitive;
- this.strict = options.strict;
- this.middleware = function router(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;
- };
- /**
- * 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;
- debug('dispatching %s %s (%s)', req.method, req.url, req.originalUrl);
- // route dispatch
- (function pass(i, err){
- var paramCallbacks
- , paramIndex = 0
- , paramVal
- , route
- , keys
- , key;
- // match next route
- function nextRoute(err) {
- pass(req._route_index + 1, err);
- }
- // match route
- req.route = route = self.matchRequest(req, i);
- // no route
- if (!route) return next(err);
- debug('matched %s %s', route.method, route.path);
- // 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) {
- if (fn.length < 4) return fn(req, res, callbacks);
- callbacks();
- } else {
- nextRoute(err);
- }
- } catch (err) {
- callbacks(err);
- }
- }
- })(0);
- };
- /**
- * Attempt to match a route for `req`
- * with optional starting index of `i`
- * defaulting to 0.
- *
- * @param {IncomingMessage} req
- * @param {Number} i
- * @return {Route}
- * @api private
- */
- Router.prototype.matchRequest = function(req, i, head){
- var method = req.method.toLowerCase()
- , url = parse(req)
- , path = url.pathname
- , routes = this.map
- , i = i || 0
- , route;
- // HEAD support
- if (!head && 'head' == method) {
- route = this.matchRequest(req, i, true);
- if (route) return route;
- method = 'get';
- }
- // routes for this method
- if (routes = routes[method]) {
- // matching routes
- for (var len = routes.length; i < len; ++i) {
- route = routes[i];
- if (route.match(path)) {
- req._route_index = i;
- return route;
- }
- }
- }
- };
- /**
- * Attempt to match a route for `method`
- * and `url` with optional starting
- * index of `i` defaulting to 0.
- *
- * @param {String} method
- * @param {String} url
- * @param {Number} i
- * @return {Route}
- * @api private
- */
- Router.prototype.match = function(method, url, i, head){
- var req = { method: method, url: url };
- return this.matchRequest(req, i, head);
- };
- /**
- * 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 method = method.toLowerCase()
- , callbacks = utils.flatten([].slice.call(arguments, 2));
- // ensure path was given
- if (!path) throw new Error('Router#' + method + '() requires a path');
- // ensure all callbacks are functions
- callbacks.forEach(function(fn, i){
- if ('function' == typeof fn) return;
- var type = {}.toString.call(fn);
- var msg = '.' + method + '() requires callback functions but got a ' + type;
- throw new Error(msg);
- });
- // create the route
- debug('defined %s %s', method, path);
- var route = new Route(method, path, callbacks, {
- sensitive: this.caseSensitive,
- strict: this.strict
- });
- // add it
- (this.map[method] = this.map[method] || []).push(route);
- return this;
- };
- methods.forEach(function(method){
- Router.prototype[method] = function(path){
- var args = [method].concat([].slice.call(arguments));
- this.route.apply(this, args);
- return this;
- };
- });
|