Aktueller Stand

This commit is contained in:
2026-01-22 19:05:45 +01:00
parent 85dee61a4d
commit e280e4eadb
1967 changed files with 397327 additions and 74093 deletions

View File

@@ -1,9 +1,10 @@
'use strict'
const VERSION = '4.29.1'
const VERSION = '5.7.1'
const Avvio = require('avvio')
const http = require('node:http')
const diagnostics = require('node:diagnostics_channel')
let lightMyRequest
const {
@@ -11,6 +12,7 @@ const {
kChildren,
kServerBindings,
kBodyLimit,
kSupportedHTTPMethods,
kRoutePrefix,
kLogLevel,
kLogSerializers,
@@ -29,163 +31,70 @@ const {
kErrorHandler,
kKeepAliveConnections,
kChildLoggerFactory,
kGenReqId
kGenReqId,
kErrorHandlerAlreadySet
} = require('./lib/symbols.js')
const { createServer, compileValidateHTTPVersion } = require('./lib/server')
const { createServer } = require('./lib/server')
const Reply = require('./lib/reply')
const Request = require('./lib/request')
const Context = require('./lib/context.js')
const { supportedMethods } = require('./lib/httpMethods')
const decorator = require('./lib/decorate')
const ContentTypeParser = require('./lib/contentTypeParser')
const ContentTypeParser = require('./lib/content-type-parser.js')
const SchemaController = require('./lib/schema-controller')
const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks')
const { createLogger, createChildLogger, defaultChildLoggerFactory } = require('./lib/logger')
const pluginUtils = require('./lib/pluginUtils')
const { getGenReqId, 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 { FSTDEP009 } = require('./lib/warnings')
const noopSet = require('./lib/noop-set')
const { createChildLogger, defaultChildLoggerFactory, createLogger } = require('./lib/logger-factory')
const pluginUtils = require('./lib/plugin-utils.js')
const { getGenReqId, reqIdGenFactory } = require('./lib/req-id-gen-factory.js')
const { buildRouting, validateBodyLimitOption, buildRouterOptions } = require('./lib/route')
const build404 = require('./lib/four-oh-four')
const getSecuredInitialConfig = require('./lib/initial-config-validation.js')
const override = require('./lib/plugin-override')
const {
appendStackTrace,
AVVIO_ERRORS_MAP,
...errorCodes
} = require('./lib/errors')
const PonyPromise = require('./lib/promise')
const { defaultInitOptions } = getSecuredInitialConfig
const {
FST_ERR_ASYNC_CONSTRAINT,
FST_ERR_BAD_URL,
FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE,
FST_ERR_OPTIONS_NOT_OBJ,
FST_ERR_QSP_NOT_FN,
FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN,
FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ,
FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR,
FST_ERR_VERSION_CONSTRAINT_NOT_STR,
FST_ERR_INSTANCE_ALREADY_LISTENING,
FST_ERR_REOPENED_CLOSE_SERVER,
FST_ERR_ROUTE_REWRITE_NOT_STR,
FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN,
FST_ERR_ERROR_HANDLER_NOT_FN
FST_ERR_ERROR_HANDLER_NOT_FN,
FST_ERR_ERROR_HANDLER_ALREADY_SET,
FST_ERR_ROUTE_METHOD_INVALID
} = errorCodes
const { buildErrorHandler } = require('./lib/error-handler.js')
const { FSTWRN004 } = require('./lib/warnings.js')
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)
}
const initChannel = diagnostics.channel('fastify.initialization')
/**
* @param {import('./fastify.js').FastifyServerOptions} options
* @param {import('./fastify.js').FastifyServerOptions} serverOptions
*/
function fastify (options) {
// Options validations
options = options || {}
if (typeof options !== 'object') {
throw new FST_ERR_OPTIONS_NOT_OBJ()
}
if (options.querystringParser && typeof options.querystringParser !== 'function') {
throw new FST_ERR_QSP_NOT_FN(typeof options.querystringParser)
}
if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') {
throw new FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN(typeof options.schemaController.bucket)
}
validateBodyLimitOption(options.bodyLimit)
const requestIdHeader = (options.requestIdHeader === false) ? false : (options.requestIdHeader || defaultInitOptions.requestIdHeader).toLowerCase()
const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId)
const requestIdLogLabel = options.requestIdLogLabel || 'reqId'
const bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit
const disableRequestLogging = options.disableRequestLogging || 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 FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ(typeof ajvOptions.customOptions)
}
if (!ajvOptions.plugins || !Array.isArray(ajvOptions.plugins)) {
throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR(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.maxRequestsPerSocket = options.maxRequestsPerSocket || defaultInitOptions.maxRequestsPerSocket
options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout
options.logger = logger
options.requestIdHeader = requestIdHeader
options.requestIdLogLabel = requestIdLogLabel
options.disableRequestLogging = disableRequestLogging
options.ajv = ajvOptions
options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler
const initialConfig = getSecuredInitialConfig(options)
// exposeHeadRoutes have its default set from the validator
options.exposeHeadRoutes = initialConfig.exposeHeadRoutes
let constraints = options.constraints
if (options.versioning) {
FSTDEP009()
constraints = {
...constraints,
version: {
name: 'version',
mustMatchWhenDerived: true,
storage: options.versioning.storage,
deriveConstraint: options.versioning.deriveVersion,
validate (value) {
if (typeof value !== 'string') {
throw new FST_ERR_VERSION_CONSTRAINT_NOT_STR()
}
}
}
}
}
function fastify (serverOptions) {
const {
options,
genReqId,
disableRequestLogging,
hasLogger,
initialConfig
} = processOptions(serverOptions, defaultRoute, onBadUrl)
// Default router
const router = buildRouting({
config: {
defaultRoute,
onBadUrl,
constraints,
ignoreTrailingSlash: options.ignoreTrailingSlash || defaultInitOptions.ignoreTrailingSlash,
ignoreDuplicateSlashes: options.ignoreDuplicateSlashes || defaultInitOptions.ignoreDuplicateSlashes,
maxParamLength: options.maxParamLength || defaultInitOptions.maxParamLength,
caseSensitive: options.caseSensitive,
allowUnsafeRegex: options.allowUnsafeRegex || defaultInitOptions.allowUnsafeRegex,
buildPrettyMeta: defaultBuildPrettyMeta,
querystringParser: options.querystringParser,
useSemicolonDelimiter: options.useSemicolonDelimiter ?? defaultInitOptions.useSemicolonDelimiter
}
})
const router = buildRouting(options.routerOptions)
// 404 router, used for handling encapsulated 404 handlers
const fourOhFour = build404(options)
@@ -193,22 +102,14 @@ function fastify (options) {
// HTTP server and its handler
const httpHandler = wrapRouting(router, options)
// we need to set this before calling createServer
options.http2SessionTimeout = initialConfig.http2SessionTimeout
const { server, listen } = createServer(options, httpHandler)
const serverHasCloseAllConnections = typeof server.closeAllConnections === 'function'
const serverHasCloseIdleConnections = typeof server.closeIdleConnections === 'function'
let forceCloseConnections = options.forceCloseConnections
if (forceCloseConnections === 'idle' && !serverHasCloseIdleConnections) {
throw new FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE()
} else if (typeof forceCloseConnections !== 'boolean') {
/* istanbul ignore next: only one branch can be valid in a given Node.js version */
forceCloseConnections = serverHasCloseIdleConnections ? 'idle' : false
}
const keepAliveConnections = !serverHasCloseAllConnections && forceCloseConnections === true ? new Set() : noopSet()
const {
server,
listen,
forceCloseConnections,
serverHasCloseAllConnections,
serverHasCloseHttp2Sessions,
keepAliveConnections
} = createServer(options, httpHandler)
const setupResponseListeners = Reply.setupResponseListeners
const schemaController = SchemaController.buildSchemaController(null, options.schemaController)
@@ -222,13 +123,30 @@ function fastify (options) {
started: false,
ready: false,
booting: false,
readyPromise: null
aborted: false,
readyResolver: null
},
[kKeepAliveConnections]: keepAliveConnections,
[kSupportedHTTPMethods]: {
bodyless: new Set([
// Standard
'GET',
'HEAD',
'TRACE'
]),
bodywith: new Set([
// Standard
'DELETE',
'OPTIONS',
'PATCH',
'PUT',
'POST'
])
},
[kOptions]: options,
[kChildren]: [],
[kServerBindings]: [],
[kBodyLimit]: bodyLimit,
[kBodyLimit]: options.bodyLimit,
[kRoutePrefix]: '',
[kLogLevel]: '',
[kLogSerializers]: null,
@@ -236,10 +154,11 @@ function fastify (options) {
[kSchemaController]: schemaController,
[kSchemaErrorFormatter]: null,
[kErrorHandler]: buildErrorHandler(),
[kChildLoggerFactory]: defaultChildLoggerFactory,
[kErrorHandlerAlreadySet]: false,
[kChildLoggerFactory]: options.childLoggerFactory || defaultChildLoggerFactory,
[kReplySerializerDefault]: null,
[kContentTypeParser]: new ContentTypeParser(
bodyLimit,
options.bodyLimit,
(options.onProtoPoisoning || defaultInitOptions.onProtoPoisoning),
(options.onConstructorPoisoning || defaultInitOptions.onConstructorPoisoning)
),
@@ -252,8 +171,6 @@ function fastify (options) {
[kGenReqId]: genReqId,
// routing method
routing: httpHandler,
getDefaultRoute: router.getDefaultRoute.bind(router),
setDefaultRoute: router.setDefaultRoute.bind(router),
// routes shorthand methods
delete: function _delete (url, options, handler) {
return router.prepareRoute.call(this, { method: 'DELETE', url, options, handler })
@@ -264,6 +181,9 @@ function fastify (options) {
head: function _head (url, options, handler) {
return router.prepareRoute.call(this, { method: 'HEAD', url, options, handler })
},
trace: function _trace (url, options, handler) {
return router.prepareRoute.call(this, { method: 'TRACE', url, options, handler })
},
patch: function _patch (url, options, handler) {
return router.prepareRoute.call(this, { method: 'PATCH', url, options, handler })
},
@@ -277,7 +197,7 @@ function fastify (options) {
return router.prepareRoute.call(this, { method: 'OPTIONS', url, options, handler })
},
all: function _all (url, options, handler) {
return router.prepareRoute.call(this, { method: supportedMethods, url, options, handler })
return router.prepareRoute.call(this, { method: this.supportedMethods, url, options, handler })
},
// extended route
route: function _route (options) {
@@ -292,7 +212,7 @@ function fastify (options) {
return router.findRoute(options)
},
// expose logger instance
log: logger,
log: options.logger,
// type provider
withTypeProvider,
// hooks
@@ -341,6 +261,8 @@ function fastify (options) {
decorateRequest: decorator.decorateRequest,
hasRequestDecorator: decorator.existRequest,
hasReplyDecorator: decorator.existReply,
getDecorator: decorator.getInstanceDecorator,
addHttpMethod,
// fake http injection
inject,
// pretty print of the registered routes
@@ -408,6 +330,15 @@ function fastify (options) {
genReqId: {
configurable: true,
get () { return this[kGenReqId] }
},
supportedMethods: {
configurable: false,
get () {
return [
...this[kSupportedHTTPMethods].bodyless,
...this[kSupportedHTTPMethods].bodywith
]
}
}
})
@@ -451,7 +382,7 @@ function fastify (options) {
if (forceCloseConnections === 'idle') {
// Not needed in Node 19
instance.server.closeIdleConnections()
/* istanbul ignore next: Cannot test this without Node.js core support */
/* istanbul ignore next: Cannot test this without Node.js core support */
} else if (serverHasCloseAllConnections && forceCloseConnections) {
instance.server.closeAllConnections()
} else if (forceCloseConnections === true) {
@@ -466,6 +397,10 @@ function fastify (options) {
}
}
if (serverHasCloseHttp2Sessions) {
instance.server.closeHttp2Sessions()
}
// No new TCP connections are accepted.
// We must call close on the server even if we are not listening
// otherwise memory will be leaked.
@@ -499,26 +434,17 @@ function fastify (options) {
router.setup(options, {
avvio,
fourOhFour,
logger,
hasLogger,
setupResponseListeners,
throwIfAlreadyStarted,
validateHTTPVersion: compileValidateHTTPVersion(options),
keepAliveConnections
})
// Delay configuring clientError handler so that it can access fastify state.
server.on('clientError', options.clientErrorHandler.bind(fastify))
try {
const dc = require('node: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.
if (initChannel.hasSubscribers) {
initChannel.publish({ fastify })
}
// Older nodejs versions may not have asyncDispose
@@ -577,18 +503,15 @@ function fastify (options) {
}
function ready (cb) {
if (this[kState].readyPromise !== null) {
if (this[kState].readyResolver !== null) {
if (cb != null) {
this[kState].readyPromise.then(() => cb(null, fastify), cb)
this[kState].readyResolver.promise.then(() => cb(null, fastify), cb)
return
}
return this[kState].readyPromise
return this[kState].readyResolver.promise
}
let resolveReady
let rejectReady
// run the hooks after returning the promise
process.nextTick(runHooks)
@@ -596,15 +519,12 @@ function fastify (options) {
// It will work as a barrier for all the .ready() calls (ensuring single hook execution)
// as well as a flow control mechanism to chain cbs and further
// promises
this[kState].readyPromise = new Promise(function (resolve, reject) {
resolveReady = resolve
rejectReady = reject
})
this[kState].readyResolver = PonyPromise.withResolvers()
if (!cb) {
return this[kState].readyPromise
return this[kState].readyResolver.promise
} else {
this[kState].readyPromise.then(() => cb(null, fastify), cb)
this[kState].readyResolver.promise.then(() => cb(null, fastify), cb)
}
function runHooks () {
@@ -629,13 +549,13 @@ function fastify (options) {
: err
if (err) {
return rejectReady(err)
return fastify[kState].readyResolver.reject(err)
}
resolveReady(fastify)
fastify[kState].readyResolver.resolve(fastify)
fastify[kState].booting = false
fastify[kState].ready = true
fastify[kState].promise = null
fastify[kState].readyResolver = null
}
}
@@ -676,8 +596,12 @@ function fastify (options) {
this[kHooks].add(name, fn)
} else {
this.after((err, done) => {
_addHook.call(this, name, fn)
done(err)
try {
_addHook.call(this, name, fn)
done(err)
} catch (err) {
done(err)
}
})
}
return this
@@ -696,46 +620,6 @@ function fastify (options) {
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
}
let body, errorCode, errorStatus, errorLabel
if (err.code === 'ERR_HTTP_REQUEST_TIMEOUT') {
errorCode = '408'
errorStatus = http.STATUS_CODES[errorCode]
body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}`
errorLabel = 'timeout'
} else if (err.code === 'HPE_HEADER_OVERFLOW') {
errorCode = '431'
errorStatus = http.STATUS_CODES[errorCode]
body = `{"error":"${errorStatus}","message":"Exceeded maximum allowed HTTP header size","statusCode":431}`
errorLabel = 'header_overflow'
} else {
errorCode = '400'
errorStatus = http.STATUS_CODES[errorCode]
body = `{"error":"${errorStatus}","message":"Client Error","statusCode":400}`
errorLabel = 'error'
}
// 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 ${errorLabel}`)
// Copying standard node behavior
// 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 ${errorCode} ${errorStatus}\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) {
@@ -750,23 +634,29 @@ function fastify (options) {
}
function onBadUrl (path, req, res) {
if (frameworkErrors) {
if (options.frameworkErrors) {
const id = getGenReqId(onBadUrlContext.server, req)
const childLogger = createChildLogger(onBadUrlContext, logger, req, id)
const childLogger = createChildLogger(onBadUrlContext, options.logger, req, id)
const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
const reply = new Reply(res, request, childLogger)
if (disableRequestLogging === false) {
const resolvedDisableRequestLogging = typeof disableRequestLogging === 'function' ? disableRequestLogging(req) : disableRequestLogging
if (resolvedDisableRequestLogging === false) {
childLogger.info({ req: request }, 'incoming request')
}
return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
return options.frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
}
const body = `{"error":"Bad Request","code":"FST_ERR_BAD_URL","message":"'${path}' is not a valid url component","statusCode":400}`
const body = JSON.stringify({
error: 'Bad Request',
code: 'FST_ERR_BAD_URL',
message: `'${path}' is not a valid url component`,
statusCode: 400
})
res.writeHead(400, {
'Content-Type': 'application/json',
'Content-Length': body.length
'Content-Length': Buffer.byteLength(body)
})
res.end(body)
}
@@ -775,18 +665,19 @@ function fastify (options) {
if (isAsync === false) return undefined
return function onAsyncConstraintError (err) {
if (err) {
if (frameworkErrors) {
if (options.frameworkErrors) {
const id = getGenReqId(onBadUrlContext.server, req)
const childLogger = createChildLogger(onBadUrlContext, logger, req, id)
const childLogger = createChildLogger(onBadUrlContext, options.logger, req, id)
const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
const reply = new Reply(res, request, childLogger)
if (disableRequestLogging === false) {
const resolvedDisableRequestLogging = typeof disableRequestLogging === 'function' ? disableRequestLogging(req) : disableRequestLogging
if (resolvedDisableRequestLogging === false) {
childLogger.info({ req: request }, 'incoming request')
}
return frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)
return options.frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)
}
const body = '{"error":"Internal Server Error","message":"Unexpected error from async constraint","statusCode":500}'
res.writeHead(500, {
@@ -827,7 +718,10 @@ function fastify (options) {
function setSchemaController (schemaControllerOpts) {
throwIfAlreadyStarted('Cannot call "setSchemaController"!')
const old = this[kSchemaController]
const schemaController = SchemaController.buildSchemaController(old, Object.assign({}, old.opts, schemaControllerOpts))
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)
@@ -849,6 +743,13 @@ function fastify (options) {
throw new FST_ERR_ERROR_HANDLER_NOT_FN()
}
if (!options.allowErrorHandlerOverride && this[kErrorHandlerAlreadySet]) {
throw new FST_ERR_ERROR_HANDLER_ALREADY_SET()
} else if (this[kErrorHandlerAlreadySet]) {
FSTWRN004("To disable this behavior, set 'allowErrorHandlerOverride' to false or ignore this message. For more information, visit: https://fastify.dev/docs/latest/Reference/Server/#allowerrorhandleroverride")
}
this[kErrorHandlerAlreadySet] = true
this[kErrorHandler] = buildErrorHandler(this[kErrorHandler], func.bind(this))
return this
}
@@ -862,7 +763,9 @@ function fastify (options) {
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
opts.includeMeta = opts.includeHooks
? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks
: opts.includeMeta
return router.printRoutes(opts)
}
@@ -891,6 +794,168 @@ function fastify (options) {
this[kGenReqId] = reqIdGenFactory(this[kOptions].requestIdHeader, func)
return this
}
function addHttpMethod (method, { hasBody = false } = {}) {
if (typeof method !== 'string' || http.METHODS.indexOf(method) === -1) {
throw new FST_ERR_ROUTE_METHOD_INVALID()
}
if (hasBody === true) {
this[kSupportedHTTPMethods].bodywith.add(method)
this[kSupportedHTTPMethods].bodyless.delete(method)
} else {
this[kSupportedHTTPMethods].bodywith.delete(method)
this[kSupportedHTTPMethods].bodyless.add(method)
}
const _method = method.toLowerCase()
if (!this.hasDecorator(_method)) {
this.decorate(_method, function (url, options, handler) {
return router.prepareRoute.call(this, { method, url, options, handler })
})
}
return this
}
}
function processOptions (options, defaultRoute, onBadUrl) {
// Options validations
if (options && typeof options !== 'object') {
throw new FST_ERR_OPTIONS_NOT_OBJ()
} else {
// Shallow copy options object to prevent mutations outside of this function
options = Object.assign({}, options)
}
if (
(options.querystringParser && typeof options.querystringParser !== 'function') ||
(
options.routerOptions?.querystringParser &&
typeof options.routerOptions.querystringParser !== 'function'
)
) {
throw new FST_ERR_QSP_NOT_FN(typeof (options.querystringParser ?? options.routerOptions.querystringParser))
}
if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') {
throw new FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN(typeof options.schemaController.bucket)
}
validateBodyLimitOption(options.bodyLimit)
const requestIdHeader = typeof options.requestIdHeader === 'string' && options.requestIdHeader.length !== 0 ? options.requestIdHeader.toLowerCase() : (options.requestIdHeader === true && 'request-id')
const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId)
const requestIdLogLabel = options.requestIdLogLabel || 'reqId'
options.bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit
const disableRequestLogging = options.disableRequestLogging || false
const ajvOptions = Object.assign({
customOptions: {},
plugins: []
}, options.ajv)
if (!ajvOptions.customOptions || Object.prototype.toString.call(ajvOptions.customOptions) !== '[object Object]') {
throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ(typeof ajvOptions.customOptions)
}
if (!ajvOptions.plugins || !Array.isArray(ajvOptions.plugins)) {
throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR(typeof ajvOptions.plugins)
}
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.maxRequestsPerSocket = options.maxRequestsPerSocket || defaultInitOptions.maxRequestsPerSocket
options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout
options.logger = logger
options.requestIdHeader = requestIdHeader
options.requestIdLogLabel = requestIdLogLabel
options.disableRequestLogging = disableRequestLogging
options.ajv = ajvOptions
options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler
options.allowErrorHandlerOverride = options.allowErrorHandlerOverride ?? defaultInitOptions.allowErrorHandlerOverride
const initialConfig = getSecuredInitialConfig(options)
// exposeHeadRoutes have its default set from the validator
options.exposeHeadRoutes = initialConfig.exposeHeadRoutes
// we need to set this before calling createServer
options.http2SessionTimeout = initialConfig.http2SessionTimeout
options.routerOptions = buildRouterOptions(options, {
defaultRoute,
onBadUrl,
ignoreTrailingSlash: defaultInitOptions.ignoreTrailingSlash,
ignoreDuplicateSlashes: defaultInitOptions.ignoreDuplicateSlashes,
maxParamLength: defaultInitOptions.maxParamLength,
allowUnsafeRegex: defaultInitOptions.allowUnsafeRegex,
buildPrettyMeta: defaultBuildPrettyMeta,
useSemicolonDelimiter: defaultInitOptions.useSemicolonDelimiter
})
return {
options,
genReqId,
disableRequestLogging,
hasLogger,
initialConfig
}
}
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 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
}
let body, errorCode, errorStatus, errorLabel
if (err.code === 'ERR_HTTP_REQUEST_TIMEOUT') {
errorCode = '408'
errorStatus = http.STATUS_CODES[errorCode]
body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}`
errorLabel = 'timeout'
} else if (err.code === 'HPE_HEADER_OVERFLOW') {
errorCode = '431'
errorStatus = http.STATUS_CODES[errorCode]
body = `{"error":"${errorStatus}","message":"Exceeded maximum allowed HTTP header size","statusCode":431}`
errorLabel = 'header_overflow'
} else {
errorCode = '400'
errorStatus = http.STATUS_CODES[errorCode]
body = `{"error":"${errorStatus}","message":"Client Error","statusCode":400}`
errorLabel = 'error'
}
// 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 ${errorLabel}`)
// Copying standard node behavior
// 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 ${errorCode} ${errorStatus}\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
}
socket.destroy(err)
}
function validateSchemaErrorFormatter (schemaErrorFormatter) {
@@ -903,7 +968,7 @@ function validateSchemaErrorFormatter (schemaErrorFormatter) {
/**
* These export configurations enable JS and TS developers
* to consumer fastify in whatever way best suits their needs.
* to consume fastify in whatever way best suits their needs.
* Some examples of supported import syntax includes:
* - `const fastify = require('fastify')`
* - `const { fastify } = require('fastify')`