243 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			243 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*!
 | |
|  * router
 | |
|  * Copyright(c) 2013 Roman Shtylman
 | |
|  * Copyright(c) 2014-2022 Douglas Christopher Wilson
 | |
|  * MIT Licensed
 | |
|  */
 | |
| 
 | |
| 'use strict'
 | |
| 
 | |
| /**
 | |
|  * Module dependencies.
 | |
|  * @private
 | |
|  */
 | |
| 
 | |
| const debug = require('debug')('router:route')
 | |
| const Layer = require('./layer')
 | |
| const { METHODS } = require('node:http')
 | |
| 
 | |
| /**
 | |
|  * Module variables.
 | |
|  * @private
 | |
|  */
 | |
| 
 | |
| const slice = Array.prototype.slice
 | |
| const flatten = Array.prototype.flat
 | |
| const methods = METHODS.map((method) => method.toLowerCase())
 | |
| 
 | |
| /**
 | |
|  * Expose `Route`.
 | |
|  */
 | |
| 
 | |
| module.exports = Route
 | |
| 
 | |
| /**
 | |
|  * Initialize `Route` with the given `path`,
 | |
|  *
 | |
|  * @param {String} path
 | |
|  * @api private
 | |
|  */
 | |
| 
 | |
| function Route (path) {
 | |
|   debug('new %o', path)
 | |
|   this.path = path
 | |
|   this.stack = []
 | |
| 
 | |
|   // route handlers for various http methods
 | |
|   this.methods = Object.create(null)
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @private
 | |
|  */
 | |
| 
 | |
| Route.prototype._handlesMethod = function _handlesMethod (method) {
 | |
|   if (this.methods._all) {
 | |
|     return true
 | |
|   }
 | |
| 
 | |
|   // normalize name
 | |
|   let name = typeof method === 'string'
 | |
|     ? method.toLowerCase()
 | |
|     : method
 | |
| 
 | |
|   if (name === 'head' && !this.methods.head) {
 | |
|     name = 'get'
 | |
|   }
 | |
| 
 | |
|   return Boolean(this.methods[name])
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @return {array} supported HTTP methods
 | |
|  * @private
 | |
|  */
 | |
| 
 | |
| Route.prototype._methods = function _methods () {
 | |
|   const methods = Object.keys(this.methods)
 | |
| 
 | |
|   // append automatic head
 | |
|   if (this.methods.get && !this.methods.head) {
 | |
|     methods.push('head')
 | |
|   }
 | |
| 
 | |
|   for (let i = 0; i < methods.length; i++) {
 | |
|     // make upper case
 | |
|     methods[i] = methods[i].toUpperCase()
 | |
|   }
 | |
| 
 | |
|   return methods
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * dispatch req, res into this route
 | |
|  *
 | |
|  * @private
 | |
|  */
 | |
| 
 | |
| Route.prototype.dispatch = function dispatch (req, res, done) {
 | |
|   let idx = 0
 | |
|   const stack = this.stack
 | |
|   let sync = 0
 | |
| 
 | |
|   if (stack.length === 0) {
 | |
|     return done()
 | |
|   }
 | |
| 
 | |
|   let method = typeof req.method === 'string'
 | |
|     ? req.method.toLowerCase()
 | |
|     : req.method
 | |
| 
 | |
|   if (method === 'head' && !this.methods.head) {
 | |
|     method = 'get'
 | |
|   }
 | |
| 
 | |
|   req.route = this
 | |
| 
 | |
|   next()
 | |
| 
 | |
|   function next (err) {
 | |
|     // signal to exit route
 | |
|     if (err && err === 'route') {
 | |
|       return done()
 | |
|     }
 | |
| 
 | |
|     // signal to exit router
 | |
|     if (err && err === 'router') {
 | |
|       return done(err)
 | |
|     }
 | |
| 
 | |
|     // no more matching layers
 | |
|     if (idx >= stack.length) {
 | |
|       return done(err)
 | |
|     }
 | |
| 
 | |
|     // max sync stack
 | |
|     if (++sync > 100) {
 | |
|       return setImmediate(next, err)
 | |
|     }
 | |
| 
 | |
|     let layer
 | |
|     let match
 | |
| 
 | |
|     // find next matching layer
 | |
|     while (match !== true && idx < stack.length) {
 | |
|       layer = stack[idx++]
 | |
|       match = !layer.method || layer.method === method
 | |
|     }
 | |
| 
 | |
|     // no match
 | |
|     if (match !== true) {
 | |
|       return done(err)
 | |
|     }
 | |
| 
 | |
|     if (err) {
 | |
|       layer.handleError(err, req, res, next)
 | |
|     } else {
 | |
|       layer.handleRequest(req, res, next)
 | |
|     }
 | |
| 
 | |
|     sync = 0
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add a handler for all HTTP verbs to this route.
 | |
|  *
 | |
|  * Behaves just like middleware and can respond or call `next`
 | |
|  * to continue processing.
 | |
|  *
 | |
|  * You can use multiple `.all` call to add multiple handlers.
 | |
|  *
 | |
|  *   function check_something(req, res, next){
 | |
|  *     next()
 | |
|  *   }
 | |
|  *
 | |
|  *   function validate_user(req, res, next){
 | |
|  *     next()
 | |
|  *   }
 | |
|  *
 | |
|  *   route
 | |
|  *   .all(validate_user)
 | |
|  *   .all(check_something)
 | |
|  *   .get(function(req, res, next){
 | |
|  *     res.send('hello world')
 | |
|  *   })
 | |
|  *
 | |
|  * @param {array|function} handler
 | |
|  * @return {Route} for chaining
 | |
|  * @api public
 | |
|  */
 | |
| 
 | |
| Route.prototype.all = function all (handler) {
 | |
|   const callbacks = flatten.call(slice.call(arguments), Infinity)
 | |
| 
 | |
|   if (callbacks.length === 0) {
 | |
|     throw new TypeError('argument handler is required')
 | |
|   }
 | |
| 
 | |
|   for (let i = 0; i < callbacks.length; i++) {
 | |
|     const fn = callbacks[i]
 | |
| 
 | |
|     if (typeof fn !== 'function') {
 | |
|       throw new TypeError('argument handler must be a function')
 | |
|     }
 | |
| 
 | |
|     const layer = Layer('/', {}, fn)
 | |
|     layer.method = undefined
 | |
| 
 | |
|     this.methods._all = true
 | |
|     this.stack.push(layer)
 | |
|   }
 | |
| 
 | |
|   return this
 | |
| }
 | |
| 
 | |
| methods.forEach(function (method) {
 | |
|   Route.prototype[method] = function (handler) {
 | |
|     const callbacks = flatten.call(slice.call(arguments), Infinity)
 | |
| 
 | |
|     if (callbacks.length === 0) {
 | |
|       throw new TypeError('argument handler is required')
 | |
|     }
 | |
| 
 | |
|     for (let i = 0; i < callbacks.length; i++) {
 | |
|       const fn = callbacks[i]
 | |
| 
 | |
|       if (typeof fn !== 'function') {
 | |
|         throw new TypeError('argument handler must be a function')
 | |
|       }
 | |
| 
 | |
|       debug('%s %s', method, this.path)
 | |
| 
 | |
|       const layer = Layer('/', {}, fn)
 | |
|       layer.method = method
 | |
| 
 | |
|       this.methods[method] = true
 | |
|       this.stack.push(layer)
 | |
|     }
 | |
| 
 | |
|     return this
 | |
|   }
 | |
| })
 |