The LM Control website. Simple yet efficient.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

index.js 15KB

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