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.

645 lines
14 KiB

  1. /*!
  2. * express
  3. * Copyright(c) 2009-2013 TJ Holowaychuk
  4. * Copyright(c) 2013 Roman Shtylman
  5. * Copyright(c) 2014-2015 Douglas Christopher Wilson
  6. * MIT Licensed
  7. */
  8. 'use strict';
  9. /**
  10. * Module dependencies.
  11. * @private
  12. */
  13. var Route = require('./route');
  14. var Layer = require('./layer');
  15. var methods = require('methods');
  16. var mixin = require('utils-merge');
  17. var debug = require('debug')('express:router');
  18. var deprecate = require('depd')('express');
  19. var flatten = require('array-flatten');
  20. var parseUrl = require('parseurl');
  21. /**
  22. * Module variables.
  23. * @private
  24. */
  25. var objectRegExp = /^\[object (\S+)\]$/;
  26. var slice = Array.prototype.slice;
  27. var toString = Object.prototype.toString;
  28. /**
  29. * Initialize a new `Router` with the given `options`.
  30. *
  31. * @param {Object} options
  32. * @return {Router} which is an callable function
  33. * @public
  34. */
  35. var proto = module.exports = function(options) {
  36. var opts = options || {};
  37. function router(req, res, next) {
  38. router.handle(req, res, next);
  39. }
  40. // mixin Router class functions
  41. router.__proto__ = proto;
  42. router.params = {};
  43. router._params = [];
  44. router.caseSensitive = opts.caseSensitive;
  45. router.mergeParams = opts.mergeParams;
  46. router.strict = opts.strict;
  47. router.stack = [];
  48. return router;
  49. };
  50. /**
  51. * Map the given param placeholder `name`(s) to the given callback.
  52. *
  53. * Parameter mapping is used to provide pre-conditions to routes
  54. * which use normalized placeholders. For example a _:user_id_ parameter
  55. * could automatically load a user's information from the database without
  56. * any additional code,
  57. *
  58. * The callback uses the same signature as middleware, the only difference
  59. * being that the value of the placeholder is passed, in this case the _id_
  60. * of the user. Once the `next()` function is invoked, just like middleware
  61. * it will continue on to execute the route, or subsequent parameter functions.
  62. *
  63. * Just like in middleware, you must either respond to the request or call next
  64. * to avoid stalling the request.
  65. *
  66. * app.param('user_id', function(req, res, next, id){
  67. * User.find(id, function(err, user){
  68. * if (err) {
  69. * return next(err);
  70. * } else if (!user) {
  71. * return next(new Error('failed to load user'));
  72. * }
  73. * req.user = user;
  74. * next();
  75. * });
  76. * });
  77. *
  78. * @param {String} name
  79. * @param {Function} fn
  80. * @return {app} for chaining
  81. * @public
  82. */
  83. proto.param = function param(name, fn) {
  84. // param logic
  85. if (typeof name === 'function') {
  86. deprecate('router.param(fn): Refactor to use path params');
  87. this._params.push(name);
  88. return;
  89. }
  90. // apply param functions
  91. var params = this._params;
  92. var len = params.length;
  93. var ret;
  94. if (name[0] === ':') {
  95. deprecate('router.param(' + JSON.stringify(name) + ', fn): Use router.param(' + JSON.stringify(name.substr(1)) + ', fn) instead');
  96. name = name.substr(1);
  97. }
  98. for (var i = 0; i < len; ++i) {
  99. if (ret = params[i](name, fn)) {
  100. fn = ret;
  101. }
  102. }
  103. // ensure we end up with a
  104. // middleware function
  105. if ('function' !== typeof fn) {
  106. throw new Error('invalid param() call for ' + name + ', got ' + fn);
  107. }
  108. (this.params[name] = this.params[name] || []).push(fn);
  109. return this;
  110. };
  111. /**
  112. * Dispatch a req, res into the router.
  113. * @private
  114. */
  115. proto.handle = function handle(req, res, out) {
  116. var self = this;
  117. debug('dispatching %s %s', req.method, req.url);
  118. var search = 1 + req.url.indexOf('?');
  119. var pathlength = search ? search - 1 : req.url.length;
  120. var fqdn = req.url[0] !== '/' && 1 + req.url.substr(0, pathlength).indexOf('://');
  121. var protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : '';
  122. var idx = 0;
  123. var removed = '';
  124. var slashAdded = false;
  125. var paramcalled = {};
  126. // store options for OPTIONS request
  127. // only used if OPTIONS request
  128. var options = [];
  129. // middleware and routes
  130. var stack = self.stack;
  131. // manage inter-router variables
  132. var parentParams = req.params;
  133. var parentUrl = req.baseUrl || '';
  134. var done = restore(out, req, 'baseUrl', 'next', 'params');
  135. // setup next layer
  136. req.next = next;
  137. // for options requests, respond with a default if nothing else responds
  138. if (req.method === 'OPTIONS') {
  139. done = wrap(done, function(old, err) {
  140. if (err || options.length === 0) return old(err);
  141. sendOptionsResponse(res, options, old);
  142. });
  143. }
  144. // setup basic req values
  145. req.baseUrl = parentUrl;
  146. req.originalUrl = req.originalUrl || req.url;
  147. next();
  148. function next(err) {
  149. var layerError = err === 'route'
  150. ? null
  151. : err;
  152. // remove added slash
  153. if (slashAdded) {
  154. req.url = req.url.substr(1);
  155. slashAdded = false;
  156. }
  157. // restore altered req.url
  158. if (removed.length !== 0) {
  159. req.baseUrl = parentUrl;
  160. req.url = protohost + removed + req.url.substr(protohost.length);
  161. removed = '';
  162. }
  163. // no more matching layers
  164. if (idx >= stack.length) {
  165. setImmediate(done, layerError);
  166. return;
  167. }
  168. // get pathname of request
  169. var path = getPathname(req);
  170. if (path == null) {
  171. return done(layerError);
  172. }
  173. // find next matching layer
  174. var layer;
  175. var match;
  176. var route;
  177. while (match !== true && idx < stack.length) {
  178. layer = stack[idx++];
  179. match = matchLayer(layer, path);
  180. route = layer.route;
  181. if (typeof match !== 'boolean') {
  182. // hold on to layerError
  183. layerError = layerError || match;
  184. }
  185. if (match !== true) {
  186. continue;
  187. }
  188. if (!route) {
  189. // process non-route handlers normally
  190. continue;
  191. }
  192. if (layerError) {
  193. // routes do not match with a pending error
  194. match = false;
  195. continue;
  196. }
  197. var method = req.method;
  198. var has_method = route._handles_method(method);
  199. // build up automatic options response
  200. if (!has_method && method === 'OPTIONS') {
  201. appendMethods(options, route._options());
  202. }
  203. // don't even bother matching route
  204. if (!has_method && method !== 'HEAD') {
  205. match = false;
  206. continue;
  207. }
  208. }
  209. // no match
  210. if (match !== true) {
  211. return done(layerError);
  212. }
  213. // store route for dispatch on change
  214. if (route) {
  215. req.route = route;
  216. }
  217. // Capture one-time layer values
  218. req.params = self.mergeParams
  219. ? mergeParams(layer.params, parentParams)
  220. : layer.params;
  221. var layerPath = layer.path;
  222. // this should be done for the layer
  223. self.process_params(layer, paramcalled, req, res, function (err) {
  224. if (err) {
  225. return next(layerError || err);
  226. }
  227. if (route) {
  228. return layer.handle_request(req, res, next);
  229. }
  230. trim_prefix(layer, layerError, layerPath, path);
  231. });
  232. }
  233. function trim_prefix(layer, layerError, layerPath, path) {
  234. var c = path[layerPath.length];
  235. if (c && '/' !== c && '.' !== c) return next(layerError);
  236. // Trim off the part of the url that matches the route
  237. // middleware (.use stuff) needs to have the path stripped
  238. if (layerPath.length !== 0) {
  239. debug('trim prefix (%s) from url %s', layerPath, req.url);
  240. removed = layerPath;
  241. req.url = protohost + req.url.substr(protohost.length + removed.length);
  242. // Ensure leading slash
  243. if (!fqdn && req.url[0] !== '/') {
  244. req.url = '/' + req.url;
  245. slashAdded = true;
  246. }
  247. // Setup base URL (no trailing slash)
  248. req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
  249. ? removed.substring(0, removed.length - 1)
  250. : removed);
  251. }
  252. debug('%s %s : %s', layer.name, layerPath, req.originalUrl);
  253. if (layerError) {
  254. layer.handle_error(layerError, req, res, next);
  255. } else {
  256. layer.handle_request(req, res, next);
  257. }
  258. }
  259. };
  260. /**
  261. * Process any parameters for the layer.
  262. * @private
  263. */
  264. proto.process_params = function process_params(layer, called, req, res, done) {
  265. var params = this.params;
  266. // captured parameters from the layer, keys and values
  267. var keys = layer.keys;
  268. // fast track
  269. if (!keys || keys.length === 0) {
  270. return done();
  271. }
  272. var i = 0;
  273. var name;
  274. var paramIndex = 0;
  275. var key;
  276. var paramVal;
  277. var paramCallbacks;
  278. var paramCalled;
  279. // process params in order
  280. // param callbacks can be async
  281. function param(err) {
  282. if (err) {
  283. return done(err);
  284. }
  285. if (i >= keys.length ) {
  286. return done();
  287. }
  288. paramIndex = 0;
  289. key = keys[i++];
  290. if (!key) {
  291. return done();
  292. }
  293. name = key.name;
  294. paramVal = req.params[name];
  295. paramCallbacks = params[name];
  296. paramCalled = called[name];
  297. if (paramVal === undefined || !paramCallbacks) {
  298. return param();
  299. }
  300. // param previously called with same value or error occurred
  301. if (paramCalled && (paramCalled.match === paramVal
  302. || (paramCalled.error && paramCalled.error !== 'route'))) {
  303. // restore value
  304. req.params[name] = paramCalled.value;
  305. // next param
  306. return param(paramCalled.error);
  307. }
  308. called[name] = paramCalled = {
  309. error: null,
  310. match: paramVal,
  311. value: paramVal
  312. };
  313. paramCallback();
  314. }
  315. // single param callbacks
  316. function paramCallback(err) {
  317. var fn = paramCallbacks[paramIndex++];
  318. // store updated value
  319. paramCalled.value = req.params[key.name];
  320. if (err) {
  321. // store error
  322. paramCalled.error = err;
  323. param(err);
  324. return;
  325. }
  326. if (!fn) return param();
  327. try {
  328. fn(req, res, paramCallback, paramVal, key.name);
  329. } catch (e) {
  330. paramCallback(e);
  331. }
  332. }
  333. param();
  334. };
  335. /**
  336. * Use the given middleware function, with optional path, defaulting to "/".
  337. *
  338. * Use (like `.all`) will run for any http METHOD, but it will not add
  339. * handlers for those methods so OPTIONS requests will not consider `.use`
  340. * functions even if they could respond.
  341. *
  342. * The other difference is that _route_ path is stripped and not visible
  343. * to the handler function. The main effect of this feature is that mounted
  344. * handlers can operate without any code changes regardless of the "prefix"
  345. * pathname.
  346. *
  347. * @public
  348. */
  349. proto.use = function use(fn) {
  350. var offset = 0;
  351. var path = '/';
  352. // default path to '/'
  353. // disambiguate router.use([fn])
  354. if (typeof fn !== 'function') {
  355. var arg = fn;
  356. while (Array.isArray(arg) && arg.length !== 0) {
  357. arg = arg[0];
  358. }
  359. // first arg is the path
  360. if (typeof arg !== 'function') {
  361. offset = 1;
  362. path = fn;
  363. }
  364. }
  365. var callbacks = flatten(slice.call(arguments, offset));
  366. if (callbacks.length === 0) {
  367. throw new TypeError('Router.use() requires middleware functions');
  368. }
  369. for (var i = 0; i < callbacks.length; i++) {
  370. var fn = callbacks[i];
  371. if (typeof fn !== 'function') {
  372. throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn));
  373. }
  374. // add the middleware
  375. debug('use %s %s', path, fn.name || '<anonymous>');
  376. var layer = new Layer(path, {
  377. sensitive: this.caseSensitive,
  378. strict: false,
  379. end: false
  380. }, fn);
  381. layer.route = undefined;
  382. this.stack.push(layer);
  383. }
  384. return this;
  385. };
  386. /**
  387. * Create a new Route for the given path.
  388. *
  389. * Each route contains a separate middleware stack and VERB handlers.
  390. *
  391. * See the Route api documentation for details on adding handlers
  392. * and middleware to routes.
  393. *
  394. * @param {String} path
  395. * @return {Route}
  396. * @public
  397. */
  398. proto.route = function route(path) {
  399. var route = new Route(path);
  400. var layer = new Layer(path, {
  401. sensitive: this.caseSensitive,
  402. strict: this.strict,
  403. end: true
  404. }, route.dispatch.bind(route));
  405. layer.route = route;
  406. this.stack.push(layer);
  407. return route;
  408. };
  409. // create Router#VERB functions
  410. methods.concat('all').forEach(function(method){
  411. proto[method] = function(path){
  412. var route = this.route(path)
  413. route[method].apply(route, slice.call(arguments, 1));
  414. return this;
  415. };
  416. });
  417. // append methods to a list of methods
  418. function appendMethods(list, addition) {
  419. for (var i = 0; i < addition.length; i++) {
  420. var method = addition[i];
  421. if (list.indexOf(method) === -1) {
  422. list.push(method);
  423. }
  424. }
  425. }
  426. // get pathname of request
  427. function getPathname(req) {
  428. try {
  429. return parseUrl(req).pathname;
  430. } catch (err) {
  431. return undefined;
  432. }
  433. }
  434. // get type for error message
  435. function gettype(obj) {
  436. var type = typeof obj;
  437. if (type !== 'object') {
  438. return type;
  439. }
  440. // inspect [[Class]] for objects
  441. return toString.call(obj)
  442. .replace(objectRegExp, '$1');
  443. }
  444. /**
  445. * Match path to a layer.
  446. *
  447. * @param {Layer} layer
  448. * @param {string} path
  449. * @private
  450. */
  451. function matchLayer(layer, path) {
  452. try {
  453. return layer.match(path);
  454. } catch (err) {
  455. return err;
  456. }
  457. }
  458. // merge params with parent params
  459. function mergeParams(params, parent) {
  460. if (typeof parent !== 'object' || !parent) {
  461. return params;
  462. }
  463. // make copy of parent for base
  464. var obj = mixin({}, parent);
  465. // simple non-numeric merging
  466. if (!(0 in params) || !(0 in parent)) {
  467. return mixin(obj, params);
  468. }
  469. var i = 0;
  470. var o = 0;
  471. // determine numeric gaps
  472. while (i in params) {
  473. i++;
  474. }
  475. while (o in parent) {
  476. o++;
  477. }
  478. // offset numeric indices in params before merge
  479. for (i--; i >= 0; i--) {
  480. params[i + o] = params[i];
  481. // create holes for the merge when necessary
  482. if (i < o) {
  483. delete params[i];
  484. }
  485. }
  486. return mixin(obj, params);
  487. }
  488. // restore obj props after function
  489. function restore(fn, obj) {
  490. var props = new Array(arguments.length - 2);
  491. var vals = new Array(arguments.length - 2);
  492. for (var i = 0; i < props.length; i++) {
  493. props[i] = arguments[i + 2];
  494. vals[i] = obj[props[i]];
  495. }
  496. return function(err){
  497. // restore vals
  498. for (var i = 0; i < props.length; i++) {
  499. obj[props[i]] = vals[i];
  500. }
  501. return fn.apply(this, arguments);
  502. };
  503. }
  504. // send an OPTIONS response
  505. function sendOptionsResponse(res, options, next) {
  506. try {
  507. var body = options.join(',');
  508. res.set('Allow', body);
  509. res.send(body);
  510. } catch (err) {
  511. next(err);
  512. }
  513. }
  514. // wrap a function
  515. function wrap(old, fn) {
  516. return function proxy() {
  517. var args = new Array(arguments.length + 1);
  518. args[0] = old;
  519. for (var i = 0, len = arguments.length; i < len; i++) {
  520. args[i + 1] = arguments[i];
  521. }
  522. fn.apply(this, args);
  523. };
  524. }