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 | ||
|  |   } | ||
|  | }) |