summaryrefslogtreecommitdiff
path: root/node_modules/express/lib/router/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/express/lib/router/index.js')
-rw-r--r--node_modules/express/lib/router/index.js398
1 files changed, 398 insertions, 0 deletions
diff --git a/node_modules/express/lib/router/index.js b/node_modules/express/lib/router/index.js
new file mode 100644
index 0000000..ff1498b
--- /dev/null
+++ b/node_modules/express/lib/router/index.js
@@ -0,0 +1,398 @@
+
+/*!
+ * 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;
+};