router.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845
  1. /**
  2. * RESTful resource routing middleware for koa.
  3. *
  4. * @author Alex Mingoia <talk@alexmingoia.com>
  5. * @link https://github.com/alexmingoia/koa-router
  6. */
  7. const compose = require('koa-compose');
  8. const HttpError = require('http-errors');
  9. const methods = require('methods');
  10. const { pathToRegexp } = require('path-to-regexp');
  11. const Layer = require('./layer');
  12. const debug = require('debug')('koa-router');
  13. /**
  14. * @module koa-router
  15. */
  16. module.exports = Router;
  17. /**
  18. * Create a new router.
  19. *
  20. * @example
  21. *
  22. * Basic usage:
  23. *
  24. * ```javascript
  25. * const Koa = require('koa');
  26. * const Router = require('@koa/router');
  27. *
  28. * const app = new Koa();
  29. * const router = new Router();
  30. *
  31. * router.get('/', (ctx, next) => {
  32. * // ctx.router available
  33. * });
  34. *
  35. * app
  36. * .use(router.routes())
  37. * .use(router.allowedMethods());
  38. * ```
  39. *
  40. * @alias module:koa-router
  41. * @param {Object=} opts
  42. * @param {Boolean=false} opts.exclusive only run last matched route's controller when there are multiple matches
  43. * @param {String=} opts.prefix prefix router paths
  44. * @param {String|RegExp=} opts.host host for router match
  45. * @constructor
  46. */
  47. function Router(opts = {}) {
  48. if (!(this instanceof Router)) return new Router(opts);
  49. this.opts = opts;
  50. this.methods = this.opts.methods || [
  51. 'HEAD',
  52. 'OPTIONS',
  53. 'GET',
  54. 'PUT',
  55. 'PATCH',
  56. 'POST',
  57. 'DELETE'
  58. ];
  59. this.exclusive = Boolean(this.opts.exclusive);
  60. this.params = {};
  61. this.stack = [];
  62. this.host = this.opts.host;
  63. }
  64. /**
  65. * Create `router.verb()` methods, where *verb* is one of the HTTP verbs such
  66. * as `router.get()` or `router.post()`.
  67. *
  68. * Match URL patterns to callback functions or controller actions using `router.verb()`,
  69. * where **verb** is one of the HTTP verbs such as `router.get()` or `router.post()`.
  70. *
  71. * Additionally, `router.all()` can be used to match against all methods.
  72. *
  73. * ```javascript
  74. * router
  75. * .get('/', (ctx, next) => {
  76. * ctx.body = 'Hello World!';
  77. * })
  78. * .post('/users', (ctx, next) => {
  79. * // ...
  80. * })
  81. * .put('/users/:id', (ctx, next) => {
  82. * // ...
  83. * })
  84. * .del('/users/:id', (ctx, next) => {
  85. * // ...
  86. * })
  87. * .all('/users/:id', (ctx, next) => {
  88. * // ...
  89. * });
  90. * ```
  91. *
  92. * When a route is matched, its path is available at `ctx._matchedRoute` and if named,
  93. * the name is available at `ctx._matchedRouteName`
  94. *
  95. * Route paths will be translated to regular expressions using
  96. * [path-to-regexp](https://github.com/pillarjs/path-to-regexp).
  97. *
  98. * Query strings will not be considered when matching requests.
  99. *
  100. * #### Named routes
  101. *
  102. * Routes can optionally have names. This allows generation of URLs and easy
  103. * renaming of URLs during development.
  104. *
  105. * ```javascript
  106. * router.get('user', '/users/:id', (ctx, next) => {
  107. * // ...
  108. * });
  109. *
  110. * router.url('user', 3);
  111. * // => "/users/3"
  112. * ```
  113. *
  114. * #### Multiple middleware
  115. *
  116. * Multiple middleware may be given:
  117. *
  118. * ```javascript
  119. * router.get(
  120. * '/users/:id',
  121. * (ctx, next) => {
  122. * return User.findOne(ctx.params.id).then(function(user) {
  123. * ctx.user = user;
  124. * next();
  125. * });
  126. * },
  127. * ctx => {
  128. * console.log(ctx.user);
  129. * // => { id: 17, name: "Alex" }
  130. * }
  131. * );
  132. * ```
  133. *
  134. * ### Nested routers
  135. *
  136. * Nesting routers is supported:
  137. *
  138. * ```javascript
  139. * const forums = new Router();
  140. * const posts = new Router();
  141. *
  142. * posts.get('/', (ctx, next) => {...});
  143. * posts.get('/:pid', (ctx, next) => {...});
  144. * forums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods());
  145. *
  146. * // responds to "/forums/123/posts" and "/forums/123/posts/123"
  147. * app.use(forums.routes());
  148. * ```
  149. *
  150. * #### Router prefixes
  151. *
  152. * Route paths can be prefixed at the router level:
  153. *
  154. * ```javascript
  155. * const router = new Router({
  156. * prefix: '/users'
  157. * });
  158. *
  159. * router.get('/', ...); // responds to "/users"
  160. * router.get('/:id', ...); // responds to "/users/:id"
  161. * ```
  162. *
  163. * #### URL parameters
  164. *
  165. * Named route parameters are captured and added to `ctx.params`.
  166. *
  167. * ```javascript
  168. * router.get('/:category/:title', (ctx, next) => {
  169. * console.log(ctx.params);
  170. * // => { category: 'programming', title: 'how-to-node' }
  171. * });
  172. * ```
  173. *
  174. * The [path-to-regexp](https://github.com/pillarjs/path-to-regexp) module is
  175. * used to convert paths to regular expressions.
  176. *
  177. *
  178. * ### Match host for each router instance
  179. *
  180. * ```javascript
  181. * const router = new Router({
  182. * host: 'example.domain' // only match if request host exactly equal `example.domain`
  183. * });
  184. *
  185. * ```
  186. *
  187. * OR host cloud be a regexp
  188. *
  189. * ```javascript
  190. * const router = new Router({
  191. * host: /.*\.?example\.domain$/ // all host end with .example.domain would be matched
  192. * });
  193. * ```
  194. *
  195. * @name get|put|post|patch|delete|del
  196. * @memberof module:koa-router.prototype
  197. * @param {String} path
  198. * @param {Function=} middleware route middleware(s)
  199. * @param {Function} callback route callback
  200. * @returns {Router}
  201. */
  202. for (const method_ of methods) {
  203. function setMethodVerb(method) {
  204. Router.prototype[method] = function (name, path, middleware) {
  205. if (typeof path === 'string' || path instanceof RegExp) {
  206. middleware = Array.prototype.slice.call(arguments, 2);
  207. } else {
  208. middleware = Array.prototype.slice.call(arguments, 1);
  209. path = name;
  210. name = null;
  211. }
  212. // Sanity check to ensure we have a viable path candidate (eg: string|regex|non-empty array)
  213. if (
  214. typeof path !== 'string' &&
  215. !(path instanceof RegExp) &&
  216. (!Array.isArray(path) || path.length === 0)
  217. )
  218. throw new Error(
  219. `You have to provide a path when adding a ${method} handler`
  220. );
  221. this.register(path, [method], middleware, { name });
  222. return this;
  223. };
  224. }
  225. setMethodVerb(method_);
  226. }
  227. // Alias for `router.delete()` because delete is a reserved word
  228. // eslint-disable-next-line dot-notation
  229. Router.prototype.del = Router.prototype['delete'];
  230. /**
  231. * Use given middleware.
  232. *
  233. * Middleware run in the order they are defined by `.use()`. They are invoked
  234. * sequentially, requests start at the first middleware and work their way
  235. * "down" the middleware stack.
  236. *
  237. * @example
  238. *
  239. * ```javascript
  240. * // session middleware will run before authorize
  241. * router
  242. * .use(session())
  243. * .use(authorize());
  244. *
  245. * // use middleware only with given path
  246. * router.use('/users', userAuth());
  247. *
  248. * // or with an array of paths
  249. * router.use(['/users', '/admin'], userAuth());
  250. *
  251. * app.use(router.routes());
  252. * ```
  253. *
  254. * @param {String=} path
  255. * @param {Function} middleware
  256. * @param {Function=} ...
  257. * @returns {Router}
  258. */
  259. Router.prototype.use = function () {
  260. const router = this;
  261. const middleware = Array.prototype.slice.call(arguments);
  262. let path;
  263. // support array of paths
  264. if (Array.isArray(middleware[0]) && typeof middleware[0][0] === 'string') {
  265. const arrPaths = middleware[0];
  266. for (const p of arrPaths) {
  267. router.use.apply(router, [p].concat(middleware.slice(1)));
  268. }
  269. return this;
  270. }
  271. const hasPath = typeof middleware[0] === 'string';
  272. if (hasPath) path = middleware.shift();
  273. for (const m of middleware) {
  274. if (m.router) {
  275. const cloneRouter = Object.assign(
  276. Object.create(Router.prototype),
  277. m.router,
  278. {
  279. stack: [...m.router.stack]
  280. }
  281. );
  282. for (let j = 0; j < cloneRouter.stack.length; j++) {
  283. const nestedLayer = cloneRouter.stack[j];
  284. const cloneLayer = Object.assign(
  285. Object.create(Layer.prototype),
  286. nestedLayer
  287. );
  288. if (path) cloneLayer.setPrefix(path);
  289. if (router.opts.prefix) cloneLayer.setPrefix(router.opts.prefix);
  290. router.stack.push(cloneLayer);
  291. cloneRouter.stack[j] = cloneLayer;
  292. }
  293. if (router.params) {
  294. function setRouterParams(paramArr) {
  295. const routerParams = paramArr;
  296. for (const key of routerParams) {
  297. cloneRouter.param(key, router.params[key]);
  298. }
  299. }
  300. setRouterParams(Object.keys(router.params));
  301. }
  302. } else {
  303. const keys = [];
  304. pathToRegexp(router.opts.prefix || '', keys);
  305. const routerPrefixHasParam = router.opts.prefix && keys.length;
  306. router.register(path || '([^/]*)', [], m, {
  307. end: false,
  308. ignoreCaptures: !hasPath && !routerPrefixHasParam
  309. });
  310. }
  311. }
  312. return this;
  313. };
  314. /**
  315. * Set the path prefix for a Router instance that was already initialized.
  316. *
  317. * @example
  318. *
  319. * ```javascript
  320. * router.prefix('/things/:thing_id')
  321. * ```
  322. *
  323. * @param {String} prefix
  324. * @returns {Router}
  325. */
  326. Router.prototype.prefix = function (prefix) {
  327. prefix = prefix.replace(/\/$/, '');
  328. this.opts.prefix = prefix;
  329. for (let i = 0; i < this.stack.length; i++) {
  330. const route = this.stack[i];
  331. route.setPrefix(prefix);
  332. }
  333. return this;
  334. };
  335. /**
  336. * Returns router middleware which dispatches a route matching the request.
  337. *
  338. * @returns {Function}
  339. */
  340. Router.prototype.routes = Router.prototype.middleware = function () {
  341. const router = this;
  342. const dispatch = function dispatch(ctx, next) {
  343. debug('%s %s', ctx.method, ctx.path);
  344. const hostMatched = router.matchHost(ctx.host);
  345. if (!hostMatched) {
  346. return next();
  347. }
  348. const path =
  349. router.opts.routerPath || ctx.newRouterPath || ctx.path || ctx.routerPath;
  350. const matched = router.match(path, ctx.method);
  351. let layerChain;
  352. if (ctx.matched) {
  353. ctx.matched.push.apply(ctx.matched, matched.path);
  354. } else {
  355. ctx.matched = matched.path;
  356. }
  357. ctx.router = router;
  358. if (!matched.route) return next();
  359. const matchedLayers = matched.pathAndMethod;
  360. const mostSpecificLayer = matchedLayers[matchedLayers.length - 1];
  361. ctx._matchedRoute = mostSpecificLayer.path;
  362. if (mostSpecificLayer.name) {
  363. ctx._matchedRouteName = mostSpecificLayer.name;
  364. }
  365. layerChain = (
  366. router.exclusive ? [mostSpecificLayer] : matchedLayers
  367. ).reduce(function (memo, layer) {
  368. memo.push(function (ctx, next) {
  369. ctx.captures = layer.captures(path, ctx.captures);
  370. ctx.params = ctx.request.params = layer.params(
  371. path,
  372. ctx.captures,
  373. ctx.params
  374. );
  375. ctx.routerPath = layer.path;
  376. ctx.routerName = layer.name;
  377. ctx._matchedRoute = layer.path;
  378. if (layer.name) {
  379. ctx._matchedRouteName = layer.name;
  380. }
  381. return next();
  382. });
  383. return memo.concat(layer.stack);
  384. }, []);
  385. return compose(layerChain)(ctx, next);
  386. };
  387. dispatch.router = this;
  388. return dispatch;
  389. };
  390. /**
  391. * Returns separate middleware for responding to `OPTIONS` requests with
  392. * an `Allow` header containing the allowed methods, as well as responding
  393. * with `405 Method Not Allowed` and `501 Not Implemented` as appropriate.
  394. *
  395. * @example
  396. *
  397. * ```javascript
  398. * const Koa = require('koa');
  399. * const Router = require('@koa/router');
  400. *
  401. * const app = new Koa();
  402. * const router = new Router();
  403. *
  404. * app.use(router.routes());
  405. * app.use(router.allowedMethods());
  406. * ```
  407. *
  408. * **Example with [Boom](https://github.com/hapijs/boom)**
  409. *
  410. * ```javascript
  411. * const Koa = require('koa');
  412. * const Router = require('@koa/router');
  413. * const Boom = require('boom');
  414. *
  415. * const app = new Koa();
  416. * const router = new Router();
  417. *
  418. * app.use(router.routes());
  419. * app.use(router.allowedMethods({
  420. * throw: true,
  421. * notImplemented: () => new Boom.notImplemented(),
  422. * methodNotAllowed: () => new Boom.methodNotAllowed()
  423. * }));
  424. * ```
  425. *
  426. * @param {Object=} options
  427. * @param {Boolean=} options.throw throw error instead of setting status and header
  428. * @param {Function=} options.notImplemented throw the returned value in place of the default NotImplemented error
  429. * @param {Function=} options.methodNotAllowed throw the returned value in place of the default MethodNotAllowed error
  430. * @returns {Function}
  431. */
  432. Router.prototype.allowedMethods = function (options = {}) {
  433. const implemented = this.methods;
  434. return function allowedMethods(ctx, next) {
  435. return next().then(function () {
  436. const allowed = {};
  437. if (!ctx.status || ctx.status === 404) {
  438. for (let i = 0; i < ctx.matched.length; i++) {
  439. const route = ctx.matched[i];
  440. for (let j = 0; j < route.methods.length; j++) {
  441. const method = route.methods[j];
  442. allowed[method] = method;
  443. }
  444. }
  445. const allowedArr = Object.keys(allowed);
  446. if (!~implemented.indexOf(ctx.method)) {
  447. if (options.throw) {
  448. const notImplementedThrowable =
  449. typeof options.notImplemented === 'function'
  450. ? options.notImplemented() // set whatever the user returns from their function
  451. : new HttpError.NotImplemented();
  452. throw notImplementedThrowable;
  453. } else {
  454. ctx.status = 501;
  455. ctx.set('Allow', allowedArr.join(', '));
  456. }
  457. } else if (allowedArr.length > 0) {
  458. if (ctx.method === 'OPTIONS') {
  459. ctx.status = 200;
  460. ctx.body = '';
  461. ctx.set('Allow', allowedArr.join(', '));
  462. } else if (!allowed[ctx.method]) {
  463. if (options.throw) {
  464. const notAllowedThrowable =
  465. typeof options.methodNotAllowed === 'function'
  466. ? options.methodNotAllowed() // set whatever the user returns from their function
  467. : new HttpError.MethodNotAllowed();
  468. throw notAllowedThrowable;
  469. } else {
  470. ctx.status = 405;
  471. ctx.set('Allow', allowedArr.join(', '));
  472. }
  473. }
  474. }
  475. }
  476. });
  477. };
  478. };
  479. /**
  480. * Register route with all methods.
  481. *
  482. * @param {String} name Optional.
  483. * @param {String} path
  484. * @param {Function=} middleware You may also pass multiple middleware.
  485. * @param {Function} callback
  486. * @returns {Router}
  487. */
  488. Router.prototype.all = function (name, path, middleware) {
  489. if (typeof path === 'string') {
  490. middleware = Array.prototype.slice.call(arguments, 2);
  491. } else {
  492. middleware = Array.prototype.slice.call(arguments, 1);
  493. path = name;
  494. name = null;
  495. }
  496. // Sanity check to ensure we have a viable path candidate (eg: string|regex|non-empty array)
  497. if (
  498. typeof path !== 'string' &&
  499. !(path instanceof RegExp) &&
  500. (!Array.isArray(path) || path.length === 0)
  501. )
  502. throw new Error('You have to provide a path when adding an all handler');
  503. this.register(path, methods, middleware, { name });
  504. return this;
  505. };
  506. /**
  507. * Redirect `source` to `destination` URL with optional 30x status `code`.
  508. *
  509. * Both `source` and `destination` can be route names.
  510. *
  511. * ```javascript
  512. * router.redirect('/login', 'sign-in');
  513. * ```
  514. *
  515. * This is equivalent to:
  516. *
  517. * ```javascript
  518. * router.all('/login', ctx => {
  519. * ctx.redirect('/sign-in');
  520. * ctx.status = 301;
  521. * });
  522. * ```
  523. *
  524. * @param {String} source URL or route name.
  525. * @param {String} destination URL or route name.
  526. * @param {Number=} code HTTP status code (default: 301).
  527. * @returns {Router}
  528. */
  529. Router.prototype.redirect = function (source, destination, code) {
  530. // lookup source route by name
  531. if (typeof source === 'symbol' || source[0] !== '/') {
  532. source = this.url(source);
  533. if (source instanceof Error) throw source;
  534. }
  535. // lookup destination route by name
  536. if (
  537. typeof destination === 'symbol' ||
  538. (destination[0] !== '/' && !destination.includes('://'))
  539. ) {
  540. destination = this.url(destination);
  541. if (destination instanceof Error) throw destination;
  542. }
  543. return this.all(source, (ctx) => {
  544. ctx.redirect(destination);
  545. ctx.status = code || 301;
  546. });
  547. };
  548. /**
  549. * Create and register a route.
  550. *
  551. * @param {String} path Path string.
  552. * @param {Array.<String>} methods Array of HTTP verbs.
  553. * @param {Function} middleware Multiple middleware also accepted.
  554. * @returns {Layer}
  555. * @private
  556. */
  557. Router.prototype.register = function (path, methods, middleware, opts = {}) {
  558. const router = this;
  559. const { stack } = this;
  560. // support array of paths
  561. if (Array.isArray(path)) {
  562. for (const curPath of path) {
  563. router.register.call(router, curPath, methods, middleware, opts);
  564. }
  565. return this;
  566. }
  567. // create route
  568. const route = new Layer(path, methods, middleware, {
  569. end: opts.end === false ? opts.end : true,
  570. name: opts.name,
  571. sensitive: opts.sensitive || this.opts.sensitive || false,
  572. strict: opts.strict || this.opts.strict || false,
  573. prefix: opts.prefix || this.opts.prefix || '',
  574. ignoreCaptures: opts.ignoreCaptures
  575. });
  576. if (this.opts.prefix) {
  577. route.setPrefix(this.opts.prefix);
  578. }
  579. // add parameter middleware
  580. for (let i = 0; i < Object.keys(this.params).length; i++) {
  581. const param = Object.keys(this.params)[i];
  582. route.param(param, this.params[param]);
  583. }
  584. stack.push(route);
  585. debug('defined route %s %s', route.methods, route.path);
  586. return route;
  587. };
  588. /**
  589. * Lookup route with given `name`.
  590. *
  591. * @param {String} name
  592. * @returns {Layer|false}
  593. */
  594. Router.prototype.route = function (name) {
  595. const routes = this.stack;
  596. for (let len = routes.length, i = 0; i < len; i++) {
  597. if (routes[i].name && routes[i].name === name) return routes[i];
  598. }
  599. return false;
  600. };
  601. /**
  602. * Generate URL for route. Takes a route name and map of named `params`.
  603. *
  604. * @example
  605. *
  606. * ```javascript
  607. * router.get('user', '/users/:id', (ctx, next) => {
  608. * // ...
  609. * });
  610. *
  611. * router.url('user', 3);
  612. * // => "/users/3"
  613. *
  614. * router.url('user', { id: 3 });
  615. * // => "/users/3"
  616. *
  617. * router.use((ctx, next) => {
  618. * // redirect to named route
  619. * ctx.redirect(ctx.router.url('sign-in'));
  620. * })
  621. *
  622. * router.url('user', { id: 3 }, { query: { limit: 1 } });
  623. * // => "/users/3?limit=1"
  624. *
  625. * router.url('user', { id: 3 }, { query: "limit=1" });
  626. * // => "/users/3?limit=1"
  627. * ```
  628. *
  629. * @param {String} name route name
  630. * @param {Object} params url parameters
  631. * @param {Object} [options] options parameter
  632. * @param {Object|String} [options.query] query options
  633. * @returns {String|Error}
  634. */
  635. Router.prototype.url = function (name, params) {
  636. const route = this.route(name);
  637. if (route) {
  638. const args = Array.prototype.slice.call(arguments, 1);
  639. return route.url.apply(route, args);
  640. }
  641. return new Error(`No route found for name: ${String(name)}`);
  642. };
  643. /**
  644. * Match given `path` and return corresponding routes.
  645. *
  646. * @param {String} path
  647. * @param {String} method
  648. * @returns {Object.<path, pathAndMethod>} returns layers that matched path and
  649. * path and method.
  650. * @private
  651. */
  652. Router.prototype.match = function (path, method) {
  653. const layers = this.stack;
  654. let layer;
  655. const matched = {
  656. path: [],
  657. pathAndMethod: [],
  658. route: false
  659. };
  660. for (let len = layers.length, i = 0; i < len; i++) {
  661. layer = layers[i];
  662. debug('test %s %s', layer.path, layer.regexp);
  663. // eslint-disable-next-line unicorn/prefer-regexp-test
  664. if (layer.match(path)) {
  665. matched.path.push(layer);
  666. if (layer.methods.length === 0 || ~layer.methods.indexOf(method)) {
  667. matched.pathAndMethod.push(layer);
  668. if (layer.methods.length > 0) matched.route = true;
  669. }
  670. }
  671. }
  672. return matched;
  673. };
  674. /**
  675. * Match given `input` to allowed host
  676. * @param {String} input
  677. * @returns {boolean}
  678. */
  679. Router.prototype.matchHost = function (input) {
  680. const { host } = this;
  681. if (!host) {
  682. return true;
  683. }
  684. if (!input) {
  685. return false;
  686. }
  687. if (typeof host === 'string') {
  688. return input === host;
  689. }
  690. if (typeof host === 'object' && host instanceof RegExp) {
  691. return host.test(input);
  692. }
  693. };
  694. /**
  695. * Run middleware for named route parameters. Useful for auto-loading or
  696. * validation.
  697. *
  698. * @example
  699. *
  700. * ```javascript
  701. * router
  702. * .param('user', (id, ctx, next) => {
  703. * ctx.user = users[id];
  704. * if (!ctx.user) return ctx.status = 404;
  705. * return next();
  706. * })
  707. * .get('/users/:user', ctx => {
  708. * ctx.body = ctx.user;
  709. * })
  710. * .get('/users/:user/friends', ctx => {
  711. * return ctx.user.getFriends().then(function(friends) {
  712. * ctx.body = friends;
  713. * });
  714. * })
  715. * // /users/3 => {"id": 3, "name": "Alex"}
  716. * // /users/3/friends => [{"id": 4, "name": "TJ"}]
  717. * ```
  718. *
  719. * @param {String} param
  720. * @param {Function} middleware
  721. * @returns {Router}
  722. */
  723. Router.prototype.param = function (param, middleware) {
  724. this.params[param] = middleware;
  725. for (let i = 0; i < this.stack.length; i++) {
  726. const route = this.stack[i];
  727. route.param(param, middleware);
  728. }
  729. return this;
  730. };
  731. /**
  732. * Generate URL from url pattern and given `params`.
  733. *
  734. * @example
  735. *
  736. * ```javascript
  737. * const url = Router.url('/users/:id', {id: 1});
  738. * // => "/users/1"
  739. * ```
  740. *
  741. * @param {String} path url pattern
  742. * @param {Object} params url parameters
  743. * @returns {String}
  744. */
  745. Router.url = function (path) {
  746. const args = Array.prototype.slice.call(arguments, 1);
  747. return Layer.prototype.url.apply({ path }, args);
  748. };