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

@@ -2,16 +2,10 @@
const FindMyWay = require('find-my-way')
const Context = require('./context')
const handleRequest = require('./handleRequest')
const handleRequest = require('./handle-request.js')
const { onRequestAbortHookRunner, lifecycleHooks, preParsingHookRunner, onTimeoutHookRunner, onRequestHookRunner } = require('./hooks')
const { supportedMethods } = require('./httpMethods')
const { normalizeSchema } = require('./schemas')
const { parseHeadOnSendHandlers } = require('./headRoute')
const {
FSTDEP007,
FSTDEP008,
FSTDEP014
} = require('./warnings')
const { parseHeadOnSendHandlers } = require('./head-route.js')
const {
compileSchemasForValidation,
@@ -21,7 +15,6 @@ const {
const {
FST_ERR_SCH_VALIDATION_BUILD,
FST_ERR_SCH_SERIALIZATION_BUILD,
FST_ERR_DEFAULT_ROUTE_INVALID_TYPE,
FST_ERR_DUPLICATED_ROUTE,
FST_ERR_INVALID_URL,
FST_ERR_HOOK_INVALID_HANDLER,
@@ -38,6 +31,7 @@ const {
const {
kRoutePrefix,
kSupportedHTTPMethods,
kLogLevel,
kLogSerializers,
kHooks,
@@ -55,11 +49,26 @@ const {
kRouteContext
} = require('./symbols.js')
const { buildErrorHandler } = require('./error-handler')
const { createChildLogger } = require('./logger')
const { getGenReqId } = require('./reqIdGenFactory.js')
const { createChildLogger } = require('./logger-factory.js')
const { getGenReqId } = require('./req-id-gen-factory.js')
const { FSTDEP022 } = require('./warnings')
const routerKeys = [
'allowUnsafeRegex',
'buildPrettyMeta',
'caseSensitive',
'constraints',
'defaultRoute',
'ignoreDuplicateSlashes',
'ignoreTrailingSlash',
'maxParamLength',
'onBadUrl',
'querystringParser',
'useSemicolonDelimiter'
]
function buildRouting (options) {
const router = FindMyWay(options.config)
const router = FindMyWay(options)
let avvio
let fourOhFour
@@ -68,11 +77,11 @@ function buildRouting (options) {
let setupResponseListeners
let throwIfAlreadyStarted
let disableRequestLogging
let disableRequestLoggingFn
let ignoreTrailingSlash
let ignoreDuplicateSlashes
let return503OnClosing
let globalExposeHeadRoutes
let validateHTTPVersion
let keepAliveConnections
let closing = false
@@ -85,35 +94,26 @@ function buildRouting (options) {
setup (options, fastifyArgs) {
avvio = fastifyArgs.avvio
fourOhFour = fastifyArgs.fourOhFour
logger = fastifyArgs.logger
logger = options.logger
hasLogger = fastifyArgs.hasLogger
setupResponseListeners = fastifyArgs.setupResponseListeners
throwIfAlreadyStarted = fastifyArgs.throwIfAlreadyStarted
validateHTTPVersion = fastifyArgs.validateHTTPVersion
globalExposeHeadRoutes = options.exposeHeadRoutes
disableRequestLogging = options.disableRequestLogging
ignoreTrailingSlash = options.ignoreTrailingSlash
ignoreDuplicateSlashes = options.ignoreDuplicateSlashes
return503OnClosing = Object.prototype.hasOwnProperty.call(options, 'return503OnClosing') ? options.return503OnClosing : true
if (typeof disableRequestLogging === 'function') {
disableRequestLoggingFn = options.disableRequestLogging
}
ignoreTrailingSlash = options.routerOptions.ignoreTrailingSlash
ignoreDuplicateSlashes = options.routerOptions.ignoreDuplicateSlashes
return503OnClosing = Object.hasOwn(options, 'return503OnClosing') ? options.return503OnClosing : true
keepAliveConnections = fastifyArgs.keepAliveConnections
},
routing: router.lookup.bind(router), // router func to find the right handler to call
route, // configure a route in the fastify instance
hasRoute,
prepareRoute,
getDefaultRoute: function () {
FSTDEP014()
return router.defaultRoute
},
setDefaultRoute: function (defaultRoute) {
FSTDEP014()
if (typeof defaultRoute !== 'function') {
throw new FST_ERR_DEFAULT_ROUTE_INVALID_TYPE()
}
router.defaultRoute = defaultRoute
},
routeHandler,
closeRoutes: () => { closing = true },
printRoutes: router.prettyPrint.bind(router),
@@ -169,10 +169,11 @@ function buildRouting (options) {
function hasRoute ({ options }) {
const normalizedMethod = options.method?.toUpperCase() ?? ''
return findRoute({
...options,
method: normalizedMethod
}) !== null
return router.hasRoute(
normalizedMethod,
options.url || '',
options.constraints
)
}
function findRoute (options) {
@@ -200,36 +201,13 @@ function buildRouting (options) {
* @param {{ options: import('../fastify').RouteOptions, isFastify: boolean }}
*/
function route ({ options, isFastify }) {
throwIfAlreadyStarted('Cannot add route!')
// Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator
const opts = { ...options }
const { exposeHeadRoute } = opts
const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
const isGetRoute = opts.method === 'GET' ||
(Array.isArray(opts.method) && opts.method.includes('GET'))
const isHeadRoute = opts.method === 'HEAD' ||
(Array.isArray(opts.method) && opts.method.includes('HEAD'))
// we need to clone a set of initial options for HEAD route
const headOpts = shouldExposeHead && isGetRoute ? { ...options } : null
throwIfAlreadyStarted('Cannot add route!')
const path = opts.url || opts.path || ''
if (Array.isArray(opts.method)) {
// eslint-disable-next-line no-var
for (var i = 0; i < opts.method.length; ++i) {
opts.method[i] = normalizeAndValidateMethod(opts.method[i])
validateSchemaBodyOption(opts.method[i], path, opts.schema)
}
} else {
opts.method = normalizeAndValidateMethod(opts.method)
validateSchemaBodyOption(opts.method, path, opts.schema)
}
if (!opts.handler) {
throw new FST_ERR_ROUTE_MISSING_HANDLER(opts.method, path)
}
@@ -240,6 +218,30 @@ function buildRouting (options) {
validateBodyLimitOption(opts.bodyLimit)
const shouldExposeHead = opts.exposeHeadRoute ?? globalExposeHeadRoutes
let isGetRoute = false
let isHeadRoute = false
if (Array.isArray(opts.method)) {
for (let i = 0; i < opts.method.length; ++i) {
opts.method[i] = normalizeAndValidateMethod.call(this, opts.method[i])
validateSchemaBodyOption.call(this, opts.method[i], path, opts.schema)
isGetRoute = opts.method.includes('GET')
isHeadRoute = opts.method.includes('HEAD')
}
} else {
opts.method = normalizeAndValidateMethod.call(this, opts.method)
validateSchemaBodyOption.call(this, opts.method, path, opts.schema)
isGetRoute = opts.method === 'GET'
isHeadRoute = opts.method === 'HEAD'
}
// we need to clone a set of initial options for HEAD route
const headOpts = shouldExposeHead && isGetRoute ? { ...options } : null
const prefix = this[kRoutePrefix]
if (path === '/' && prefix.length > 0 && opts.method !== 'HEAD') {
@@ -347,19 +349,9 @@ function buildRouting (options) {
isFastify
})
if (opts.version) {
FSTDEP008()
constraints.version = opts.version
}
const headHandler = router.findRoute('HEAD', opts.url, constraints)
const hasHEADHandler = headHandler !== null
// remove the head route created by fastify
if (isHeadRoute && hasHEADHandler && !context[kRouteByFastify] && headHandler.store[kRouteByFastify]) {
router.off('HEAD', opts.url, constraints)
}
try {
router.on(opts.method, opts.url, { constraints }, routeHandler, context)
} catch (error) {
@@ -377,13 +369,16 @@ function buildRouting (options) {
this.after((notHandledErr, done) => {
// Send context async
context.errorHandler = opts.errorHandler ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) : this[kErrorHandler]
context.errorHandler = opts.errorHandler
? buildErrorHandler(this[kErrorHandler], opts.errorHandler)
: this[kErrorHandler]
context._parserOptions.limit = opts.bodyLimit || null
context.logLevel = opts.logLevel
context.logSerializers = opts.logSerializers
context.attachValidation = opts.attachValidation
context[kReplySerializerDefault] = this[kReplySerializerDefault]
context.schemaErrorFormatter = opts.schemaErrorFormatter || this[kSchemaErrorFormatter] || context.schemaErrorFormatter
context.schemaErrorFormatter =
opts.schemaErrorFormatter || this[kSchemaErrorFormatter] || context.schemaErrorFormatter
// Run hooks and more
avvio.once('preReady', () => {
@@ -407,15 +402,24 @@ function buildRouting (options) {
fourOhFour.setContext(this, context)
if (opts.schema) {
context.schema = normalizeSchema(opts, context.schema, this.initialConfig)
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)) {
const hasValidationSchema = opts.schema.body ||
opts.schema.headers ||
opts.schema.querystring ||
opts.schema.params
if (!opts.validatorCompiler && hasValidationSchema) {
schemaController.setupValidator(this[kOptions])
}
try {
const isCustom = typeof opts?.validatorCompiler === 'function' || schemaController.isCustomValidatorCompiler
compileSchemasForValidation(context, opts.validatorCompiler || schemaController.validatorCompiler, isCustom)
const isCustom = typeof opts?.validatorCompiler === 'function' ||
schemaController.isCustomValidatorCompiler
compileSchemasForValidation(
context,
opts.validatorCompiler || schemaController.validatorCompiler,
isCustom
)
} catch (error) {
throw new FST_ERR_SCH_VALIDATION_BUILD(opts.method, url, error.message)
}
@@ -440,8 +444,6 @@ function buildRouting (options) {
if (shouldExposeHead && isGetRoute && !isHeadRoute && !hasHEADHandler) {
const onSendHandlers = parseHeadOnSendHandlers(headOpts.onSend)
prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...headOpts, onSend: onSendHandlers }, isFastify: true })
} else if (hasHEADHandler && exposeHeadRoute) {
FSTDEP007()
}
}
}
@@ -458,20 +460,8 @@ function buildRouting (options) {
loggerOpts.serializers = context.logSerializers
}
const childLogger = createChildLogger(context, logger, req, id, loggerOpts)
childLogger[kDisableRequestLogging] = disableRequestLogging
// TODO: The check here should be removed once https://github.com/nodejs/node/issues/43115 resolve in core.
if (!validateHTTPVersion(req.httpVersion)) {
childLogger.info({ res: { statusCode: 505 } }, 'request aborted - invalid HTTP version')
const message = '{"error":"HTTP Version Not Supported","message":"HTTP Version Not Supported","statusCode":505}'
const headers = {
'Content-Type': 'application/json',
'Content-Length': message.length
}
res.writeHead(505, headers)
res.end(message)
return
}
// Set initial value; will be re-evaluated after FastifyRequest is constructed if it's a function
childLogger[kDisableRequestLogging] = disableRequestLoggingFn ? false : disableRequestLogging
if (closing === true) {
/* istanbul ignore next mac, windows */
@@ -515,7 +505,15 @@ function buildRouting (options) {
const request = new context.Request(id, params, req, query, childLogger, context)
const reply = new context.Reply(res, request, childLogger)
if (disableRequestLogging === false) {
// Evaluate disableRequestLogging after FastifyRequest is constructed
// so the caller has access to decorations and customizations
const resolvedDisableRequestLogging = disableRequestLoggingFn
? disableRequestLoggingFn(request)
: disableRequestLogging
childLogger[kDisableRequestLogging] = resolvedDisableRequestLogging
if (resolvedDisableRequestLogging === false) {
childLogger.info({ req: request }, 'incoming request')
}
@@ -577,7 +575,8 @@ function normalizeAndValidateMethod (method) {
throw new FST_ERR_ROUTE_METHOD_INVALID()
}
method = method.toUpperCase()
if (supportedMethods.indexOf(method) === -1) {
if (!this[kSupportedHTTPMethods].bodyless.has(method) &&
!this[kSupportedHTTPMethods].bodywith.has(method)) {
throw new FST_ERR_ROUTE_METHOD_NOT_SUPPORTED(method)
}
@@ -585,7 +584,7 @@ function normalizeAndValidateMethod (method) {
}
function validateSchemaBodyOption (method, path, schema) {
if ((method === 'GET' || method === 'HEAD') && schema && schema.body) {
if (this[kSupportedHTTPMethods].bodyless.has(method) && schema?.body) {
throw new FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED(method, path)
}
}
@@ -608,12 +607,30 @@ function runPreParsing (err, request, reply) {
request[kRequestPayloadStream] = request.raw
if (request[kRouteContext].preParsing !== null) {
preParsingHookRunner(request[kRouteContext].preParsing, request, reply, handleRequest)
preParsingHookRunner(request[kRouteContext].preParsing, request, reply, handleRequest.bind(request.server))
} else {
handleRequest(null, request, reply)
handleRequest.call(request.server, null, request, reply)
}
}
function buildRouterOptions (options, defaultOptions) {
const routerOptions = options.routerOptions || Object.create(null)
const usedDeprecatedOptions = routerKeys.filter(key => Object.hasOwn(options, key))
if (usedDeprecatedOptions.length > 0) {
FSTDEP022(usedDeprecatedOptions.join(', '))
}
for (const key of routerKeys) {
if (!Object.hasOwn(routerOptions, key)) {
routerOptions[key] = options[key] ?? defaultOptions[key]
}
}
return routerOptions
}
/**
* 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
@@ -625,4 +642,4 @@ function removeTrackedSocket () {
function noop () { }
module.exports = { buildRouting, validateBodyLimitOption }
module.exports = { buildRouting, validateBodyLimitOption, buildRouterOptions }