You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					517 lines
				
				16 KiB
			
		
		
			
		
	
	
					517 lines
				
				16 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								'use strict'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const FindMyWay = require('find-my-way')
							 | 
						||
| 
								 | 
							
								const Context = require('./context')
							 | 
						||
| 
								 | 
							
								const handleRequest = require('./handleRequest')
							 | 
						||
| 
								 | 
							
								const { hookRunner, hookIterator, lifecycleHooks } = require('./hooks')
							 | 
						||
| 
								 | 
							
								const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS']
							 | 
						||
| 
								 | 
							
								const { normalizeSchema } = require('./schemas')
							 | 
						||
| 
								 | 
							
								const { parseHeadOnSendHandlers } = require('./headRoute')
							 | 
						||
| 
								 | 
							
								const warning = require('./warnings')
							 | 
						||
| 
								 | 
							
								const { kRequestAcceptVersion } = require('./symbols')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const {
							 | 
						||
| 
								 | 
							
								  compileSchemasForValidation,
							 | 
						||
| 
								 | 
							
								  compileSchemasForSerialization
							 | 
						||
| 
								 | 
							
								} = require('./validation')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const {
							 | 
						||
| 
								 | 
							
								  FST_ERR_SCH_VALIDATION_BUILD,
							 | 
						||
| 
								 | 
							
								  FST_ERR_SCH_SERIALIZATION_BUILD,
							 | 
						||
| 
								 | 
							
								  FST_ERR_DEFAULT_ROUTE_INVALID_TYPE,
							 | 
						||
| 
								 | 
							
								  FST_ERR_INVALID_URL
							 | 
						||
| 
								 | 
							
								} = require('./errors')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const {
							 | 
						||
| 
								 | 
							
								  kRoutePrefix,
							 | 
						||
| 
								 | 
							
								  kLogLevel,
							 | 
						||
| 
								 | 
							
								  kLogSerializers,
							 | 
						||
| 
								 | 
							
								  kHooks,
							 | 
						||
| 
								 | 
							
								  kHooksDeprecatedPreParsing,
							 | 
						||
| 
								 | 
							
								  kSchemaController,
							 | 
						||
| 
								 | 
							
								  kOptions,
							 | 
						||
| 
								 | 
							
								  kContentTypeParser,
							 | 
						||
| 
								 | 
							
								  kReply,
							 | 
						||
| 
								 | 
							
								  kReplySerializerDefault,
							 | 
						||
| 
								 | 
							
								  kReplyIsError,
							 | 
						||
| 
								 | 
							
								  kRequest,
							 | 
						||
| 
								 | 
							
								  kRequestPayloadStream,
							 | 
						||
| 
								 | 
							
								  kDisableRequestLogging,
							 | 
						||
| 
								 | 
							
								  kSchemaErrorFormatter,
							 | 
						||
| 
								 | 
							
								  kErrorHandler
							 | 
						||
| 
								 | 
							
								} = require('./symbols.js')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function buildRouting (options) {
							 | 
						||
| 
								 | 
							
								  const { keepAliveConnections } = options
							 | 
						||
| 
								 | 
							
								  const router = FindMyWay(options.config)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let avvio
							 | 
						||
| 
								 | 
							
								  let fourOhFour
							 | 
						||
| 
								 | 
							
								  let requestIdHeader
							 | 
						||
| 
								 | 
							
								  let querystringParser
							 | 
						||
| 
								 | 
							
								  let requestIdLogLabel
							 | 
						||
| 
								 | 
							
								  let logger
							 | 
						||
| 
								 | 
							
								  let hasLogger
							 | 
						||
| 
								 | 
							
								  let setupResponseListeners
							 | 
						||
| 
								 | 
							
								  let throwIfAlreadyStarted
							 | 
						||
| 
								 | 
							
								  let genReqId
							 | 
						||
| 
								 | 
							
								  let disableRequestLogging
							 | 
						||
| 
								 | 
							
								  let ignoreTrailingSlash
							 | 
						||
| 
								 | 
							
								  let return503OnClosing
							 | 
						||
| 
								 | 
							
								  let globalExposeHeadRoutes
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let closing = false
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return {
							 | 
						||
| 
								 | 
							
								    setup (options, fastifyArgs) {
							 | 
						||
| 
								 | 
							
								      avvio = fastifyArgs.avvio
							 | 
						||
| 
								 | 
							
								      fourOhFour = fastifyArgs.fourOhFour
							 | 
						||
| 
								 | 
							
								      logger = fastifyArgs.logger
							 | 
						||
| 
								 | 
							
								      hasLogger = fastifyArgs.hasLogger
							 | 
						||
| 
								 | 
							
								      setupResponseListeners = fastifyArgs.setupResponseListeners
							 | 
						||
| 
								 | 
							
								      throwIfAlreadyStarted = fastifyArgs.throwIfAlreadyStarted
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      globalExposeHeadRoutes = options.exposeHeadRoutes
							 | 
						||
| 
								 | 
							
								      requestIdHeader = options.requestIdHeader
							 | 
						||
| 
								 | 
							
								      querystringParser = options.querystringParser
							 | 
						||
| 
								 | 
							
								      requestIdLogLabel = options.requestIdLogLabel
							 | 
						||
| 
								 | 
							
								      genReqId = options.genReqId
							 | 
						||
| 
								 | 
							
								      disableRequestLogging = options.disableRequestLogging
							 | 
						||
| 
								 | 
							
								      ignoreTrailingSlash = options.ignoreTrailingSlash
							 | 
						||
| 
								 | 
							
								      return503OnClosing = Object.prototype.hasOwnProperty.call(options, 'return503OnClosing') ? options.return503OnClosing : true
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    routing: router.lookup.bind(router), // router func to find the right handler to call
							 | 
						||
| 
								 | 
							
								    route, // configure a route in the fastify instance
							 | 
						||
| 
								 | 
							
								    prepareRoute,
							 | 
						||
| 
								 | 
							
								    getDefaultRoute: function () {
							 | 
						||
| 
								 | 
							
								      return router.defaultRoute
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    setDefaultRoute: function (defaultRoute) {
							 | 
						||
| 
								 | 
							
								      if (typeof defaultRoute !== 'function') {
							 | 
						||
| 
								 | 
							
								        throw new FST_ERR_DEFAULT_ROUTE_INVALID_TYPE()
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      router.defaultRoute = defaultRoute
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    routeHandler,
							 | 
						||
| 
								 | 
							
								    closeRoutes: () => { closing = true },
							 | 
						||
| 
								 | 
							
								    printRoutes: router.prettyPrint.bind(router)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Convert shorthand to extended route declaration
							 | 
						||
| 
								 | 
							
								  function prepareRoute (method, url, options, handler) {
							 | 
						||
| 
								 | 
							
								    if (typeof url !== 'string') {
							 | 
						||
| 
								 | 
							
								      throw new FST_ERR_INVALID_URL(typeof url)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!handler && typeof options === 'function') {
							 | 
						||
| 
								 | 
							
								      handler = options // for support over direct function calls such as fastify.get() options are reused as the handler
							 | 
						||
| 
								 | 
							
								      options = {}
							 | 
						||
| 
								 | 
							
								    } else if (handler && typeof handler === 'function') {
							 | 
						||
| 
								 | 
							
								      if (Object.prototype.toString.call(options) !== '[object Object]') {
							 | 
						||
| 
								 | 
							
								        throw new Error(`Options for ${method}:${url} route must be an object`)
							 | 
						||
| 
								 | 
							
								      } else if (options.handler) {
							 | 
						||
| 
								 | 
							
								        if (typeof options.handler === 'function') {
							 | 
						||
| 
								 | 
							
								          throw new Error(`Duplicate handler for ${method}:${url} route is not allowed!`)
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          throw new Error(`Handler for ${method}:${url} route must be a function`)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    options = Object.assign({}, options, {
							 | 
						||
| 
								 | 
							
								      method,
							 | 
						||
| 
								 | 
							
								      url,
							 | 
						||
| 
								 | 
							
								      path: url,
							 | 
						||
| 
								 | 
							
								      handler: handler || (options && options.handler)
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return route.call(this, options)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Route management
							 | 
						||
| 
								 | 
							
								  function route (options) {
							 | 
						||
| 
								 | 
							
								    // Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator
							 | 
						||
| 
								 | 
							
								    const opts = { ...options }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    throwIfAlreadyStarted('Cannot add route when fastify instance is already started!')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (Array.isArray(opts.method)) {
							 | 
						||
| 
								 | 
							
								      // eslint-disable-next-line no-var
							 | 
						||
| 
								 | 
							
								      for (var i = 0; i < opts.method.length; ++i) {
							 | 
						||
| 
								 | 
							
								        const method = opts.method[i]
							 | 
						||
| 
								 | 
							
								        if (supportedMethods.indexOf(method) === -1) {
							 | 
						||
| 
								 | 
							
								          throw new Error(`${method} method is not supported!`)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      if (supportedMethods.indexOf(opts.method) === -1) {
							 | 
						||
| 
								 | 
							
								        throw new Error(`${opts.method} method is not supported!`)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const path = opts.url || opts.path
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!opts.handler) {
							 | 
						||
| 
								 | 
							
								      throw new Error(`Missing handler function for ${opts.method}:${path} route.`)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (opts.errorHandler !== undefined && typeof opts.errorHandler !== 'function') {
							 | 
						||
| 
								 | 
							
								      throw new Error(`Error Handler for ${opts.method}:${path} route, if defined, must be a function`)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    validateBodyLimitOption(opts.bodyLimit)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const prefix = this[kRoutePrefix]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.after((notHandledErr, done) => {
							 | 
						||
| 
								 | 
							
								      if (path === '/' && prefix.length && opts.method !== 'HEAD') {
							 | 
						||
| 
								 | 
							
								        switch (opts.prefixTrailingSlash) {
							 | 
						||
| 
								 | 
							
								          case 'slash':
							 | 
						||
| 
								 | 
							
								            afterRouteAdded.call(this, { path }, notHandledErr, done)
							 | 
						||
| 
								 | 
							
								            break
							 | 
						||
| 
								 | 
							
								          case 'no-slash':
							 | 
						||
| 
								 | 
							
								            afterRouteAdded.call(this, { path: '' }, notHandledErr, done)
							 | 
						||
| 
								 | 
							
								            break
							 | 
						||
| 
								 | 
							
								          case 'both':
							 | 
						||
| 
								 | 
							
								          default:
							 | 
						||
| 
								 | 
							
								            afterRouteAdded.call(this, { path: '' }, notHandledErr, done)
							 | 
						||
| 
								 | 
							
								            // If ignoreTrailingSlash is set to true we need to add only the '' route to prevent adding an incomplete one.
							 | 
						||
| 
								 | 
							
								            if (ignoreTrailingSlash !== true) {
							 | 
						||
| 
								 | 
							
								              afterRouteAdded.call(this, { path, prefixing: true }, notHandledErr, done)
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else if (path && path[0] === '/' && prefix.endsWith('/')) {
							 | 
						||
| 
								 | 
							
								        // Ensure that '/prefix/' + '/route' gets registered as '/prefix/route'
							 | 
						||
| 
								 | 
							
								        afterRouteAdded.call(this, { path: path.slice(1) }, notHandledErr, done)
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        afterRouteAdded.call(this, { path }, notHandledErr, done)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // chainable api
							 | 
						||
| 
								 | 
							
								    return this
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * This function sets up a new route, its log serializers, and triggers route hooks.
							 | 
						||
| 
								 | 
							
								     *
							 | 
						||
| 
								 | 
							
								     * @param {object} opts contains route `path` and `prefixing` flag which indicates if this is an auto-prefixed route, e.g. `fastify.register(routes, { prefix: '/foo' })`
							 | 
						||
| 
								 | 
							
								     * @param {*} notHandledErr error object to be passed back to the original invoker
							 | 
						||
| 
								 | 
							
								     * @param {*} done callback
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    function afterRouteAdded ({ path, prefixing = false }, notHandledErr, done) {
							 | 
						||
| 
								 | 
							
								      const url = prefix + path
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      opts.url = url
							 | 
						||
| 
								 | 
							
								      opts.path = url
							 | 
						||
| 
								 | 
							
								      opts.routePath = path
							 | 
						||
| 
								 | 
							
								      opts.prefix = prefix
							 | 
						||
| 
								 | 
							
								      opts.logLevel = opts.logLevel || this[kLogLevel]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (this[kLogSerializers] || opts.logSerializers) {
							 | 
						||
| 
								 | 
							
								        opts.logSerializers = Object.assign(Object.create(this[kLogSerializers]), opts.logSerializers)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (opts.attachValidation == null) {
							 | 
						||
| 
								 | 
							
								        opts.attachValidation = false
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (prefixing === false) {
							 | 
						||
| 
								 | 
							
								      // run 'onRoute' hooks
							 | 
						||
| 
								 | 
							
								        for (const hook of this[kHooks].onRoute) {
							 | 
						||
| 
								 | 
							
								          try {
							 | 
						||
| 
								 | 
							
								            hook.call(this, opts)
							 | 
						||
| 
								 | 
							
								          } catch (error) {
							 | 
						||
| 
								 | 
							
								            done(error)
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const config = {
							 | 
						||
| 
								 | 
							
								        ...opts.config,
							 | 
						||
| 
								 | 
							
								        url,
							 | 
						||
| 
								 | 
							
								        method: opts.method
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      const constraints = opts.constraints || {}
							 | 
						||
| 
								 | 
							
								      if (opts.version) {
							 | 
						||
| 
								 | 
							
								        warning.emit('FSTDEP008')
							 | 
						||
| 
								 | 
							
								        constraints.version = opts.version
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const context = new Context(
							 | 
						||
| 
								 | 
							
								        opts.schema,
							 | 
						||
| 
								 | 
							
								        opts.handler.bind(this),
							 | 
						||
| 
								 | 
							
								        this[kReply],
							 | 
						||
| 
								 | 
							
								        this[kRequest],
							 | 
						||
| 
								 | 
							
								        this[kContentTypeParser],
							 | 
						||
| 
								 | 
							
								        config,
							 | 
						||
| 
								 | 
							
								        opts.errorHandler || this[kErrorHandler],
							 | 
						||
| 
								 | 
							
								        opts.bodyLimit,
							 | 
						||
| 
								 | 
							
								        opts.logLevel,
							 | 
						||
| 
								 | 
							
								        opts.logSerializers,
							 | 
						||
| 
								 | 
							
								        opts.attachValidation,
							 | 
						||
| 
								 | 
							
								        this[kReplySerializerDefault],
							 | 
						||
| 
								 | 
							
								        opts.schemaErrorFormatter || this[kSchemaErrorFormatter]
							 | 
						||
| 
								 | 
							
								      )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const headRouteExists = router.find('HEAD', url, constraints) != null
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      try {
							 | 
						||
| 
								 | 
							
								        router.on(opts.method, opts.url, { constraints }, routeHandler, context)
							 | 
						||
| 
								 | 
							
								      } catch (err) {
							 | 
						||
| 
								 | 
							
								        done(err)
							 | 
						||
| 
								 | 
							
								        return
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const { exposeHeadRoute } = opts
							 | 
						||
| 
								 | 
							
								      const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
							 | 
						||
| 
								 | 
							
								      const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (shouldExposeHead && options.method === 'GET' && !headRouteExists) {
							 | 
						||
| 
								 | 
							
								        const onSendHandlers = parseHeadOnSendHandlers(opts.onSend)
							 | 
						||
| 
								 | 
							
								        prepareRoute.call(this, 'HEAD', path, { ...opts, onSend: onSendHandlers })
							 | 
						||
| 
								 | 
							
								      } else if (headRouteExists && exposeHeadRoute) {
							 | 
						||
| 
								 | 
							
								        warning.emit('FSTDEP007')
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // It can happen that a user registers a plugin with some hooks *after*
							 | 
						||
| 
								 | 
							
								      // the route registration. To be sure to also load those hooks,
							 | 
						||
| 
								 | 
							
								      // we must listen for the avvio's preReady event, and update the context object accordingly.
							 | 
						||
| 
								 | 
							
								      avvio.once('preReady', () => {
							 | 
						||
| 
								 | 
							
								        for (const hook of lifecycleHooks) {
							 | 
						||
| 
								 | 
							
								          const toSet = this[kHooks][hook]
							 | 
						||
| 
								 | 
							
								            .concat(opts[hook] || [])
							 | 
						||
| 
								 | 
							
								            .map(h => {
							 | 
						||
| 
								 | 
							
								              const bound = h.bind(this)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								              // Track hooks deprecation markers
							 | 
						||
| 
								 | 
							
								              if (hook === 'preParsing') {
							 | 
						||
| 
								 | 
							
								                // Check for deprecation syntax
							 | 
						||
| 
								 | 
							
								                if (h.length === (h.constructor.name === 'AsyncFunction' ? 2 : 3)) {
							 | 
						||
| 
								 | 
							
								                  warning.emit('FSTDEP004')
							 | 
						||
| 
								 | 
							
								                  bound[kHooksDeprecatedPreParsing] = true
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								              }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								              return bound
							 | 
						||
| 
								 | 
							
								            })
							 | 
						||
| 
								 | 
							
								          context[hook] = toSet.length ? toSet : null
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Must store the 404 Context in 'preReady' because it is only guaranteed to
							 | 
						||
| 
								 | 
							
								        // be available after all of the plugins and routes have been loaded.
							 | 
						||
| 
								 | 
							
								        fourOhFour.setContext(this, context)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (opts.schema) {
							 | 
						||
| 
								 | 
							
								          context.schema = normalizeSchema(context.schema, this.initialConfig)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          const schemaController = this[kSchemaController]
							 | 
						||
| 
								 | 
							
								          if (!opts.validatorCompiler && (opts.schema.body || opts.schema.headers || opts.schema.querystring || opts.schema.params)) {
							 | 
						||
| 
								 | 
							
								            schemaController.setupValidator(this[kOptions])
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          try {
							 | 
						||
| 
								 | 
							
								            compileSchemasForValidation(context, opts.validatorCompiler || schemaController.validatorCompiler)
							 | 
						||
| 
								 | 
							
								          } catch (error) {
							 | 
						||
| 
								 | 
							
								            throw new FST_ERR_SCH_VALIDATION_BUILD(opts.method, url, error.message)
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          if (opts.schema.response && !opts.serializerCompiler) {
							 | 
						||
| 
								 | 
							
								            schemaController.setupSerializer(this[kOptions])
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          try {
							 | 
						||
| 
								 | 
							
								            compileSchemasForSerialization(context, opts.serializerCompiler || schemaController.serializerCompiler)
							 | 
						||
| 
								 | 
							
								          } catch (error) {
							 | 
						||
| 
								 | 
							
								            throw new FST_ERR_SCH_SERIALIZATION_BUILD(opts.method, url, error.message)
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      done(notHandledErr)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // HTTP request entry point, the routing has already been executed
							 | 
						||
| 
								 | 
							
								  function routeHandler (req, res, params, context) {
							 | 
						||
| 
								 | 
							
								    if (closing === true) {
							 | 
						||
| 
								 | 
							
								      /* istanbul ignore next mac, windows */
							 | 
						||
| 
								 | 
							
								      if (req.httpVersionMajor !== 2) {
							 | 
						||
| 
								 | 
							
								        res.once('finish', () => req.destroy())
							 | 
						||
| 
								 | 
							
								        res.setHeader('Connection', 'close')
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (return503OnClosing) {
							 | 
						||
| 
								 | 
							
								        const headers = {
							 | 
						||
| 
								 | 
							
								          'Content-Type': 'application/json',
							 | 
						||
| 
								 | 
							
								          'Content-Length': '80'
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        res.writeHead(503, headers)
							 | 
						||
| 
								 | 
							
								        res.end('{"error":"Service Unavailable","message":"Service Unavailable","statusCode":503}')
							 | 
						||
| 
								 | 
							
								        return
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // When server.forceCloseConnections is true, we will collect any requests
							 | 
						||
| 
								 | 
							
								    // that have indicated they want persistence so that they can be reaped
							 | 
						||
| 
								 | 
							
								    // on server close. Otherwise, the container is a noop container.
							 | 
						||
| 
								 | 
							
								    const connHeader = String.prototype.toLowerCase.call(req.headers.connection || '')
							 | 
						||
| 
								 | 
							
								    if (connHeader === 'keep-alive') {
							 | 
						||
| 
								 | 
							
								      if (keepAliveConnections.has(req.socket) === false) {
							 | 
						||
| 
								 | 
							
								        keepAliveConnections.add(req.socket)
							 | 
						||
| 
								 | 
							
								        req.socket.on('close', removeTrackedSocket.bind({ keepAliveConnections, socket: req.socket }))
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // we revert the changes in defaultRoute
							 | 
						||
| 
								 | 
							
								    if (req.headers[kRequestAcceptVersion] !== undefined) {
							 | 
						||
| 
								 | 
							
								      req.headers['accept-version'] = req.headers[kRequestAcceptVersion]
							 | 
						||
| 
								 | 
							
								      req.headers[kRequestAcceptVersion] = undefined
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const id = req.headers[requestIdHeader] || genReqId(req)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const loggerBinding = {
							 | 
						||
| 
								 | 
							
								      [requestIdLogLabel]: id
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const loggerOpts = {
							 | 
						||
| 
								 | 
							
								      level: context.logLevel
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (context.logSerializers) {
							 | 
						||
| 
								 | 
							
								      loggerOpts.serializers = context.logSerializers
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const childLogger = logger.child(loggerBinding, loggerOpts)
							 | 
						||
| 
								 | 
							
								    childLogger[kDisableRequestLogging] = disableRequestLogging
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const queryPrefix = req.url.indexOf('?')
							 | 
						||
| 
								 | 
							
								    const query = querystringParser(queryPrefix > -1 ? req.url.slice(queryPrefix + 1) : '')
							 | 
						||
| 
								 | 
							
								    const request = new context.Request(id, params, req, query, childLogger, context)
							 | 
						||
| 
								 | 
							
								    const reply = new context.Reply(res, request, childLogger)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (disableRequestLogging === false) {
							 | 
						||
| 
								 | 
							
								      childLogger.info({ req: request }, 'incoming request')
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (hasLogger === true || context.onResponse !== null) {
							 | 
						||
| 
								 | 
							
								      setupResponseListeners(reply)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (context.onRequest !== null) {
							 | 
						||
| 
								 | 
							
								      hookRunner(
							 | 
						||
| 
								 | 
							
								        context.onRequest,
							 | 
						||
| 
								 | 
							
								        hookIterator,
							 | 
						||
| 
								 | 
							
								        request,
							 | 
						||
| 
								 | 
							
								        reply,
							 | 
						||
| 
								 | 
							
								        runPreParsing
							 | 
						||
| 
								 | 
							
								      )
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      runPreParsing(null, request, reply)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (context.onTimeout !== null) {
							 | 
						||
| 
								 | 
							
								      if (!request.raw.socket._meta) {
							 | 
						||
| 
								 | 
							
								        request.raw.socket.on('timeout', handleTimeout)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      request.raw.socket._meta = { context, request, reply }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function handleTimeout () {
							 | 
						||
| 
								 | 
							
								  const { context, request, reply } = this._meta
							 | 
						||
| 
								 | 
							
								  hookRunner(
							 | 
						||
| 
								 | 
							
								    context.onTimeout,
							 | 
						||
| 
								 | 
							
								    hookIterator,
							 | 
						||
| 
								 | 
							
								    request,
							 | 
						||
| 
								 | 
							
								    reply,
							 | 
						||
| 
								 | 
							
								    noop
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function validateBodyLimitOption (bodyLimit) {
							 | 
						||
| 
								 | 
							
								  if (bodyLimit === undefined) return
							 | 
						||
| 
								 | 
							
								  if (!Number.isInteger(bodyLimit) || bodyLimit <= 0) {
							 | 
						||
| 
								 | 
							
								    throw new TypeError(`'bodyLimit' option must be an integer > 0. Got '${bodyLimit}'`)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function runPreParsing (err, request, reply) {
							 | 
						||
| 
								 | 
							
								  if (reply.sent === true) return
							 | 
						||
| 
								 | 
							
								  if (err != null) {
							 | 
						||
| 
								 | 
							
								    reply.send(err)
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  request[kRequestPayloadStream] = request.raw
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (reply.context.preParsing !== null) {
							 | 
						||
| 
								 | 
							
								    preParsingHookRunner(reply.context.preParsing, request, reply, handleRequest)
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    handleRequest(null, request, reply)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function preParsingHookRunner (functions, request, reply, cb) {
							 | 
						||
| 
								 | 
							
								  let i = 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function next (err, stream) {
							 | 
						||
| 
								 | 
							
								    if (reply.sent) {
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (typeof stream !== 'undefined') {
							 | 
						||
| 
								 | 
							
								      request[kRequestPayloadStream] = stream
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (err || i === functions.length) {
							 | 
						||
| 
								 | 
							
								      if (err && !(err instanceof Error)) {
							 | 
						||
| 
								 | 
							
								        reply[kReplyIsError] = true
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      cb(err, request, reply)
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const fn = functions[i++]
							 | 
						||
| 
								 | 
							
								    let result
							 | 
						||
| 
								 | 
							
								    try {
							 | 
						||
| 
								 | 
							
								      if (fn[kHooksDeprecatedPreParsing]) {
							 | 
						||
| 
								 | 
							
								        result = fn(request, reply, next)
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        result = fn(request, reply, request[kRequestPayloadStream], next)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } catch (error) {
							 | 
						||
| 
								 | 
							
								      next(error)
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (result && typeof result.then === 'function') {
							 | 
						||
| 
								 | 
							
								      result.then(handleResolve, handleReject)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function handleResolve (stream) {
							 | 
						||
| 
								 | 
							
								    next(null, stream)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function handleReject (err) {
							 | 
						||
| 
								 | 
							
								    next(err)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  next(null, request[kRequestPayloadStream])
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Used within the route handler as a `net.Socket.close` event handler.
							 | 
						||
| 
								 | 
							
								 * The purpose is to remove a socket from the tracked sockets collection when
							 | 
						||
| 
								 | 
							
								 * the socket has naturally timed out.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function removeTrackedSocket () {
							 | 
						||
| 
								 | 
							
								  this.keepAliveConnections.delete(this.socket)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function noop () { }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = { buildRouting, validateBodyLimitOption }
							 |