Buckets:
| /*! | |
| * router | |
| * Copyright(c) 2013 Roman Shtylman | |
| * Copyright(c) 2014-2022 Douglas Christopher Wilson | |
| * MIT Licensed | |
| */ | |
| /** | |
| * Module dependencies. | |
| * @private | |
| */ | |
| const isPromise = require('is-promise') | |
| const Layer = require('./lib/layer') | |
| const { METHODS } = require('node:http') | |
| const parseUrl = require('parseurl') | |
| const Route = require('./lib/route') | |
| const debug = require('debug')('router') | |
| const deprecate = require('depd')('router') | |
| /** | |
| * Module variables. | |
| * @private | |
| */ | |
| const slice = Array.prototype.slice | |
| const flatten = Array.prototype.flat | |
| const methods = METHODS.map((method) => method.toLowerCase()) | |
| /** | |
| * Expose `Router`. | |
| */ | |
| module.exports = Router | |
| /** | |
| * Expose `Route`. | |
| */ | |
| module.exports.Route = Route | |
| /** | |
| * Initialize a new `Router` with the given `options`. | |
| * | |
| * @param {object} [options] | |
| * @return {Router} which is a callable function | |
| * @public | |
| */ | |
| function Router (options) { | |
| if (!(this instanceof Router)) { | |
| return new Router(options) | |
| } | |
| const opts = options || {} | |
| function router (req, res, next) { | |
| router.handle(req, res, next) | |
| } | |
| // inherit from the correct prototype | |
| Object.setPrototypeOf(router, this) | |
| router.caseSensitive = opts.caseSensitive | |
| router.mergeParams = opts.mergeParams | |
| router.params = {} | |
| router.strict = opts.strict | |
| router.stack = [] | |
| return router | |
| } | |
| /** | |
| * Router prototype inherits from a Function. | |
| */ | |
| /* istanbul ignore next */ | |
| Router.prototype = function () {} | |
| /** | |
| * Map the given param placeholder `name`(s) to the given callback. | |
| * | |
| * Parameter mapping is used to provide pre-conditions to routes | |
| * which use normalized placeholders. For example a _:user_id_ parameter | |
| * could automatically load a user's information from the database without | |
| * any additional code. | |
| * | |
| * The callback uses the same signature as middleware, the only difference | |
| * being that the value of the placeholder is passed, in this case the _id_ | |
| * of the user. Once the `next()` function is invoked, just like middleware | |
| * it will continue on to execute the route, or subsequent parameter functions. | |
| * | |
| * Just like in middleware, you must either respond to the request or call next | |
| * to avoid stalling the request. | |
| * | |
| * router.param('user_id', function(req, res, next, id){ | |
| * User.find(id, function(err, user){ | |
| * if (err) { | |
| * return next(err) | |
| * } else if (!user) { | |
| * return next(new Error('failed to load user')) | |
| * } | |
| * req.user = user | |
| * next() | |
| * }) | |
| * }) | |
| * | |
| * @param {string} name | |
| * @param {function} fn | |
| * @public | |
| */ | |
| Router.prototype.param = function param (name, fn) { | |
| if (!name) { | |
| throw new TypeError('argument name is required') | |
| } | |
| if (typeof name !== 'string') { | |
| throw new TypeError('argument name must be a string') | |
| } | |
| if (!fn) { | |
| throw new TypeError('argument fn is required') | |
| } | |
| if (typeof fn !== 'function') { | |
| throw new TypeError('argument fn must be a function') | |
| } | |
| let params = this.params[name] | |
| if (!params) { | |
| params = this.params[name] = [] | |
| } | |
| params.push(fn) | |
| return this | |
| } | |
| /** | |
| * Dispatch a req, res into the router. | |
| * | |
| * @private | |
| */ | |
| Router.prototype.handle = function handle (req, res, callback) { | |
| if (!callback) { | |
| throw new TypeError('argument callback is required') | |
| } | |
| debug('dispatching %s %s', req.method, req.url) | |
| let idx = 0 | |
| let methods | |
| const protohost = getProtohost(req.url) || '' | |
| let removed = '' | |
| const self = this | |
| let slashAdded = false | |
| let sync = 0 | |
| const paramcalled = {} | |
| // middleware and routes | |
| const stack = this.stack | |
| // manage inter-router variables | |
| const parentParams = req.params | |
| const parentUrl = req.baseUrl || '' | |
| let done = restore(callback, req, 'baseUrl', 'next', 'params') | |
| // setup next layer | |
| req.next = next | |
| // for options requests, respond with a default if nothing else responds | |
| if (req.method === 'OPTIONS') { | |
| methods = [] | |
| done = wrap(done, generateOptionsResponder(res, methods)) | |
| } | |
| // setup basic req values | |
| req.baseUrl = parentUrl | |
| req.originalUrl = req.originalUrl || req.url | |
| next() | |
| function next (err) { | |
| let layerError = err === 'route' | |
| ? null | |
| : err | |
| // remove added slash | |
| if (slashAdded) { | |
| req.url = req.url.slice(1) | |
| slashAdded = false | |
| } | |
| // restore altered req.url | |
| if (removed.length !== 0) { | |
| req.baseUrl = parentUrl | |
| req.url = protohost + removed + req.url.slice(protohost.length) | |
| removed = '' | |
| } | |
| // signal to exit router | |
| if (layerError === 'router') { | |
| setImmediate(done, null) | |
| return | |
| } | |
| // no more matching layers | |
| if (idx >= stack.length) { | |
| setImmediate(done, layerError) | |
| return | |
| } | |
| // max sync stack | |
| if (++sync > 100) { | |
| return setImmediate(next, err) | |
| } | |
| // get pathname of request | |
| const path = getPathname(req) | |
| if (path == null) { | |
| return done(layerError) | |
| } | |
| // find next matching layer | |
| let layer | |
| let match | |
| let route | |
| while (match !== true && idx < stack.length) { | |
| layer = stack[idx++] | |
| match = matchLayer(layer, path) | |
| route = layer.route | |
| if (typeof match !== 'boolean') { | |
| // hold on to layerError | |
| layerError = layerError || match | |
| } | |
| if (match !== true) { | |
| continue | |
| } | |
| if (!route) { | |
| // process non-route handlers normally | |
| continue | |
| } | |
| if (layerError) { | |
| // routes do not match with a pending error | |
| match = false | |
| continue | |
| } | |
| const method = req.method | |
| const hasMethod = route._handlesMethod(method) | |
| // build up automatic options response | |
| if (!hasMethod && method === 'OPTIONS' && methods) { | |
| methods.push.apply(methods, route._methods()) | |
| } | |
| // don't even bother matching route | |
| if (!hasMethod && method !== 'HEAD') { | |
| match = false | |
| } | |
| } | |
| // no match | |
| if (match !== true) { | |
| return done(layerError) | |
| } | |
| // store route for dispatch on change | |
| if (route) { | |
| req.route = route | |
| } | |
| // Capture one-time layer values | |
| req.params = self.mergeParams | |
| ? mergeParams(layer.params, parentParams) | |
| : layer.params | |
| const layerPath = layer.path | |
| // this should be done for the layer | |
| processParams(self.params, layer, paramcalled, req, res, function (err) { | |
| if (err) { | |
| next(layerError || err) | |
| } else if (route) { | |
| layer.handleRequest(req, res, next) | |
| } else { | |
| trimPrefix(layer, layerError, layerPath, path) | |
| } | |
| sync = 0 | |
| }) | |
| } | |
| function trimPrefix (layer, layerError, layerPath, path) { | |
| if (layerPath.length !== 0) { | |
| // Validate path is a prefix match | |
| if (layerPath !== path.substring(0, layerPath.length)) { | |
| next(layerError) | |
| return | |
| } | |
| // Validate path breaks on a path separator | |
| const c = path[layerPath.length] | |
| if (c && c !== '/') { | |
| next(layerError) | |
| return | |
| } | |
| // Trim off the part of the url that matches the route | |
| // middleware (.use stuff) needs to have the path stripped | |
| debug('trim prefix (%s) from url %s', layerPath, req.url) | |
| removed = layerPath | |
| req.url = protohost + req.url.slice(protohost.length + removed.length) | |
| // Ensure leading slash | |
| if (!protohost && req.url[0] !== '/') { | |
| req.url = '/' + req.url | |
| slashAdded = true | |
| } | |
| // Setup base URL (no trailing slash) | |
| req.baseUrl = parentUrl + (removed[removed.length - 1] === '/' | |
| ? removed.substring(0, removed.length - 1) | |
| : removed) | |
| } | |
| debug('%s %s : %s', layer.name, layerPath, req.originalUrl) | |
| if (layerError) { | |
| layer.handleError(layerError, req, res, next) | |
| } else { | |
| layer.handleRequest(req, res, next) | |
| } | |
| } | |
| } | |
| /** | |
| * Use the given middleware function, with optional path, defaulting to "/". | |
| * | |
| * Use (like `.all`) will run for any http METHOD, but it will not add | |
| * handlers for those methods so OPTIONS requests will not consider `.use` | |
| * functions even if they could respond. | |
| * | |
| * The other difference is that _route_ path is stripped and not visible | |
| * to the handler function. The main effect of this feature is that mounted | |
| * handlers can operate without any code changes regardless of the "prefix" | |
| * pathname. | |
| * | |
| * @public | |
| */ | |
| Router.prototype.use = function use (handler) { | |
| let offset = 0 | |
| let path = '/' | |
| // default path to '/' | |
| // disambiguate router.use([handler]) | |
| if (typeof handler !== 'function') { | |
| let arg = handler | |
| while (Array.isArray(arg) && arg.length !== 0) { | |
| arg = arg[0] | |
| } | |
| // first arg is the path | |
| if (typeof arg !== 'function') { | |
| offset = 1 | |
| path = handler | |
| } | |
| } | |
| const callbacks = flatten.call(slice.call(arguments, offset), 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') | |
| } | |
| // add the middleware | |
| debug('use %o %s', path, fn.name || '<anonymous>') | |
| const layer = new Layer(path, { | |
| sensitive: this.caseSensitive, | |
| strict: false, | |
| end: false | |
| }, fn) | |
| layer.route = undefined | |
| this.stack.push(layer) | |
| } | |
| return this | |
| } | |
| /** | |
| * Create a new Route for the given path. | |
| * | |
| * Each route contains a separate middleware stack and VERB handlers. | |
| * | |
| * See the Route api documentation for details on adding handlers | |
| * and middleware to routes. | |
| * | |
| * @param {string} path | |
| * @return {Route} | |
| * @public | |
| */ | |
| Router.prototype.route = function route (path) { | |
| const route = new Route(path) | |
| const layer = new Layer(path, { | |
| sensitive: this.caseSensitive, | |
| strict: this.strict, | |
| end: true | |
| }, handle) | |
| function handle (req, res, next) { | |
| route.dispatch(req, res, next) | |
| } | |
| layer.route = route | |
| this.stack.push(layer) | |
| return route | |
| } | |
| // create Router#VERB functions | |
| methods.concat('all').forEach(function (method) { | |
| Router.prototype[method] = function (path) { | |
| const route = this.route(path) | |
| route[method].apply(route, slice.call(arguments, 1)) | |
| return this | |
| } | |
| }) | |
| /** | |
| * Generate a callback that will make an OPTIONS response. | |
| * | |
| * @param {OutgoingMessage} res | |
| * @param {array} methods | |
| * @private | |
| */ | |
| function generateOptionsResponder (res, methods) { | |
| return function onDone (fn, err) { | |
| if (err || methods.length === 0) { | |
| return fn(err) | |
| } | |
| trySendOptionsResponse(res, methods, fn) | |
| } | |
| } | |
| /** | |
| * Get pathname of request. | |
| * | |
| * @param {IncomingMessage} req | |
| * @private | |
| */ | |
| function getPathname (req) { | |
| try { | |
| return parseUrl(req).pathname | |
| } catch (err) { | |
| return undefined | |
| } | |
| } | |
| /** | |
| * Get get protocol + host for a URL. | |
| * | |
| * @param {string} url | |
| * @private | |
| */ | |
| function getProtohost (url) { | |
| if (typeof url !== 'string' || url.length === 0 || url[0] === '/') { | |
| return undefined | |
| } | |
| const searchIndex = url.indexOf('?') | |
| const pathLength = searchIndex !== -1 | |
| ? searchIndex | |
| : url.length | |
| const fqdnIndex = url.substring(0, pathLength).indexOf('://') | |
| return fqdnIndex !== -1 | |
| ? url.substring(0, url.indexOf('/', 3 + fqdnIndex)) | |
| : undefined | |
| } | |
| /** | |
| * Match path to a layer. | |
| * | |
| * @param {Layer} layer | |
| * @param {string} path | |
| * @private | |
| */ | |
| function matchLayer (layer, path) { | |
| try { | |
| return layer.match(path) | |
| } catch (err) { | |
| return err | |
| } | |
| } | |
| /** | |
| * Merge params with parent params | |
| * | |
| * @private | |
| */ | |
| function mergeParams (params, parent) { | |
| if (typeof parent !== 'object' || !parent) { | |
| return params | |
| } | |
| // make copy of parent for base | |
| const obj = Object.assign({}, parent) | |
| // simple non-numeric merging | |
| if (!(0 in params) || !(0 in parent)) { | |
| return Object.assign(obj, params) | |
| } | |
| let i = 0 | |
| let o = 0 | |
| // determine numeric gap in params | |
| while (i in params) { | |
| i++ | |
| } | |
| // determine numeric gap in parent | |
| while (o in parent) { | |
| o++ | |
| } | |
| // offset numeric indices in params before merge | |
| for (i--; i >= 0; i--) { | |
| params[i + o] = params[i] | |
| // create holes for the merge when necessary | |
| if (i < o) { | |
| delete params[i] | |
| } | |
| } | |
| return Object.assign(obj, params) | |
| } | |
| /** | |
| * Process any parameters for the layer. | |
| * | |
| * @private | |
| */ | |
| function processParams (params, layer, called, req, res, done) { | |
| // captured parameters from the layer, keys and values | |
| const keys = layer.keys | |
| // fast track | |
| if (!keys || keys.length === 0) { | |
| return done() | |
| } | |
| let i = 0 | |
| let paramIndex = 0 | |
| let key | |
| let paramVal | |
| let paramCallbacks | |
| let paramCalled | |
| // process params in order | |
| // param callbacks can be async | |
| function param (err) { | |
| if (err) { | |
| return done(err) | |
| } | |
| if (i >= keys.length) { | |
| return done() | |
| } | |
| paramIndex = 0 | |
| key = keys[i++] | |
| paramVal = req.params[key] | |
| paramCallbacks = params[key] | |
| paramCalled = called[key] | |
| if (paramVal === undefined || !paramCallbacks) { | |
| return param() | |
| } | |
| // param previously called with same value or error occurred | |
| if (paramCalled && (paramCalled.match === paramVal || | |
| (paramCalled.error && paramCalled.error !== 'route'))) { | |
| // restore value | |
| req.params[key] = paramCalled.value | |
| // next param | |
| return param(paramCalled.error) | |
| } | |
| called[key] = paramCalled = { | |
| error: null, | |
| match: paramVal, | |
| value: paramVal | |
| } | |
| paramCallback() | |
| } | |
| // single param callbacks | |
| function paramCallback (err) { | |
| const fn = paramCallbacks[paramIndex++] | |
| // store updated value | |
| paramCalled.value = req.params[key] | |
| if (err) { | |
| // store error | |
| paramCalled.error = err | |
| param(err) | |
| return | |
| } | |
| if (!fn) return param() | |
| try { | |
| const ret = fn(req, res, paramCallback, paramVal, key) | |
| if (isPromise(ret)) { | |
| if (!(ret instanceof Promise)) { | |
| deprecate('parameters that are Promise-like are deprecated, use a native Promise instead') | |
| } | |
| ret.then(null, function (error) { | |
| paramCallback(error || new Error('Rejected promise')) | |
| }) | |
| } | |
| } catch (e) { | |
| paramCallback(e) | |
| } | |
| } | |
| param() | |
| } | |
| /** | |
| * Restore obj props after function | |
| * | |
| * @private | |
| */ | |
| function restore (fn, obj) { | |
| const props = new Array(arguments.length - 2) | |
| const vals = new Array(arguments.length - 2) | |
| for (let i = 0; i < props.length; i++) { | |
| props[i] = arguments[i + 2] | |
| vals[i] = obj[props[i]] | |
| } | |
| return function () { | |
| // restore vals | |
| for (let i = 0; i < props.length; i++) { | |
| obj[props[i]] = vals[i] | |
| } | |
| return fn.apply(this, arguments) | |
| } | |
| } | |
| /** | |
| * Send an OPTIONS response. | |
| * | |
| * @private | |
| */ | |
| function sendOptionsResponse (res, methods) { | |
| const options = Object.create(null) | |
| // build unique method map | |
| for (let i = 0; i < methods.length; i++) { | |
| options[methods[i]] = true | |
| } | |
| // construct the allow list | |
| const allow = Object.keys(options).sort().join(', ') | |
| // send response | |
| res.setHeader('Allow', allow) | |
| res.setHeader('Content-Length', Buffer.byteLength(allow)) | |
| res.setHeader('Content-Type', 'text/plain') | |
| res.setHeader('X-Content-Type-Options', 'nosniff') | |
| res.end(allow) | |
| } | |
| /** | |
| * Try to send an OPTIONS response. | |
| * | |
| * @private | |
| */ | |
| function trySendOptionsResponse (res, methods, next) { | |
| try { | |
| sendOptionsResponse(res, methods) | |
| } catch (err) { | |
| next(err) | |
| } | |
| } | |
| /** | |
| * Wrap a function | |
| * | |
| * @private | |
| */ | |
| function wrap (old, fn) { | |
| return function proxy () { | |
| const args = new Array(arguments.length + 1) | |
| args[0] = old | |
| for (let i = 0, len = arguments.length; i < len; i++) { | |
| args[i + 1] = arguments[i] | |
| } | |
| fn.apply(this, args) | |
| } | |
| } | |
Xet Storage Details
- Size:
- 15.9 kB
- Xet hash:
- 15b4b1bb93c3c92346d3b604e96a6c2ebdc93c4d43a1edd626856e06c769b280
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.