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.
		
		
		
		
		
			
		
			
				
					
					
						
							724 lines
						
					
					
						
							24 KiB
						
					
					
				
			
		
		
	
	
							724 lines
						
					
					
						
							24 KiB
						
					
					
				| 'use strict'
 | |
| 
 | |
| const VERSION = '3.29.5'
 | |
| 
 | |
| const Avvio = require('avvio')
 | |
| const http = require('http')
 | |
| const querystring = require('querystring')
 | |
| let lightMyRequest
 | |
| 
 | |
| const {
 | |
|   kAvvioBoot,
 | |
|   kChildren,
 | |
|   kBodyLimit,
 | |
|   kRoutePrefix,
 | |
|   kLogLevel,
 | |
|   kLogSerializers,
 | |
|   kHooks,
 | |
|   kSchemaController,
 | |
|   kRequestAcceptVersion,
 | |
|   kReplySerializerDefault,
 | |
|   kContentTypeParser,
 | |
|   kReply,
 | |
|   kRequest,
 | |
|   kFourOhFour,
 | |
|   kState,
 | |
|   kOptions,
 | |
|   kPluginNameChain,
 | |
|   kSchemaErrorFormatter,
 | |
|   kErrorHandler,
 | |
|   kKeepAliveConnections,
 | |
|   kFourOhFourContext
 | |
| } = require('./lib/symbols.js')
 | |
| 
 | |
| const { createServer } = require('./lib/server')
 | |
| const Reply = require('./lib/reply')
 | |
| const Request = require('./lib/request')
 | |
| const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS']
 | |
| const decorator = require('./lib/decorate')
 | |
| const ContentTypeParser = require('./lib/contentTypeParser')
 | |
| const SchemaController = require('./lib/schema-controller')
 | |
| const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks')
 | |
| const { createLogger } = require('./lib/logger')
 | |
| const pluginUtils = require('./lib/pluginUtils')
 | |
| const reqIdGenFactory = require('./lib/reqIdGenFactory')
 | |
| const { buildRouting, validateBodyLimitOption } = require('./lib/route')
 | |
| const build404 = require('./lib/fourOhFour')
 | |
| const getSecuredInitialConfig = require('./lib/initialConfigValidation')
 | |
| const override = require('./lib/pluginOverride')
 | |
| const warning = require('./lib/warnings')
 | |
| const noopSet = require('./lib/noop-set')
 | |
| const { defaultInitOptions } = getSecuredInitialConfig
 | |
| 
 | |
| const {
 | |
|   FST_ERR_BAD_URL,
 | |
|   FST_ERR_MISSING_MIDDLEWARE
 | |
| } = require('./lib/errors')
 | |
| 
 | |
| const onBadUrlContext = {
 | |
|   config: {
 | |
|   },
 | |
|   onSend: [],
 | |
|   onError: [],
 | |
|   [kFourOhFourContext]: null
 | |
| }
 | |
| 
 | |
| function defaultBuildPrettyMeta (route) {
 | |
|   // return a shallow copy of route's sanitized context
 | |
| 
 | |
|   const cleanKeys = {}
 | |
|   const allowedProps = ['errorHandler', 'logLevel', 'logSerializers']
 | |
| 
 | |
|   allowedProps.concat(supportedHooks).forEach(k => {
 | |
|     cleanKeys[k] = route.store[k]
 | |
|   })
 | |
| 
 | |
|   return Object.assign({}, cleanKeys)
 | |
| }
 | |
| 
 | |
| function defaultErrorHandler (error, request, reply) {
 | |
|   if (reply.statusCode < 500) {
 | |
|     reply.log.info(
 | |
|       { res: reply, err: error },
 | |
|       error && error.message
 | |
|     )
 | |
|   } else {
 | |
|     reply.log.error(
 | |
|       { req: request, res: reply, err: error },
 | |
|       error && error.message
 | |
|     )
 | |
|   }
 | |
|   reply.send(error)
 | |
| }
 | |
| 
 | |
| function fastify (options) {
 | |
|   // Options validations
 | |
|   options = options || {}
 | |
| 
 | |
|   if (typeof options !== 'object') {
 | |
|     throw new TypeError('Options must be an object')
 | |
|   }
 | |
| 
 | |
|   if (options.querystringParser && typeof options.querystringParser !== 'function') {
 | |
|     throw new Error(`querystringParser option should be a function, instead got '${typeof options.querystringParser}'`)
 | |
|   }
 | |
| 
 | |
|   if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') {
 | |
|     throw new Error(`schemaController.bucket option should be a function, instead got '${typeof options.schemaController.bucket}'`)
 | |
|   }
 | |
| 
 | |
|   validateBodyLimitOption(options.bodyLimit)
 | |
| 
 | |
|   const requestIdHeader = options.requestIdHeader || defaultInitOptions.requestIdHeader
 | |
|   const querystringParser = options.querystringParser || querystring.parse
 | |
|   const genReqId = options.genReqId || reqIdGenFactory()
 | |
|   const requestIdLogLabel = options.requestIdLogLabel || 'reqId'
 | |
|   const bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit
 | |
|   const disableRequestLogging = options.disableRequestLogging || false
 | |
|   const exposeHeadRoutes = options.exposeHeadRoutes != null ? options.exposeHeadRoutes : false
 | |
| 
 | |
|   const ajvOptions = Object.assign({
 | |
|     customOptions: {},
 | |
|     plugins: []
 | |
|   }, options.ajv)
 | |
|   const frameworkErrors = options.frameworkErrors
 | |
| 
 | |
|   // Ajv options
 | |
|   if (!ajvOptions.customOptions || Object.prototype.toString.call(ajvOptions.customOptions) !== '[object Object]') {
 | |
|     throw new Error(`ajv.customOptions option should be an object, instead got '${typeof ajvOptions.customOptions}'`)
 | |
|   }
 | |
|   if (!ajvOptions.plugins || !Array.isArray(ajvOptions.plugins)) {
 | |
|     throw new Error(`ajv.plugins option should be an array, instead got '${typeof ajvOptions.plugins}'`)
 | |
|   }
 | |
| 
 | |
|   // Instance Fastify components
 | |
|   const { logger, hasLogger } = createLogger(options)
 | |
| 
 | |
|   // Update the options with the fixed values
 | |
|   options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout
 | |
|   options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout
 | |
|   options.forceCloseConnections = typeof options.forceCloseConnections === 'boolean' ? options.forceCloseConnections : defaultInitOptions.forceCloseConnections
 | |
|   options.maxRequestsPerSocket = options.maxRequestsPerSocket || defaultInitOptions.maxRequestsPerSocket
 | |
|   options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout
 | |
|   options.logger = logger
 | |
|   options.genReqId = genReqId
 | |
|   options.requestIdHeader = requestIdHeader
 | |
|   options.querystringParser = querystringParser
 | |
|   options.requestIdLogLabel = requestIdLogLabel
 | |
|   options.disableRequestLogging = disableRequestLogging
 | |
|   options.ajv = ajvOptions
 | |
|   options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler
 | |
|   options.exposeHeadRoutes = exposeHeadRoutes
 | |
| 
 | |
|   const initialConfig = getSecuredInitialConfig(options)
 | |
|   const keepAliveConnections = options.forceCloseConnections === true ? new Set() : noopSet()
 | |
| 
 | |
|   let constraints = options.constraints
 | |
|   if (options.versioning) {
 | |
|     warning.emit('FSTDEP009')
 | |
|     constraints = {
 | |
|       ...constraints,
 | |
|       version: {
 | |
|         name: 'version',
 | |
|         mustMatchWhenDerived: true,
 | |
|         storage: options.versioning.storage,
 | |
|         deriveConstraint: options.versioning.deriveVersion,
 | |
|         validate (value) {
 | |
|           if (typeof value !== 'string') {
 | |
|             throw new Error('Version constraint should be a string.')
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Default router
 | |
|   const router = buildRouting({
 | |
|     config: {
 | |
|       defaultRoute,
 | |
|       onBadUrl,
 | |
|       constraints,
 | |
|       ignoreTrailingSlash: options.ignoreTrailingSlash || defaultInitOptions.ignoreTrailingSlash,
 | |
|       maxParamLength: options.maxParamLength || defaultInitOptions.maxParamLength,
 | |
|       caseSensitive: options.caseSensitive,
 | |
|       buildPrettyMeta: defaultBuildPrettyMeta
 | |
|     },
 | |
|     keepAliveConnections
 | |
|   })
 | |
| 
 | |
|   // 404 router, used for handling encapsulated 404 handlers
 | |
|   const fourOhFour = build404(options)
 | |
| 
 | |
|   // HTTP server and its handler
 | |
|   const httpHandler = wrapRouting(router.routing, options)
 | |
| 
 | |
|   // we need to set this before calling createServer
 | |
|   options.http2SessionTimeout = initialConfig.http2SessionTimeout
 | |
|   const { server, listen } = createServer(options, httpHandler)
 | |
| 
 | |
|   const setupResponseListeners = Reply.setupResponseListeners
 | |
|   const schemaController = SchemaController.buildSchemaController(null, options.schemaController)
 | |
| 
 | |
|   // Public API
 | |
|   const fastify = {
 | |
|     // Fastify internals
 | |
|     [kState]: {
 | |
|       listening: false,
 | |
|       closing: false,
 | |
|       started: false
 | |
|     },
 | |
|     [kKeepAliveConnections]: keepAliveConnections,
 | |
|     [kOptions]: options,
 | |
|     [kChildren]: [],
 | |
|     [kBodyLimit]: bodyLimit,
 | |
|     [kRoutePrefix]: '',
 | |
|     [kLogLevel]: '',
 | |
|     [kLogSerializers]: null,
 | |
|     [kHooks]: new Hooks(),
 | |
|     [kSchemaController]: schemaController,
 | |
|     [kSchemaErrorFormatter]: null,
 | |
|     [kErrorHandler]: defaultErrorHandler,
 | |
|     [kReplySerializerDefault]: null,
 | |
|     [kContentTypeParser]: new ContentTypeParser(
 | |
|       bodyLimit,
 | |
|       (options.onProtoPoisoning || defaultInitOptions.onProtoPoisoning),
 | |
|       (options.onConstructorPoisoning || defaultInitOptions.onConstructorPoisoning)
 | |
|     ),
 | |
|     [kReply]: Reply.buildReply(Reply),
 | |
|     [kRequest]: Request.buildRequest(Request, options.trustProxy),
 | |
|     [kFourOhFour]: fourOhFour,
 | |
|     [pluginUtils.registeredPlugins]: [],
 | |
|     [kPluginNameChain]: [],
 | |
|     [kAvvioBoot]: null,
 | |
|     // routing method
 | |
|     routing: httpHandler,
 | |
|     getDefaultRoute: router.getDefaultRoute.bind(router),
 | |
|     setDefaultRoute: router.setDefaultRoute.bind(router),
 | |
|     // routes shorthand methods
 | |
|     delete: function _delete (url, opts, handler) {
 | |
|       return router.prepareRoute.call(this, 'DELETE', url, opts, handler)
 | |
|     },
 | |
|     get: function _get (url, opts, handler) {
 | |
|       return router.prepareRoute.call(this, 'GET', url, opts, handler)
 | |
|     },
 | |
|     head: function _head (url, opts, handler) {
 | |
|       return router.prepareRoute.call(this, 'HEAD', url, opts, handler)
 | |
|     },
 | |
|     patch: function _patch (url, opts, handler) {
 | |
|       return router.prepareRoute.call(this, 'PATCH', url, opts, handler)
 | |
|     },
 | |
|     post: function _post (url, opts, handler) {
 | |
|       return router.prepareRoute.call(this, 'POST', url, opts, handler)
 | |
|     },
 | |
|     put: function _put (url, opts, handler) {
 | |
|       return router.prepareRoute.call(this, 'PUT', url, opts, handler)
 | |
|     },
 | |
|     options: function _options (url, opts, handler) {
 | |
|       return router.prepareRoute.call(this, 'OPTIONS', url, opts, handler)
 | |
|     },
 | |
|     all: function _all (url, opts, handler) {
 | |
|       return router.prepareRoute.call(this, supportedMethods, url, opts, handler)
 | |
|     },
 | |
|     // extended route
 | |
|     route: function _route (opts) {
 | |
|       // we need the fastify object that we are producing so we apply a lazy loading of the function,
 | |
|       // otherwise we should bind it after the declaration
 | |
|       return router.route.call(this, opts)
 | |
|     },
 | |
|     // expose logger instance
 | |
|     log: logger,
 | |
|     // hooks
 | |
|     addHook,
 | |
|     // schemas
 | |
|     addSchema,
 | |
|     getSchema: schemaController.getSchema.bind(schemaController),
 | |
|     getSchemas: schemaController.getSchemas.bind(schemaController),
 | |
|     setValidatorCompiler,
 | |
|     setSerializerCompiler,
 | |
|     setSchemaController,
 | |
|     setReplySerializer,
 | |
|     setSchemaErrorFormatter,
 | |
|     // custom parsers
 | |
|     addContentTypeParser: ContentTypeParser.helpers.addContentTypeParser,
 | |
|     hasContentTypeParser: ContentTypeParser.helpers.hasContentTypeParser,
 | |
|     getDefaultJsonParser: ContentTypeParser.defaultParsers.getDefaultJsonParser,
 | |
|     defaultTextParser: ContentTypeParser.defaultParsers.defaultTextParser,
 | |
|     removeContentTypeParser: ContentTypeParser.helpers.removeContentTypeParser,
 | |
|     removeAllContentTypeParsers: ContentTypeParser.helpers.removeAllContentTypeParsers,
 | |
|     // Fastify architecture methods (initialized by Avvio)
 | |
|     register: null,
 | |
|     after: null,
 | |
|     ready: null,
 | |
|     onClose: null,
 | |
|     close: null,
 | |
|     printPlugins: null,
 | |
|     // http server
 | |
|     listen,
 | |
|     server,
 | |
|     // extend fastify objects
 | |
|     decorate: decorator.add,
 | |
|     hasDecorator: decorator.exist,
 | |
|     decorateReply: decorator.decorateReply,
 | |
|     decorateRequest: decorator.decorateRequest,
 | |
|     hasRequestDecorator: decorator.existRequest,
 | |
|     hasReplyDecorator: decorator.existReply,
 | |
|     // fake http injection
 | |
|     inject,
 | |
|     // pretty print of the registered routes
 | |
|     printRoutes,
 | |
|     // custom error handling
 | |
|     setNotFoundHandler,
 | |
|     setErrorHandler,
 | |
|     // Set fastify initial configuration options read-only object
 | |
|     initialConfig
 | |
|   }
 | |
| 
 | |
|   fastify[kReply].prototype.server = fastify
 | |
|   fastify[kRequest].prototype.server = fastify
 | |
| 
 | |
|   Object.defineProperties(fastify, {
 | |
|     pluginName: {
 | |
|       get () {
 | |
|         if (this[kPluginNameChain].length > 1) {
 | |
|           return this[kPluginNameChain].join(' -> ')
 | |
|         }
 | |
|         return this[kPluginNameChain][0]
 | |
|       }
 | |
|     },
 | |
|     prefix: {
 | |
|       get () { return this[kRoutePrefix] }
 | |
|     },
 | |
|     validatorCompiler: {
 | |
|       get () { return this[kSchemaController].getValidatorCompiler() }
 | |
|     },
 | |
|     serializerCompiler: {
 | |
|       get () { return this[kSchemaController].getSerializerCompiler() }
 | |
|     },
 | |
|     version: {
 | |
|       get () { return VERSION }
 | |
|     },
 | |
|     errorHandler: {
 | |
|       get () {
 | |
|         return this[kErrorHandler]
 | |
|       }
 | |
|     }
 | |
|   })
 | |
| 
 | |
|   // We are adding `use` to the fastify prototype so the user
 | |
|   // can still access it (and get the expected error), but `decorate`
 | |
|   // will not detect it, and allow the user to override it.
 | |
|   Object.setPrototypeOf(fastify, { use })
 | |
| 
 | |
|   if (options.schemaErrorFormatter) {
 | |
|     validateSchemaErrorFormatter(options.schemaErrorFormatter)
 | |
|     fastify[kSchemaErrorFormatter] = options.schemaErrorFormatter.bind(fastify)
 | |
|   }
 | |
| 
 | |
|   // Install and configure Avvio
 | |
|   // Avvio will update the following Fastify methods:
 | |
|   // - register
 | |
|   // - after
 | |
|   // - ready
 | |
|   // - onClose
 | |
|   // - close
 | |
|   const avvio = Avvio(fastify, {
 | |
|     autostart: false,
 | |
|     timeout: Number(options.pluginTimeout) || defaultInitOptions.pluginTimeout,
 | |
|     expose: {
 | |
|       use: 'register'
 | |
|     }
 | |
|   })
 | |
|   // Override to allow the plugin encapsulation
 | |
|   avvio.override = override
 | |
|   avvio.on('start', () => (fastify[kState].started = true))
 | |
|   fastify[kAvvioBoot] = fastify.ready // the avvio ready function
 | |
|   fastify.ready = ready // overwrite the avvio ready function
 | |
|   fastify.printPlugins = avvio.prettyPrint.bind(avvio)
 | |
| 
 | |
|   // cache the closing value, since we are checking it in an hot path
 | |
|   avvio.once('preReady', () => {
 | |
|     fastify.onClose((instance, done) => {
 | |
|       fastify[kState].closing = true
 | |
|       router.closeRoutes()
 | |
|       if (fastify[kState].listening) {
 | |
|         // No new TCP connections are accepted
 | |
|         instance.server.close(done)
 | |
| 
 | |
|         for (const conn of fastify[kKeepAliveConnections]) {
 | |
|           // We must invoke the destroy method instead of merely unreffing
 | |
|           // the sockets. If we only unref, then the callback passed to
 | |
|           // `fastify.close` will never be invoked; nor will any of the
 | |
|           // registered `onClose` hooks.
 | |
|           conn.destroy()
 | |
|           fastify[kKeepAliveConnections].delete(conn)
 | |
|         }
 | |
|       } else {
 | |
|         done(null)
 | |
|       }
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   // Set the default 404 handler
 | |
|   fastify.setNotFoundHandler()
 | |
|   fourOhFour.arrange404(fastify)
 | |
| 
 | |
|   router.setup(options, {
 | |
|     avvio,
 | |
|     fourOhFour,
 | |
|     logger,
 | |
|     hasLogger,
 | |
|     setupResponseListeners,
 | |
|     throwIfAlreadyStarted
 | |
|   })
 | |
| 
 | |
|   // Delay configuring clientError handler so that it can access fastify state.
 | |
|   server.on('clientError', options.clientErrorHandler.bind(fastify))
 | |
| 
 | |
|   try {
 | |
|     const dc = require('diagnostics_channel')
 | |
|     const initChannel = dc.channel('fastify.initialization')
 | |
|     if (initChannel.hasSubscribers) {
 | |
|       initChannel.publish({ fastify })
 | |
|     }
 | |
|   } catch (e) {
 | |
|     // This only happens if `diagnostics_channel` isn't available, i.e. earlier
 | |
|     // versions of Node.js. In that event, we don't care, so ignore the error.
 | |
|   }
 | |
| 
 | |
|   return fastify
 | |
| 
 | |
|   function throwIfAlreadyStarted (msg) {
 | |
|     if (fastify[kState].started) throw new Error(msg)
 | |
|   }
 | |
| 
 | |
|   // HTTP injection handling
 | |
|   // If the server is not ready yet, this
 | |
|   // utility will automatically force it.
 | |
|   function inject (opts, cb) {
 | |
|     // lightMyRequest is dynamically loaded as it seems very expensive
 | |
|     // because of Ajv
 | |
|     if (lightMyRequest === undefined) {
 | |
|       lightMyRequest = require('light-my-request')
 | |
|     }
 | |
| 
 | |
|     if (fastify[kState].started) {
 | |
|       if (fastify[kState].closing) {
 | |
|         // Force to return an error
 | |
|         const error = new Error('Server is closed')
 | |
|         if (cb) {
 | |
|           cb(error)
 | |
|           return
 | |
|         } else {
 | |
|           return Promise.reject(error)
 | |
|         }
 | |
|       }
 | |
|       return lightMyRequest(httpHandler, opts, cb)
 | |
|     }
 | |
| 
 | |
|     if (cb) {
 | |
|       this.ready(err => {
 | |
|         if (err) cb(err, null)
 | |
|         else lightMyRequest(httpHandler, opts, cb)
 | |
|       })
 | |
|     } else {
 | |
|       return lightMyRequest((req, res) => {
 | |
|         this.ready(function (err) {
 | |
|           if (err) {
 | |
|             res.emit('error', err)
 | |
|             return
 | |
|           }
 | |
|           httpHandler(req, res)
 | |
|         })
 | |
|       }, opts)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function ready (cb) {
 | |
|     let resolveReady
 | |
|     let rejectReady
 | |
| 
 | |
|     // run the hooks after returning the promise
 | |
|     process.nextTick(runHooks)
 | |
| 
 | |
|     if (!cb) {
 | |
|       return new Promise(function (resolve, reject) {
 | |
|         resolveReady = resolve
 | |
|         rejectReady = reject
 | |
|       })
 | |
|     }
 | |
| 
 | |
|     function runHooks () {
 | |
|       // start loading
 | |
|       fastify[kAvvioBoot]((err, done) => {
 | |
|         if (err || fastify[kState].started) {
 | |
|           manageErr(err)
 | |
|         } else {
 | |
|           hookRunnerApplication('onReady', fastify[kAvvioBoot], fastify, manageErr)
 | |
|         }
 | |
|         done()
 | |
|       })
 | |
|     }
 | |
| 
 | |
|     function manageErr (err) {
 | |
|       if (cb) {
 | |
|         if (err) {
 | |
|           cb(err)
 | |
|         } else {
 | |
|           cb(undefined, fastify)
 | |
|         }
 | |
|       } else {
 | |
|         if (err) {
 | |
|           return rejectReady(err)
 | |
|         }
 | |
|         resolveReady(fastify)
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function use () {
 | |
|     throw new FST_ERR_MISSING_MIDDLEWARE()
 | |
|   }
 | |
| 
 | |
|   // wrapper that we expose to the user for hooks handling
 | |
|   function addHook (name, fn) {
 | |
|     throwIfAlreadyStarted('Cannot call "addHook" when fastify instance is already started!')
 | |
| 
 | |
|     if (name === 'onSend' || name === 'preSerialization' || name === 'onError') {
 | |
|       if (fn.constructor.name === 'AsyncFunction' && fn.length === 4) {
 | |
|         throw new Error('Async function has too many arguments. Async hooks should not use the \'done\' argument.')
 | |
|       }
 | |
|     } else if (name === 'onReady') {
 | |
|       if (fn.constructor.name === 'AsyncFunction' && fn.length !== 0) {
 | |
|         throw new Error('Async function has too many arguments. Async hooks should not use the \'done\' argument.')
 | |
|       }
 | |
|     } else if (name !== 'preParsing') {
 | |
|       if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) {
 | |
|         throw new Error('Async function has too many arguments. Async hooks should not use the \'done\' argument.')
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (name === 'onClose') {
 | |
|       this.onClose(fn)
 | |
|     } else if (name === 'onReady') {
 | |
|       this[kHooks].add(name, fn)
 | |
|     } else {
 | |
|       this.after((err, done) => {
 | |
|         _addHook.call(this, name, fn)
 | |
|         done(err)
 | |
|       })
 | |
|     }
 | |
|     return this
 | |
| 
 | |
|     function _addHook (name, fn) {
 | |
|       this[kHooks].add(name, fn)
 | |
|       this[kChildren].forEach(child => _addHook.call(child, name, fn))
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // wrapper that we expose to the user for schemas handling
 | |
|   function addSchema (schema) {
 | |
|     throwIfAlreadyStarted('Cannot call "addSchema" when fastify instance is already started!')
 | |
|     this[kSchemaController].add(schema)
 | |
|     this[kChildren].forEach(child => child.addSchema(schema))
 | |
|     return this
 | |
|   }
 | |
| 
 | |
|   function defaultClientErrorHandler (err, socket) {
 | |
|     // In case of a connection reset, the socket has been destroyed and there is nothing that needs to be done.
 | |
|     // https://nodejs.org/api/http.html#http_event_clienterror
 | |
|     if (err.code === 'ECONNRESET' || socket.destroyed) {
 | |
|       return
 | |
|     }
 | |
| 
 | |
|     const body = JSON.stringify({
 | |
|       error: http.STATUS_CODES['400'],
 | |
|       message: 'Client Error',
 | |
|       statusCode: 400
 | |
|     })
 | |
| 
 | |
|     // Most devs do not know what to do with this error.
 | |
|     // In the vast majority of cases, it's a network error and/or some
 | |
|     // config issue on the load balancer side.
 | |
|     this.log.trace({ err }, 'client error')
 | |
|     // Copying standard node behaviour
 | |
|     // https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666
 | |
| 
 | |
|     // If the socket is not writable, there is no reason to try to send data.
 | |
|     if (socket.writable) {
 | |
|       socket.write(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
 | |
|     }
 | |
|     socket.destroy(err)
 | |
|   }
 | |
| 
 | |
|   // If the router does not match any route, every request will land here
 | |
|   // req and res are Node.js core objects
 | |
|   function defaultRoute (req, res) {
 | |
|     if (req.headers['accept-version'] !== undefined) {
 | |
|       // we remove the accept-version header for performance result
 | |
|       // because we do not want to go through the constraint checking
 | |
|       // the usage of symbol here to prevent any colision on custom header name
 | |
|       req.headers[kRequestAcceptVersion] = req.headers['accept-version']
 | |
|       req.headers['accept-version'] = undefined
 | |
|     }
 | |
|     fourOhFour.router.lookup(req, res)
 | |
|   }
 | |
| 
 | |
|   function onBadUrl (path, req, res) {
 | |
|     if (frameworkErrors) {
 | |
|       const id = genReqId(req)
 | |
|       const childLogger = logger.child({ reqId: id })
 | |
| 
 | |
|       childLogger.info({ req }, 'incoming request')
 | |
| 
 | |
|       const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
 | |
|       const reply = new Reply(res, request, childLogger)
 | |
|       return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
 | |
|     }
 | |
|     const body = `{"error":"Bad Request","message":"'${path}' is not a valid url component","statusCode":400}`
 | |
|     res.writeHead(400, {
 | |
|       'Content-Type': 'application/json',
 | |
|       'Content-Length': body.length
 | |
|     })
 | |
|     res.end(body)
 | |
|   }
 | |
| 
 | |
|   function setNotFoundHandler (opts, handler) {
 | |
|     throwIfAlreadyStarted('Cannot call "setNotFoundHandler" when fastify instance is already started!')
 | |
| 
 | |
|     fourOhFour.setNotFoundHandler.call(this, opts, handler, avvio, router.routeHandler)
 | |
|     return this
 | |
|   }
 | |
| 
 | |
|   function setValidatorCompiler (validatorCompiler) {
 | |
|     throwIfAlreadyStarted('Cannot call "setValidatorCompiler" when fastify instance is already started!')
 | |
|     this[kSchemaController].setValidatorCompiler(validatorCompiler)
 | |
|     return this
 | |
|   }
 | |
| 
 | |
|   function setSchemaErrorFormatter (errorFormatter) {
 | |
|     throwIfAlreadyStarted('Cannot call "setSchemaErrorFormatter" when fastify instance is already started!')
 | |
|     validateSchemaErrorFormatter(errorFormatter)
 | |
|     this[kSchemaErrorFormatter] = errorFormatter.bind(this)
 | |
|     return this
 | |
|   }
 | |
| 
 | |
|   function setSerializerCompiler (serializerCompiler) {
 | |
|     throwIfAlreadyStarted('Cannot call "setSerializerCompiler" when fastify instance is already started!')
 | |
|     this[kSchemaController].setSerializerCompiler(serializerCompiler)
 | |
|     return this
 | |
|   }
 | |
| 
 | |
|   function setSchemaController (schemaControllerOpts) {
 | |
|     throwIfAlreadyStarted('Cannot call "setSchemaController" when fastify instance is already started!')
 | |
|     const old = this[kSchemaController]
 | |
|     const schemaController = SchemaController.buildSchemaController(old, Object.assign({}, old.opts, schemaControllerOpts))
 | |
|     this[kSchemaController] = schemaController
 | |
|     this.getSchema = schemaController.getSchema.bind(schemaController)
 | |
|     this.getSchemas = schemaController.getSchemas.bind(schemaController)
 | |
|     return this
 | |
|   }
 | |
| 
 | |
|   function setReplySerializer (replySerializer) {
 | |
|     throwIfAlreadyStarted('Cannot call "setReplySerializer" when fastify instance is already started!')
 | |
| 
 | |
|     this[kReplySerializerDefault] = replySerializer
 | |
|     return this
 | |
|   }
 | |
| 
 | |
|   // wrapper that we expose to the user for configure the custom error handler
 | |
|   function setErrorHandler (func) {
 | |
|     throwIfAlreadyStarted('Cannot call "setErrorHandler" when fastify instance is already started!')
 | |
| 
 | |
|     this[kErrorHandler] = func.bind(this)
 | |
|     return this
 | |
|   }
 | |
| 
 | |
|   function printRoutes (opts = {}) {
 | |
|     // includeHooks:true - shortcut to include all supported hooks exported by fastify.Hooks
 | |
|     opts.includeMeta = opts.includeHooks ? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks : opts.includeMeta
 | |
|     return router.printRoutes(opts)
 | |
|   }
 | |
| }
 | |
| 
 | |
| function validateSchemaErrorFormatter (schemaErrorFormatter) {
 | |
|   if (typeof schemaErrorFormatter !== 'function') {
 | |
|     throw new Error(`schemaErrorFormatter option should be a function, instead got ${typeof schemaErrorFormatter}`)
 | |
|   } else if (schemaErrorFormatter.constructor.name === 'AsyncFunction') {
 | |
|     throw new Error('schemaErrorFormatter option should not be an async function')
 | |
|   }
 | |
| }
 | |
| 
 | |
| function wrapRouting (httpHandler, { rewriteUrl, logger }) {
 | |
|   if (!rewriteUrl) {
 | |
|     return httpHandler
 | |
|   }
 | |
|   return function preRouting (req, res) {
 | |
|     const originalUrl = req.url
 | |
|     const url = rewriteUrl(req)
 | |
|     if (originalUrl !== url) {
 | |
|       logger.debug({ originalUrl, url }, 'rewrite url')
 | |
|       if (typeof url === 'string') {
 | |
|         req.url = url
 | |
|       } else {
 | |
|         req.destroy(new Error(`Rewrite url for "${req.url}" needs to be of type "string" but received "${typeof url}"`))
 | |
|       }
 | |
|     }
 | |
|     httpHandler(req, res)
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * These export configurations enable JS and TS developers
 | |
|  * to consumer fastify in whatever way best suits their needs.
 | |
|  * Some examples of supported import syntax includes:
 | |
|  * - `const fastify = require('fastify')`
 | |
|  * - `const { fastify } = require('fastify')`
 | |
|  * - `import * as Fastify from 'fastify'`
 | |
|  * - `import { fastify, TSC_definition } from 'fastify'`
 | |
|  * - `import fastify from 'fastify'`
 | |
|  * - `import fastify, { TSC_definition } from 'fastify'`
 | |
|  */
 | |
| module.exports = fastify
 | |
| module.exports.fastify = fastify
 | |
| module.exports.default = fastify
 |