route.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. /*!
  2. * router
  3. * Copyright(c) 2013 Roman Shtylman
  4. * Copyright(c) 2014-2022 Douglas Christopher Wilson
  5. * MIT Licensed
  6. */
  7. 'use strict'
  8. /**
  9. * Module dependencies.
  10. * @private
  11. */
  12. const debug = require('debug')('router:route')
  13. const Layer = require('./layer')
  14. const { METHODS } = require('node:http')
  15. /**
  16. * Module variables.
  17. * @private
  18. */
  19. const slice = Array.prototype.slice
  20. const flatten = Array.prototype.flat
  21. const methods = METHODS.map((method) => method.toLowerCase())
  22. /**
  23. * Expose `Route`.
  24. */
  25. module.exports = Route
  26. /**
  27. * Initialize `Route` with the given `path`,
  28. *
  29. * @param {String} path
  30. * @api private
  31. */
  32. function Route (path) {
  33. debug('new %o', path)
  34. this.path = path
  35. this.stack = []
  36. // route handlers for various http methods
  37. this.methods = Object.create(null)
  38. }
  39. /**
  40. * @private
  41. */
  42. Route.prototype._handlesMethod = function _handlesMethod (method) {
  43. if (this.methods._all) {
  44. return true
  45. }
  46. // normalize name
  47. let name = typeof method === 'string'
  48. ? method.toLowerCase()
  49. : method
  50. if (name === 'head' && !this.methods.head) {
  51. name = 'get'
  52. }
  53. return Boolean(this.methods[name])
  54. }
  55. /**
  56. * @return {array} supported HTTP methods
  57. * @private
  58. */
  59. Route.prototype._methods = function _methods () {
  60. const methods = Object.keys(this.methods)
  61. // append automatic head
  62. if (this.methods.get && !this.methods.head) {
  63. methods.push('head')
  64. }
  65. for (let i = 0; i < methods.length; i++) {
  66. // make upper case
  67. methods[i] = methods[i].toUpperCase()
  68. }
  69. return methods
  70. }
  71. /**
  72. * dispatch req, res into this route
  73. *
  74. * @private
  75. */
  76. Route.prototype.dispatch = function dispatch (req, res, done) {
  77. let idx = 0
  78. const stack = this.stack
  79. let sync = 0
  80. if (stack.length === 0) {
  81. return done()
  82. }
  83. let method = typeof req.method === 'string'
  84. ? req.method.toLowerCase()
  85. : req.method
  86. if (method === 'head' && !this.methods.head) {
  87. method = 'get'
  88. }
  89. req.route = this
  90. next()
  91. function next (err) {
  92. // signal to exit route
  93. if (err && err === 'route') {
  94. return done()
  95. }
  96. // signal to exit router
  97. if (err && err === 'router') {
  98. return done(err)
  99. }
  100. // no more matching layers
  101. if (idx >= stack.length) {
  102. return done(err)
  103. }
  104. // max sync stack
  105. if (++sync > 100) {
  106. return setImmediate(next, err)
  107. }
  108. let layer
  109. let match
  110. // find next matching layer
  111. while (match !== true && idx < stack.length) {
  112. layer = stack[idx++]
  113. match = !layer.method || layer.method === method
  114. }
  115. // no match
  116. if (match !== true) {
  117. return done(err)
  118. }
  119. if (err) {
  120. layer.handleError(err, req, res, next)
  121. } else {
  122. layer.handleRequest(req, res, next)
  123. }
  124. sync = 0
  125. }
  126. }
  127. /**
  128. * Add a handler for all HTTP verbs to this route.
  129. *
  130. * Behaves just like middleware and can respond or call `next`
  131. * to continue processing.
  132. *
  133. * You can use multiple `.all` call to add multiple handlers.
  134. *
  135. * function check_something(req, res, next){
  136. * next()
  137. * }
  138. *
  139. * function validate_user(req, res, next){
  140. * next()
  141. * }
  142. *
  143. * route
  144. * .all(validate_user)
  145. * .all(check_something)
  146. * .get(function(req, res, next){
  147. * res.send('hello world')
  148. * })
  149. *
  150. * @param {array|function} handler
  151. * @return {Route} for chaining
  152. * @api public
  153. */
  154. Route.prototype.all = function all (handler) {
  155. const callbacks = flatten.call(slice.call(arguments), Infinity)
  156. if (callbacks.length === 0) {
  157. throw new TypeError('argument handler is required')
  158. }
  159. for (let i = 0; i < callbacks.length; i++) {
  160. const fn = callbacks[i]
  161. if (typeof fn !== 'function') {
  162. throw new TypeError('argument handler must be a function')
  163. }
  164. const layer = Layer('/', {}, fn)
  165. layer.method = undefined
  166. this.methods._all = true
  167. this.stack.push(layer)
  168. }
  169. return this
  170. }
  171. methods.forEach(function (method) {
  172. Route.prototype[method] = function (handler) {
  173. const callbacks = flatten.call(slice.call(arguments), Infinity)
  174. if (callbacks.length === 0) {
  175. throw new TypeError('argument handler is required')
  176. }
  177. for (let i = 0; i < callbacks.length; i++) {
  178. const fn = callbacks[i]
  179. if (typeof fn !== 'function') {
  180. throw new TypeError('argument handler must be a function')
  181. }
  182. debug('%s %s', method, this.path)
  183. const layer = Layer('/', {}, fn)
  184. layer.method = method
  185. this.methods[method] = true
  186. this.stack.push(layer)
  187. }
  188. return this
  189. }
  190. })