You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

273 lines
5.9 KiB

  1. /**
  2. * Module dependencies.
  3. */
  4. var Route = require('./route')
  5. , utils = require('../utils')
  6. , methods = require('methods')
  7. , debug = require('debug')('express:router')
  8. , parse = require('connect').utils.parseUrl;
  9. /**
  10. * Expose `Router` constructor.
  11. */
  12. exports = module.exports = Router;
  13. /**
  14. * Initialize a new `Router` with the given `options`.
  15. *
  16. * @param {Object} options
  17. * @api private
  18. */
  19. function Router(options) {
  20. options = options || {};
  21. var self = this;
  22. this.map = {};
  23. this.params = {};
  24. this._params = [];
  25. this.caseSensitive = options.caseSensitive;
  26. this.strict = options.strict;
  27. this.middleware = function router(req, res, next){
  28. self._dispatch(req, res, next);
  29. };
  30. }
  31. /**
  32. * Register a param callback `fn` for the given `name`.
  33. *
  34. * @param {String|Function} name
  35. * @param {Function} fn
  36. * @return {Router} for chaining
  37. * @api public
  38. */
  39. Router.prototype.param = function(name, fn){
  40. // param logic
  41. if ('function' == typeof name) {
  42. this._params.push(name);
  43. return;
  44. }
  45. // apply param functions
  46. var params = this._params
  47. , len = params.length
  48. , ret;
  49. for (var i = 0; i < len; ++i) {
  50. if (ret = params[i](name, fn)) {
  51. fn = ret;
  52. }
  53. }
  54. // ensure we end up with a
  55. // middleware function
  56. if ('function' != typeof fn) {
  57. throw new Error('invalid param() call for ' + name + ', got ' + fn);
  58. }
  59. (this.params[name] = this.params[name] || []).push(fn);
  60. return this;
  61. };
  62. /**
  63. * Route dispatcher aka the route "middleware".
  64. *
  65. * @param {IncomingMessage} req
  66. * @param {ServerResponse} res
  67. * @param {Function} next
  68. * @api private
  69. */
  70. Router.prototype._dispatch = function(req, res, next){
  71. var params = this.params
  72. , self = this;
  73. debug('dispatching %s %s (%s)', req.method, req.url, req.originalUrl);
  74. // route dispatch
  75. (function pass(i, err){
  76. var paramCallbacks
  77. , paramIndex = 0
  78. , paramVal
  79. , route
  80. , keys
  81. , key;
  82. // match next route
  83. function nextRoute(err) {
  84. pass(req._route_index + 1, err);
  85. }
  86. // match route
  87. req.route = route = self.matchRequest(req, i);
  88. // no route
  89. if (!route) return next(err);
  90. debug('matched %s %s', route.method, route.path);
  91. // we have a route
  92. // start at param 0
  93. req.params = route.params;
  94. keys = route.keys;
  95. i = 0;
  96. // param callbacks
  97. function param(err) {
  98. paramIndex = 0;
  99. key = keys[i++];
  100. paramVal = key && req.params[key.name];
  101. paramCallbacks = key && params[key.name];
  102. try {
  103. if ('route' == err) {
  104. nextRoute();
  105. } else if (err) {
  106. i = 0;
  107. callbacks(err);
  108. } else if (paramCallbacks && undefined !== paramVal) {
  109. paramCallback();
  110. } else if (key) {
  111. param();
  112. } else {
  113. i = 0;
  114. callbacks();
  115. }
  116. } catch (err) {
  117. param(err);
  118. }
  119. };
  120. param(err);
  121. // single param callbacks
  122. function paramCallback(err) {
  123. var fn = paramCallbacks[paramIndex++];
  124. if (err || !fn) return param(err);
  125. fn(req, res, paramCallback, paramVal, key.name);
  126. }
  127. // invoke route callbacks
  128. function callbacks(err) {
  129. var fn = route.callbacks[i++];
  130. try {
  131. if ('route' == err) {
  132. nextRoute();
  133. } else if (err && fn) {
  134. if (fn.length < 4) return callbacks(err);
  135. fn(err, req, res, callbacks);
  136. } else if (fn) {
  137. if (fn.length < 4) return fn(req, res, callbacks);
  138. callbacks();
  139. } else {
  140. nextRoute(err);
  141. }
  142. } catch (err) {
  143. callbacks(err);
  144. }
  145. }
  146. })(0);
  147. };
  148. /**
  149. * Attempt to match a route for `req`
  150. * with optional starting index of `i`
  151. * defaulting to 0.
  152. *
  153. * @param {IncomingMessage} req
  154. * @param {Number} i
  155. * @return {Route}
  156. * @api private
  157. */
  158. Router.prototype.matchRequest = function(req, i, head){
  159. var method = req.method.toLowerCase()
  160. , url = parse(req)
  161. , path = url.pathname
  162. , routes = this.map
  163. , i = i || 0
  164. , route;
  165. // HEAD support
  166. if (!head && 'head' == method) {
  167. route = this.matchRequest(req, i, true);
  168. if (route) return route;
  169. method = 'get';
  170. }
  171. // routes for this method
  172. if (routes = routes[method]) {
  173. // matching routes
  174. for (var len = routes.length; i < len; ++i) {
  175. route = routes[i];
  176. if (route.match(path)) {
  177. req._route_index = i;
  178. return route;
  179. }
  180. }
  181. }
  182. };
  183. /**
  184. * Attempt to match a route for `method`
  185. * and `url` with optional starting
  186. * index of `i` defaulting to 0.
  187. *
  188. * @param {String} method
  189. * @param {String} url
  190. * @param {Number} i
  191. * @return {Route}
  192. * @api private
  193. */
  194. Router.prototype.match = function(method, url, i, head){
  195. var req = { method: method, url: url };
  196. return this.matchRequest(req, i, head);
  197. };
  198. /**
  199. * Route `method`, `path`, and one or more callbacks.
  200. *
  201. * @param {String} method
  202. * @param {String} path
  203. * @param {Function} callback...
  204. * @return {Router} for chaining
  205. * @api private
  206. */
  207. Router.prototype.route = function(method, path, callbacks){
  208. var method = method.toLowerCase()
  209. , callbacks = utils.flatten([].slice.call(arguments, 2));
  210. // ensure path was given
  211. if (!path) throw new Error('Router#' + method + '() requires a path');
  212. // ensure all callbacks are functions
  213. callbacks.forEach(function(fn, i){
  214. if ('function' == typeof fn) return;
  215. var type = {}.toString.call(fn);
  216. var msg = '.' + method + '() requires callback functions but got a ' + type;
  217. throw new Error(msg);
  218. });
  219. // create the route
  220. debug('defined %s %s', method, path);
  221. var route = new Route(method, path, callbacks, {
  222. sensitive: this.caseSensitive,
  223. strict: this.strict
  224. });
  225. // add it
  226. (this.map[method] = this.map[method] || []).push(route);
  227. return this;
  228. };
  229. methods.forEach(function(method){
  230. Router.prototype[method] = function(path){
  231. var args = [method].concat([].slice.call(arguments));
  232. this.route.apply(this, args);
  233. return this;
  234. };
  235. });