rc@73: /** rc@73: * Module dependencies. rc@73: */ rc@73: rc@73: var Route = require('./route'); rc@73: var Layer = require('./layer'); rc@73: var methods = require('methods'); rc@73: var debug = require('debug')('express:router'); rc@73: var parseUrl = require('parseurl'); rc@73: var slice = Array.prototype.slice; rc@73: rc@73: /** rc@73: * Initialize a new `Router` with the given `options`. rc@73: * rc@73: * @param {Object} options rc@73: * @return {Router} which is an callable function rc@73: * @api public rc@73: */ rc@73: rc@73: var proto = module.exports = function(options) { rc@73: options = options || {}; rc@73: rc@73: function router(req, res, next) { rc@73: router.handle(req, res, next); rc@73: } rc@73: rc@73: // mixin Router class functions rc@73: router.__proto__ = proto; rc@73: rc@73: router.params = {}; rc@73: router._params = []; rc@73: router.caseSensitive = options.caseSensitive; rc@73: router.strict = options.strict; rc@73: router.stack = []; rc@73: rc@73: return router; rc@73: }; rc@73: rc@73: /** rc@73: * Map the given param placeholder `name`(s) to the given callback. rc@73: * rc@73: * Parameter mapping is used to provide pre-conditions to routes rc@73: * which use normalized placeholders. For example a _:user_id_ parameter rc@73: * could automatically load a user's information from the database without rc@73: * any additional code, rc@73: * rc@73: * The callback uses the same signature as middleware, the only difference rc@73: * being that the value of the placeholder is passed, in this case the _id_ rc@73: * of the user. Once the `next()` function is invoked, just like middleware rc@73: * it will continue on to execute the route, or subsequent parameter functions. rc@73: * rc@73: * Just like in middleware, you must either respond to the request or call next rc@73: * to avoid stalling the request. rc@73: * rc@73: * app.param('user_id', function(req, res, next, id){ rc@73: * User.find(id, function(err, user){ rc@73: * if (err) { rc@73: * return next(err); rc@73: * } else if (!user) { rc@73: * return next(new Error('failed to load user')); rc@73: * } rc@73: * req.user = user; rc@73: * next(); rc@73: * }); rc@73: * }); rc@73: * rc@73: * @param {String} name rc@73: * @param {Function} fn rc@73: * @return {app} for chaining rc@73: * @api public rc@73: */ rc@73: rc@73: proto.param = function(name, fn){ rc@73: // param logic rc@73: if ('function' == typeof name) { rc@73: this._params.push(name); rc@73: return; rc@73: } rc@73: rc@73: // apply param functions rc@73: var params = this._params; rc@73: var len = params.length; rc@73: var ret; rc@73: rc@73: if (name[0] === ':') { rc@73: name = name.substr(1); rc@73: } rc@73: rc@73: for (var i = 0; i < len; ++i) { rc@73: if (ret = params[i](name, fn)) { rc@73: fn = ret; rc@73: } rc@73: } rc@73: rc@73: // ensure we end up with a rc@73: // middleware function rc@73: if ('function' != typeof fn) { rc@73: throw new Error('invalid param() call for ' + name + ', got ' + fn); rc@73: } rc@73: rc@73: (this.params[name] = this.params[name] || []).push(fn); rc@73: return this; rc@73: }; rc@73: rc@73: /** rc@73: * Dispatch a req, res into the router. rc@73: * rc@73: * @api private rc@73: */ rc@73: rc@73: proto.handle = function(req, res, done) { rc@73: var self = this; rc@73: rc@73: debug('dispatching %s %s', req.method, req.url); rc@73: rc@73: var method = req.method.toLowerCase(); rc@73: rc@73: var search = 1 + req.url.indexOf('?'); rc@73: var pathlength = search ? search - 1 : req.url.length; rc@73: var fqdn = 1 + req.url.substr(0, pathlength).indexOf('://'); rc@73: var protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : ''; rc@73: var idx = 0; rc@73: var removed = ''; rc@73: var slashAdded = false; rc@73: var paramcalled = {}; rc@73: rc@73: // store options for OPTIONS request rc@73: // only used if OPTIONS request rc@73: var options = []; rc@73: rc@73: // middleware and routes rc@73: var stack = self.stack; rc@73: rc@73: // manage inter-router variables rc@73: var parent = req.next; rc@73: var parentUrl = req.baseUrl || ''; rc@73: done = wrap(done, function(old, err) { rc@73: req.baseUrl = parentUrl; rc@73: req.next = parent; rc@73: old(err); rc@73: }); rc@73: req.next = next; rc@73: rc@73: // for options requests, respond with a default if nothing else responds rc@73: if (method === 'options') { rc@73: done = wrap(done, function(old, err) { rc@73: if (err || options.length === 0) return old(err); rc@73: rc@73: var body = options.join(','); rc@73: return res.set('Allow', body).send(body); rc@73: }); rc@73: } rc@73: rc@73: next(); rc@73: rc@73: function next(err) { rc@73: if (err === 'route') { rc@73: err = undefined; rc@73: } rc@73: rc@73: var layer = stack[idx++]; rc@73: var layerPath; rc@73: rc@73: if (!layer) { rc@73: return done(err); rc@73: } rc@73: rc@73: if (slashAdded) { rc@73: req.url = req.url.substr(1); rc@73: slashAdded = false; rc@73: } rc@73: rc@73: req.baseUrl = parentUrl; rc@73: req.url = protohost + removed + req.url.substr(protohost.length); rc@73: req.originalUrl = req.originalUrl || req.url; rc@73: removed = ''; rc@73: rc@73: try { rc@73: var path = parseUrl(req).pathname; rc@73: if (undefined == path) path = '/'; rc@73: rc@73: if (!layer.match(path)) return next(err); rc@73: rc@73: // route object and not middleware rc@73: var route = layer.route; rc@73: rc@73: // if final route, then we support options rc@73: if (route) { rc@73: // we don't run any routes with error first rc@73: if (err) { rc@73: return next(err); rc@73: } rc@73: rc@73: req.route = route; rc@73: rc@73: // we can now dispatch to the route rc@73: if (method === 'options' && !route.methods['options']) { rc@73: options.push.apply(options, route._options()); rc@73: } rc@73: } rc@73: rc@73: // Capture one-time layer values rc@73: req.params = layer.params; rc@73: layerPath = layer.path; rc@73: rc@73: // this should be done for the layer rc@73: return self.process_params(layer, paramcalled, req, res, function(err) { rc@73: if (err) { rc@73: return next(err); rc@73: } rc@73: rc@73: if (route) { rc@73: return layer.handle(req, res, next); rc@73: } rc@73: rc@73: trim_prefix(); rc@73: }); rc@73: rc@73: } catch (err) { rc@73: next(err); rc@73: } rc@73: rc@73: function trim_prefix() { rc@73: var c = path[layerPath.length]; rc@73: if (c && '/' != c && '.' != c) return next(err); rc@73: rc@73: // Trim off the part of the url that matches the route rc@73: // middleware (.use stuff) needs to have the path stripped rc@73: removed = layerPath; rc@73: if (removed.length) { rc@73: debug('trim prefix (%s) from url %s', layerPath, req.url); rc@73: req.url = protohost + req.url.substr(protohost.length + removed.length); rc@73: } rc@73: rc@73: // Ensure leading slash rc@73: if (!fqdn && req.url[0] !== '/') { rc@73: req.url = '/' + req.url; rc@73: slashAdded = true; rc@73: } rc@73: rc@73: // Setup base URL (no trailing slash) rc@73: if (removed.length && removed.substr(-1) === '/') { rc@73: req.baseUrl = parentUrl + removed.substring(0, removed.length - 1); rc@73: } else { rc@73: req.baseUrl = parentUrl + removed; rc@73: } rc@73: rc@73: debug('%s %s : %s', layer.handle.name || 'anonymous', layerPath, req.originalUrl); rc@73: var arity = layer.handle.length; rc@73: try { rc@73: if (err && arity === 4) { rc@73: layer.handle(err, req, res, next); rc@73: } else if (!err && arity < 4) { rc@73: layer.handle(req, res, next); rc@73: } else { rc@73: next(err); rc@73: } rc@73: } catch (err) { rc@73: next(err); rc@73: } rc@73: } rc@73: } rc@73: rc@73: function wrap(old, fn) { rc@73: return function () { rc@73: var args = [old].concat(slice.call(arguments)); rc@73: fn.apply(this, args); rc@73: }; rc@73: } rc@73: }; rc@73: rc@73: /** rc@73: * Process any parameters for the layer. rc@73: * rc@73: * @api private rc@73: */ rc@73: rc@73: proto.process_params = function(layer, called, req, res, done) { rc@73: var params = this.params; rc@73: rc@73: // captured parameters from the layer, keys and values rc@73: var keys = layer.keys; rc@73: rc@73: // fast track rc@73: if (!keys || keys.length === 0) { rc@73: return done(); rc@73: } rc@73: rc@73: var i = 0; rc@73: var name; rc@73: var paramIndex = 0; rc@73: var key; rc@73: var paramVal; rc@73: var paramCallbacks; rc@73: var paramCalled; rc@73: rc@73: // process params in order rc@73: // param callbacks can be async rc@73: function param(err) { rc@73: if (err) { rc@73: return done(err); rc@73: } rc@73: rc@73: if (i >= keys.length ) { rc@73: return done(); rc@73: } rc@73: rc@73: paramIndex = 0; rc@73: key = keys[i++]; rc@73: rc@73: if (!key) { rc@73: return done(); rc@73: } rc@73: rc@73: name = key.name; rc@73: paramVal = req.params[name]; rc@73: paramCallbacks = params[name]; rc@73: paramCalled = called[name]; rc@73: rc@73: if (paramVal === undefined || !paramCallbacks) { rc@73: return param(); rc@73: } rc@73: rc@73: // param previously called with same value or error occurred rc@73: if (paramCalled && (paramCalled.error || paramCalled.match === paramVal)) { rc@73: // restore value rc@73: req.params[name] = paramCalled.value; rc@73: rc@73: // next param rc@73: return param(paramCalled.error); rc@73: } rc@73: rc@73: called[name] = paramCalled = { rc@73: error: null, rc@73: match: paramVal, rc@73: value: paramVal rc@73: }; rc@73: rc@73: try { rc@73: return paramCallback(); rc@73: } catch (err) { rc@73: return done(err); rc@73: } rc@73: } rc@73: rc@73: // single param callbacks rc@73: function paramCallback(err) { rc@73: var fn = paramCallbacks[paramIndex++]; rc@73: rc@73: // store updated value rc@73: paramCalled.value = req.params[key.name]; rc@73: rc@73: if (err) { rc@73: // store error rc@73: paramCalled.error = err; rc@73: param(err); rc@73: return; rc@73: } rc@73: rc@73: if (!fn) return param(); rc@73: rc@73: fn(req, res, paramCallback, paramVal, key.name); rc@73: } rc@73: rc@73: param(); rc@73: }; rc@73: rc@73: /** rc@73: * Use the given middleware function, with optional path, defaulting to "/". rc@73: * rc@73: * Use (like `.all`) will run for any http METHOD, but it will not add rc@73: * handlers for those methods so OPTIONS requests will not consider `.use` rc@73: * functions even if they could respond. rc@73: * rc@73: * The other difference is that _route_ path is stripped and not visible rc@73: * to the handler function. The main effect of this feature is that mounted rc@73: * handlers can operate without any code changes regardless of the "prefix" rc@73: * pathname. rc@73: * rc@73: * @param {String|Function} route rc@73: * @param {Function} fn rc@73: * @return {app} for chaining rc@73: * @api public rc@73: */ rc@73: rc@73: proto.use = function(route, fn){ rc@73: // default route to '/' rc@73: if ('string' != typeof route) { rc@73: fn = route; rc@73: route = '/'; rc@73: } rc@73: rc@73: if (typeof fn !== 'function') { rc@73: var type = {}.toString.call(fn); rc@73: var msg = 'Router.use() requires callback functions but got a ' + type; rc@73: throw new Error(msg); rc@73: } rc@73: rc@73: // strip trailing slash rc@73: if ('/' == route[route.length - 1]) { rc@73: route = route.slice(0, -1); rc@73: } rc@73: rc@73: var layer = new Layer(route, { rc@73: sensitive: this.caseSensitive, rc@73: strict: this.strict, rc@73: end: false rc@73: }, fn); rc@73: rc@73: // add the middleware rc@73: debug('use %s %s', route || '/', fn.name || 'anonymous'); rc@73: rc@73: this.stack.push(layer); rc@73: return this; rc@73: }; rc@73: rc@73: /** rc@73: * Create a new Route for the given path. rc@73: * rc@73: * Each route contains a separate middleware stack and VERB handlers. rc@73: * rc@73: * See the Route api documentation for details on adding handlers rc@73: * and middleware to routes. rc@73: * rc@73: * @param {String} path rc@73: * @return {Route} rc@73: * @api public rc@73: */ rc@73: rc@73: proto.route = function(path){ rc@73: var route = new Route(path); rc@73: rc@73: var layer = new Layer(path, { rc@73: sensitive: this.caseSensitive, rc@73: strict: this.strict, rc@73: end: true rc@73: }, route.dispatch.bind(route)); rc@73: rc@73: layer.route = route; rc@73: rc@73: this.stack.push(layer); rc@73: return route; rc@73: }; rc@73: rc@73: // create Router#VERB functions rc@73: methods.concat('all').forEach(function(method){ rc@73: proto[method] = function(path){ rc@73: var route = this.route(path) rc@73: route[method].apply(route, [].slice.call(arguments, 1)); rc@73: return this; rc@73: }; rc@73: });