fastify 5.6.0 → 5.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +22 -0
- package/SECURITY.md +12 -0
- package/SPONSORS.md +1 -0
- package/build/build-validation.js +1 -1
- package/build/sync-version.js +1 -0
- package/docs/Guides/Ecosystem.md +5 -0
- package/docs/Guides/Fluent-Schema.md +2 -2
- package/docs/Reference/Decorators.md +36 -169
- package/docs/Reference/Encapsulation.md +5 -1
- package/docs/Reference/Plugins.md +11 -2
- package/docs/Reference/Reply.md +4 -1
- package/docs/Reference/Server.md +39 -1
- package/docs/Reference/Type-Providers.md +2 -2
- package/docs/Reference/TypeScript.md +136 -0
- package/eslint.config.js +18 -2
- package/fastify.d.ts +1 -1
- package/fastify.js +183 -168
- package/lib/{contentTypeParser.js → content-type-parser.js} +2 -1
- package/lib/error-handler.js +2 -2
- package/lib/{fourOhFour.js → four-oh-four.js} +4 -2
- package/lib/{handleRequest.js → handle-request.js} +1 -1
- package/lib/{headRoute.js → head-route.js} +13 -1
- package/lib/{initialConfigValidation.js → initial-config-validation.js} +1 -1
- package/lib/{pluginOverride.js → plugin-override.js} +2 -2
- package/lib/{pluginUtils.js → plugin-utils.js} +2 -2
- package/lib/reply.js +5 -3
- package/lib/request.js +5 -0
- package/lib/route.js +20 -9
- package/lib/server.js +94 -8
- package/lib/symbols.js +1 -0
- package/lib/validation.js +9 -1
- package/lib/warnings.js +1 -1
- package/package.json +8 -8
- package/test/500s.test.js +191 -0
- package/test/child-logger-factory.test.js +3 -3
- package/test/content-parser.test.js +2 -1
- package/test/decorator-namespace.test._js_ +1 -1
- package/test/diagnostics-channel/error-before-handler.test.js +1 -1
- package/test/http2/closing.test.js +88 -0
- package/test/internals/content-type-parser.test.js +2 -2
- package/test/internals/handle-request.test.js +2 -2
- package/test/internals/initial-config.test.js +1 -1
- package/test/internals/plugin.test.js +2 -2
- package/test/internals/reply.test.js +22 -3
- package/test/internals/req-id-gen-factory.test.js +1 -1
- package/test/promises.test.js +3 -3
- package/test/reply-web-stream-locked.test.js +37 -0
- package/test/request-error.test.js +116 -0
- package/test/route.6.test.js +20 -1
- package/test/route.7.test.js +49 -0
- package/test/schema-validation.test.js +27 -4
- package/test/server.test.js +22 -4
- package/test/set-error-handler.test.js +1 -1
- package/test/skip-reply-send.test.js +2 -2
- package/test/stream.5.test.js +3 -3
- package/test/types/fastify.test-d.ts +70 -18
- package/test/types/hooks.test-d.ts +6 -1
- package/test/types/instance.test-d.ts +35 -15
- package/test/types/logger.test-d.ts +18 -6
- package/test/types/plugin.test-d.ts +24 -6
- package/test/types/register.test-d.ts +108 -33
- package/test/types/reply.test-d.ts +23 -6
- package/test/types/request.test-d.ts +25 -6
- package/test/types/route.test-d.ts +10 -1
- package/test/types/schema.test-d.ts +21 -0
- package/test/validation-error-handling.test.js +68 -1
- package/test/wrap-thenable.test.js +1 -1
- package/types/instance.d.ts +2 -2
- package/types/schema.d.ts +1 -1
- package/test/check.test.js +0 -219
- /package/lib/{configValidator.js → config-validator.js} +0 -0
- /package/lib/{reqIdGenFactory.js → req-id-gen-factory.js} +0 -0
- /package/lib/{wrapThenable.js → wrap-thenable.js} +0 -0
- /package/types/{serverFactory.d.ts → server-factory.d.ts} +0 -0
package/fastify.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const VERSION = '5.6.
|
|
3
|
+
const VERSION = '5.6.2'
|
|
4
4
|
|
|
5
5
|
const Avvio = require('avvio')
|
|
6
6
|
const http = require('node:http')
|
|
@@ -40,17 +40,16 @@ const Reply = require('./lib/reply')
|
|
|
40
40
|
const Request = require('./lib/request')
|
|
41
41
|
const Context = require('./lib/context.js')
|
|
42
42
|
const decorator = require('./lib/decorate')
|
|
43
|
-
const ContentTypeParser = require('./lib/
|
|
43
|
+
const ContentTypeParser = require('./lib/content-type-parser.js')
|
|
44
44
|
const SchemaController = require('./lib/schema-controller')
|
|
45
45
|
const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks')
|
|
46
46
|
const { createChildLogger, defaultChildLoggerFactory, createLogger } = require('./lib/logger-factory')
|
|
47
|
-
const pluginUtils = require('./lib/
|
|
48
|
-
const { getGenReqId, reqIdGenFactory } = require('./lib/
|
|
47
|
+
const pluginUtils = require('./lib/plugin-utils.js')
|
|
48
|
+
const { getGenReqId, reqIdGenFactory } = require('./lib/req-id-gen-factory.js')
|
|
49
49
|
const { buildRouting, validateBodyLimitOption, buildRouterOptions } = require('./lib/route')
|
|
50
|
-
const build404 = require('./lib/
|
|
51
|
-
const getSecuredInitialConfig = require('./lib/
|
|
52
|
-
const override = require('./lib/
|
|
53
|
-
const noopSet = require('./lib/noop-set')
|
|
50
|
+
const build404 = require('./lib/four-oh-four')
|
|
51
|
+
const getSecuredInitialConfig = require('./lib/initial-config-validation.js')
|
|
52
|
+
const override = require('./lib/plugin-override')
|
|
54
53
|
const {
|
|
55
54
|
appendStackTrace,
|
|
56
55
|
AVVIO_ERRORS_MAP,
|
|
@@ -63,7 +62,6 @@ const { defaultInitOptions } = getSecuredInitialConfig
|
|
|
63
62
|
const {
|
|
64
63
|
FST_ERR_ASYNC_CONSTRAINT,
|
|
65
64
|
FST_ERR_BAD_URL,
|
|
66
|
-
FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE,
|
|
67
65
|
FST_ERR_OPTIONS_NOT_OBJ,
|
|
68
66
|
FST_ERR_QSP_NOT_FN,
|
|
69
67
|
FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN,
|
|
@@ -83,99 +81,17 @@ const { FSTWRN004 } = require('./lib/warnings.js')
|
|
|
83
81
|
|
|
84
82
|
const initChannel = diagnostics.channel('fastify.initialization')
|
|
85
83
|
|
|
86
|
-
function defaultBuildPrettyMeta (route) {
|
|
87
|
-
// return a shallow copy of route's sanitized context
|
|
88
|
-
|
|
89
|
-
const cleanKeys = {}
|
|
90
|
-
const allowedProps = ['errorHandler', 'logLevel', 'logSerializers']
|
|
91
|
-
|
|
92
|
-
allowedProps.concat(supportedHooks).forEach(k => {
|
|
93
|
-
cleanKeys[k] = route.store[k]
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
return Object.assign({}, cleanKeys)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
84
|
/**
|
|
100
85
|
* @param {import('./fastify.js').FastifyServerOptions} options
|
|
101
86
|
*/
|
|
102
|
-
function fastify (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
(options.querystringParser && typeof options.querystringParser !== 'function') ||
|
|
113
|
-
(
|
|
114
|
-
options.routerOptions?.querystringParser &&
|
|
115
|
-
typeof options.routerOptions.querystringParser !== 'function'
|
|
116
|
-
)
|
|
117
|
-
) {
|
|
118
|
-
throw new FST_ERR_QSP_NOT_FN(typeof (options.querystringParser ?? options.routerOptions.querystringParser))
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') {
|
|
122
|
-
throw new FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN(typeof options.schemaController.bucket)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
validateBodyLimitOption(options.bodyLimit)
|
|
126
|
-
|
|
127
|
-
const requestIdHeader = typeof options.requestIdHeader === 'string' && options.requestIdHeader.length !== 0 ? options.requestIdHeader.toLowerCase() : (options.requestIdHeader === true && 'request-id')
|
|
128
|
-
const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId)
|
|
129
|
-
const requestIdLogLabel = options.requestIdLogLabel || 'reqId'
|
|
130
|
-
const bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit
|
|
131
|
-
const disableRequestLogging = options.disableRequestLogging || false
|
|
132
|
-
|
|
133
|
-
const ajvOptions = Object.assign({
|
|
134
|
-
customOptions: {},
|
|
135
|
-
plugins: []
|
|
136
|
-
}, options.ajv)
|
|
137
|
-
const frameworkErrors = options.frameworkErrors
|
|
138
|
-
|
|
139
|
-
// Ajv options
|
|
140
|
-
if (!ajvOptions.customOptions || Object.prototype.toString.call(ajvOptions.customOptions) !== '[object Object]') {
|
|
141
|
-
throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ(typeof ajvOptions.customOptions)
|
|
142
|
-
}
|
|
143
|
-
if (!ajvOptions.plugins || !Array.isArray(ajvOptions.plugins)) {
|
|
144
|
-
throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR(typeof ajvOptions.plugins)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Instance Fastify components
|
|
148
|
-
|
|
149
|
-
const { logger, hasLogger } = createLogger(options)
|
|
150
|
-
|
|
151
|
-
// Update the options with the fixed values
|
|
152
|
-
options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout
|
|
153
|
-
options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout
|
|
154
|
-
options.maxRequestsPerSocket = options.maxRequestsPerSocket || defaultInitOptions.maxRequestsPerSocket
|
|
155
|
-
options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout
|
|
156
|
-
options.logger = logger
|
|
157
|
-
options.requestIdHeader = requestIdHeader
|
|
158
|
-
options.requestIdLogLabel = requestIdLogLabel
|
|
159
|
-
options.disableRequestLogging = disableRequestLogging
|
|
160
|
-
options.ajv = ajvOptions
|
|
161
|
-
options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler
|
|
162
|
-
options.allowErrorHandlerOverride = options.allowErrorHandlerOverride ?? defaultInitOptions.allowErrorHandlerOverride
|
|
163
|
-
|
|
164
|
-
const initialConfig = getSecuredInitialConfig(options)
|
|
165
|
-
|
|
166
|
-
// exposeHeadRoutes have its default set from the validator
|
|
167
|
-
options.exposeHeadRoutes = initialConfig.exposeHeadRoutes
|
|
168
|
-
|
|
169
|
-
options.routerOptions = buildRouterOptions(options, {
|
|
170
|
-
defaultRoute,
|
|
171
|
-
onBadUrl,
|
|
172
|
-
ignoreTrailingSlash: defaultInitOptions.ignoreTrailingSlash,
|
|
173
|
-
ignoreDuplicateSlashes: defaultInitOptions.ignoreDuplicateSlashes,
|
|
174
|
-
maxParamLength: defaultInitOptions.maxParamLength,
|
|
175
|
-
allowUnsafeRegex: defaultInitOptions.allowUnsafeRegex,
|
|
176
|
-
buildPrettyMeta: defaultBuildPrettyMeta,
|
|
177
|
-
useSemicolonDelimiter: defaultInitOptions.useSemicolonDelimiter
|
|
178
|
-
})
|
|
87
|
+
function fastify (serverOptions) {
|
|
88
|
+
const {
|
|
89
|
+
options,
|
|
90
|
+
genReqId,
|
|
91
|
+
disableRequestLogging,
|
|
92
|
+
hasLogger,
|
|
93
|
+
initialConfig
|
|
94
|
+
} = processOptions(serverOptions, defaultRoute, onBadUrl)
|
|
179
95
|
|
|
180
96
|
// Default router
|
|
181
97
|
const router = buildRouting({
|
|
@@ -188,22 +104,14 @@ function fastify (options) {
|
|
|
188
104
|
// HTTP server and its handler
|
|
189
105
|
const httpHandler = wrapRouting(router, options)
|
|
190
106
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (forceCloseConnections === 'idle' && !serverHasCloseIdleConnections) {
|
|
200
|
-
throw new FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE()
|
|
201
|
-
} else if (typeof forceCloseConnections !== 'boolean') {
|
|
202
|
-
/* istanbul ignore next: only one branch can be valid in a given Node.js version */
|
|
203
|
-
forceCloseConnections = serverHasCloseIdleConnections ? 'idle' : false
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const keepAliveConnections = !serverHasCloseAllConnections && forceCloseConnections === true ? new Set() : noopSet()
|
|
107
|
+
const {
|
|
108
|
+
server,
|
|
109
|
+
listen,
|
|
110
|
+
forceCloseConnections,
|
|
111
|
+
serverHasCloseAllConnections,
|
|
112
|
+
serverHasCloseHttp2Sessions,
|
|
113
|
+
keepAliveConnections
|
|
114
|
+
} = createServer(options, httpHandler)
|
|
207
115
|
|
|
208
116
|
const setupResponseListeners = Reply.setupResponseListeners
|
|
209
117
|
const schemaController = SchemaController.buildSchemaController(null, options.schemaController)
|
|
@@ -240,7 +148,7 @@ function fastify (options) {
|
|
|
240
148
|
[kOptions]: options,
|
|
241
149
|
[kChildren]: [],
|
|
242
150
|
[kServerBindings]: [],
|
|
243
|
-
[kBodyLimit]: bodyLimit,
|
|
151
|
+
[kBodyLimit]: options.bodyLimit,
|
|
244
152
|
[kRoutePrefix]: '',
|
|
245
153
|
[kLogLevel]: '',
|
|
246
154
|
[kLogSerializers]: null,
|
|
@@ -249,10 +157,10 @@ function fastify (options) {
|
|
|
249
157
|
[kSchemaErrorFormatter]: null,
|
|
250
158
|
[kErrorHandler]: buildErrorHandler(),
|
|
251
159
|
[kErrorHandlerAlreadySet]: false,
|
|
252
|
-
[kChildLoggerFactory]: defaultChildLoggerFactory,
|
|
160
|
+
[kChildLoggerFactory]: options.childLoggerFactory || defaultChildLoggerFactory,
|
|
253
161
|
[kReplySerializerDefault]: null,
|
|
254
162
|
[kContentTypeParser]: new ContentTypeParser(
|
|
255
|
-
bodyLimit,
|
|
163
|
+
options.bodyLimit,
|
|
256
164
|
(options.onProtoPoisoning || defaultInitOptions.onProtoPoisoning),
|
|
257
165
|
(options.onConstructorPoisoning || defaultInitOptions.onConstructorPoisoning)
|
|
258
166
|
),
|
|
@@ -306,7 +214,7 @@ function fastify (options) {
|
|
|
306
214
|
return router.findRoute(options)
|
|
307
215
|
},
|
|
308
216
|
// expose logger instance
|
|
309
|
-
log: logger,
|
|
217
|
+
log: options.logger,
|
|
310
218
|
// type provider
|
|
311
219
|
withTypeProvider,
|
|
312
220
|
// hooks
|
|
@@ -491,6 +399,10 @@ function fastify (options) {
|
|
|
491
399
|
}
|
|
492
400
|
}
|
|
493
401
|
|
|
402
|
+
if (serverHasCloseHttp2Sessions) {
|
|
403
|
+
instance.server.closeHttp2Sessions()
|
|
404
|
+
}
|
|
405
|
+
|
|
494
406
|
// No new TCP connections are accepted.
|
|
495
407
|
// We must call close on the server even if we are not listening
|
|
496
408
|
// otherwise memory will be leaked.
|
|
@@ -524,7 +436,6 @@ function fastify (options) {
|
|
|
524
436
|
router.setup(options, {
|
|
525
437
|
avvio,
|
|
526
438
|
fourOhFour,
|
|
527
|
-
logger,
|
|
528
439
|
hasLogger,
|
|
529
440
|
setupResponseListeners,
|
|
530
441
|
throwIfAlreadyStarted,
|
|
@@ -711,46 +622,6 @@ function fastify (options) {
|
|
|
711
622
|
return this
|
|
712
623
|
}
|
|
713
624
|
|
|
714
|
-
function defaultClientErrorHandler (err, socket) {
|
|
715
|
-
// In case of a connection reset, the socket has been destroyed and there is nothing that needs to be done.
|
|
716
|
-
// https://nodejs.org/api/http.html#http_event_clienterror
|
|
717
|
-
if (err.code === 'ECONNRESET' || socket.destroyed) {
|
|
718
|
-
return
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
let body, errorCode, errorStatus, errorLabel
|
|
722
|
-
|
|
723
|
-
if (err.code === 'ERR_HTTP_REQUEST_TIMEOUT') {
|
|
724
|
-
errorCode = '408'
|
|
725
|
-
errorStatus = http.STATUS_CODES[errorCode]
|
|
726
|
-
body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}`
|
|
727
|
-
errorLabel = 'timeout'
|
|
728
|
-
} else if (err.code === 'HPE_HEADER_OVERFLOW') {
|
|
729
|
-
errorCode = '431'
|
|
730
|
-
errorStatus = http.STATUS_CODES[errorCode]
|
|
731
|
-
body = `{"error":"${errorStatus}","message":"Exceeded maximum allowed HTTP header size","statusCode":431}`
|
|
732
|
-
errorLabel = 'header_overflow'
|
|
733
|
-
} else {
|
|
734
|
-
errorCode = '400'
|
|
735
|
-
errorStatus = http.STATUS_CODES[errorCode]
|
|
736
|
-
body = `{"error":"${errorStatus}","message":"Client Error","statusCode":400}`
|
|
737
|
-
errorLabel = 'error'
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
// Most devs do not know what to do with this error.
|
|
741
|
-
// In the vast majority of cases, it's a network error and/or some
|
|
742
|
-
// config issue on the load balancer side.
|
|
743
|
-
this.log.trace({ err }, `client ${errorLabel}`)
|
|
744
|
-
// Copying standard node behavior
|
|
745
|
-
// https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666
|
|
746
|
-
|
|
747
|
-
// If the socket is not writable, there is no reason to try to send data.
|
|
748
|
-
if (socket.writable) {
|
|
749
|
-
socket.write(`HTTP/1.1 ${errorCode} ${errorStatus}\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
|
|
750
|
-
}
|
|
751
|
-
socket.destroy(err)
|
|
752
|
-
}
|
|
753
|
-
|
|
754
625
|
// If the router does not match any route, every request will land here
|
|
755
626
|
// req and res are Node.js core objects
|
|
756
627
|
function defaultRoute (req, res) {
|
|
@@ -765,9 +636,9 @@ function fastify (options) {
|
|
|
765
636
|
}
|
|
766
637
|
|
|
767
638
|
function onBadUrl (path, req, res) {
|
|
768
|
-
if (frameworkErrors) {
|
|
639
|
+
if (options.frameworkErrors) {
|
|
769
640
|
const id = getGenReqId(onBadUrlContext.server, req)
|
|
770
|
-
const childLogger = createChildLogger(onBadUrlContext, logger, req, id)
|
|
641
|
+
const childLogger = createChildLogger(onBadUrlContext, options.logger, req, id)
|
|
771
642
|
|
|
772
643
|
const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
|
|
773
644
|
const reply = new Reply(res, request, childLogger)
|
|
@@ -776,7 +647,7 @@ function fastify (options) {
|
|
|
776
647
|
childLogger.info({ req: request }, 'incoming request')
|
|
777
648
|
}
|
|
778
649
|
|
|
779
|
-
return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
|
|
650
|
+
return options.frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
|
|
780
651
|
}
|
|
781
652
|
const body = `{"error":"Bad Request","code":"FST_ERR_BAD_URL","message":"'${path}' is not a valid url component","statusCode":400}`
|
|
782
653
|
res.writeHead(400, {
|
|
@@ -790,9 +661,9 @@ function fastify (options) {
|
|
|
790
661
|
if (isAsync === false) return undefined
|
|
791
662
|
return function onAsyncConstraintError (err) {
|
|
792
663
|
if (err) {
|
|
793
|
-
if (frameworkErrors) {
|
|
664
|
+
if (options.frameworkErrors) {
|
|
794
665
|
const id = getGenReqId(onBadUrlContext.server, req)
|
|
795
|
-
const childLogger = createChildLogger(onBadUrlContext, logger, req, id)
|
|
666
|
+
const childLogger = createChildLogger(onBadUrlContext, options.logger, req, id)
|
|
796
667
|
|
|
797
668
|
const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
|
|
798
669
|
const reply = new Reply(res, request, childLogger)
|
|
@@ -801,7 +672,7 @@ function fastify (options) {
|
|
|
801
672
|
childLogger.info({ req: request }, 'incoming request')
|
|
802
673
|
}
|
|
803
674
|
|
|
804
|
-
return frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)
|
|
675
|
+
return options.frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)
|
|
805
676
|
}
|
|
806
677
|
const body = '{"error":"Internal Server Error","message":"Unexpected error from async constraint","statusCode":500}'
|
|
807
678
|
res.writeHead(500, {
|
|
@@ -842,7 +713,10 @@ function fastify (options) {
|
|
|
842
713
|
function setSchemaController (schemaControllerOpts) {
|
|
843
714
|
throwIfAlreadyStarted('Cannot call "setSchemaController"!')
|
|
844
715
|
const old = this[kSchemaController]
|
|
845
|
-
const schemaController = SchemaController.buildSchemaController(
|
|
716
|
+
const schemaController = SchemaController.buildSchemaController(
|
|
717
|
+
old,
|
|
718
|
+
Object.assign({}, old.opts, schemaControllerOpts)
|
|
719
|
+
)
|
|
846
720
|
this[kSchemaController] = schemaController
|
|
847
721
|
this.getSchema = schemaController.getSchema.bind(schemaController)
|
|
848
722
|
this.getSchemas = schemaController.getSchemas.bind(schemaController)
|
|
@@ -884,7 +758,9 @@ function fastify (options) {
|
|
|
884
758
|
|
|
885
759
|
function printRoutes (opts = {}) {
|
|
886
760
|
// includeHooks:true - shortcut to include all supported hooks exported by fastify.Hooks
|
|
887
|
-
opts.includeMeta = opts.includeHooks
|
|
761
|
+
opts.includeMeta = opts.includeHooks
|
|
762
|
+
? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks
|
|
763
|
+
: opts.includeMeta
|
|
888
764
|
return router.printRoutes(opts)
|
|
889
765
|
}
|
|
890
766
|
|
|
@@ -938,6 +814,145 @@ function fastify (options) {
|
|
|
938
814
|
}
|
|
939
815
|
}
|
|
940
816
|
|
|
817
|
+
function processOptions (options, defaultRoute, onBadUrl) {
|
|
818
|
+
// Options validations
|
|
819
|
+
if (options && typeof options !== 'object') {
|
|
820
|
+
throw new FST_ERR_OPTIONS_NOT_OBJ()
|
|
821
|
+
} else {
|
|
822
|
+
// Shallow copy options object to prevent mutations outside of this function
|
|
823
|
+
options = Object.assign({}, options)
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (
|
|
827
|
+
(options.querystringParser && typeof options.querystringParser !== 'function') ||
|
|
828
|
+
(
|
|
829
|
+
options.routerOptions?.querystringParser &&
|
|
830
|
+
typeof options.routerOptions.querystringParser !== 'function'
|
|
831
|
+
)
|
|
832
|
+
) {
|
|
833
|
+
throw new FST_ERR_QSP_NOT_FN(typeof (options.querystringParser ?? options.routerOptions.querystringParser))
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') {
|
|
837
|
+
throw new FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN(typeof options.schemaController.bucket)
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
validateBodyLimitOption(options.bodyLimit)
|
|
841
|
+
|
|
842
|
+
const requestIdHeader = typeof options.requestIdHeader === 'string' && options.requestIdHeader.length !== 0 ? options.requestIdHeader.toLowerCase() : (options.requestIdHeader === true && 'request-id')
|
|
843
|
+
const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId)
|
|
844
|
+
const requestIdLogLabel = options.requestIdLogLabel || 'reqId'
|
|
845
|
+
options.bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit
|
|
846
|
+
const disableRequestLogging = options.disableRequestLogging || false
|
|
847
|
+
|
|
848
|
+
const ajvOptions = Object.assign({
|
|
849
|
+
customOptions: {},
|
|
850
|
+
plugins: []
|
|
851
|
+
}, options.ajv)
|
|
852
|
+
|
|
853
|
+
if (!ajvOptions.customOptions || Object.prototype.toString.call(ajvOptions.customOptions) !== '[object Object]') {
|
|
854
|
+
throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ(typeof ajvOptions.customOptions)
|
|
855
|
+
}
|
|
856
|
+
if (!ajvOptions.plugins || !Array.isArray(ajvOptions.plugins)) {
|
|
857
|
+
throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR(typeof ajvOptions.plugins)
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const { logger, hasLogger } = createLogger(options)
|
|
861
|
+
|
|
862
|
+
// Update the options with the fixed values
|
|
863
|
+
options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout
|
|
864
|
+
options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout
|
|
865
|
+
options.maxRequestsPerSocket = options.maxRequestsPerSocket || defaultInitOptions.maxRequestsPerSocket
|
|
866
|
+
options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout
|
|
867
|
+
options.logger = logger
|
|
868
|
+
options.requestIdHeader = requestIdHeader
|
|
869
|
+
options.requestIdLogLabel = requestIdLogLabel
|
|
870
|
+
options.disableRequestLogging = disableRequestLogging
|
|
871
|
+
options.ajv = ajvOptions
|
|
872
|
+
options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler
|
|
873
|
+
options.allowErrorHandlerOverride = options.allowErrorHandlerOverride ?? defaultInitOptions.allowErrorHandlerOverride
|
|
874
|
+
|
|
875
|
+
const initialConfig = getSecuredInitialConfig(options)
|
|
876
|
+
|
|
877
|
+
// exposeHeadRoutes have its default set from the validator
|
|
878
|
+
options.exposeHeadRoutes = initialConfig.exposeHeadRoutes
|
|
879
|
+
|
|
880
|
+
// we need to set this before calling createServer
|
|
881
|
+
options.http2SessionTimeout = initialConfig.http2SessionTimeout
|
|
882
|
+
|
|
883
|
+
options.routerOptions = buildRouterOptions(options, {
|
|
884
|
+
defaultRoute,
|
|
885
|
+
onBadUrl,
|
|
886
|
+
ignoreTrailingSlash: defaultInitOptions.ignoreTrailingSlash,
|
|
887
|
+
ignoreDuplicateSlashes: defaultInitOptions.ignoreDuplicateSlashes,
|
|
888
|
+
maxParamLength: defaultInitOptions.maxParamLength,
|
|
889
|
+
allowUnsafeRegex: defaultInitOptions.allowUnsafeRegex,
|
|
890
|
+
buildPrettyMeta: defaultBuildPrettyMeta,
|
|
891
|
+
useSemicolonDelimiter: defaultInitOptions.useSemicolonDelimiter
|
|
892
|
+
})
|
|
893
|
+
|
|
894
|
+
return {
|
|
895
|
+
options,
|
|
896
|
+
genReqId,
|
|
897
|
+
disableRequestLogging,
|
|
898
|
+
hasLogger,
|
|
899
|
+
initialConfig
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function defaultBuildPrettyMeta (route) {
|
|
904
|
+
// return a shallow copy of route's sanitized context
|
|
905
|
+
|
|
906
|
+
const cleanKeys = {}
|
|
907
|
+
const allowedProps = ['errorHandler', 'logLevel', 'logSerializers']
|
|
908
|
+
|
|
909
|
+
allowedProps.concat(supportedHooks).forEach(k => {
|
|
910
|
+
cleanKeys[k] = route.store[k]
|
|
911
|
+
})
|
|
912
|
+
|
|
913
|
+
return Object.assign({}, cleanKeys)
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function defaultClientErrorHandler (err, socket) {
|
|
917
|
+
// In case of a connection reset, the socket has been destroyed and there is nothing that needs to be done.
|
|
918
|
+
// https://nodejs.org/api/http.html#http_event_clienterror
|
|
919
|
+
if (err.code === 'ECONNRESET' || socket.destroyed) {
|
|
920
|
+
return
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
let body, errorCode, errorStatus, errorLabel
|
|
924
|
+
|
|
925
|
+
if (err.code === 'ERR_HTTP_REQUEST_TIMEOUT') {
|
|
926
|
+
errorCode = '408'
|
|
927
|
+
errorStatus = http.STATUS_CODES[errorCode]
|
|
928
|
+
body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}`
|
|
929
|
+
errorLabel = 'timeout'
|
|
930
|
+
} else if (err.code === 'HPE_HEADER_OVERFLOW') {
|
|
931
|
+
errorCode = '431'
|
|
932
|
+
errorStatus = http.STATUS_CODES[errorCode]
|
|
933
|
+
body = `{"error":"${errorStatus}","message":"Exceeded maximum allowed HTTP header size","statusCode":431}`
|
|
934
|
+
errorLabel = 'header_overflow'
|
|
935
|
+
} else {
|
|
936
|
+
errorCode = '400'
|
|
937
|
+
errorStatus = http.STATUS_CODES[errorCode]
|
|
938
|
+
body = `{"error":"${errorStatus}","message":"Client Error","statusCode":400}`
|
|
939
|
+
errorLabel = 'error'
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Most devs do not know what to do with this error.
|
|
943
|
+
// In the vast majority of cases, it's a network error and/or some
|
|
944
|
+
// config issue on the load balancer side.
|
|
945
|
+
this.log.trace({ err }, `client ${errorLabel}`)
|
|
946
|
+
// Copying standard node behavior
|
|
947
|
+
// https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666
|
|
948
|
+
|
|
949
|
+
// If the socket is not writable, there is no reason to try to send data.
|
|
950
|
+
if (socket.writable) {
|
|
951
|
+
socket.write(`HTTP/1.1 ${errorCode} ${errorStatus}\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
|
|
952
|
+
}
|
|
953
|
+
socket.destroy(err)
|
|
954
|
+
}
|
|
955
|
+
|
|
941
956
|
function validateSchemaErrorFormatter (schemaErrorFormatter) {
|
|
942
957
|
if (typeof schemaErrorFormatter !== 'function') {
|
|
943
958
|
throw new FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN(typeof schemaErrorFormatter)
|
|
@@ -121,7 +121,8 @@ ContentTypeParser.prototype.getParser = function (contentType) {
|
|
|
121
121
|
(
|
|
122
122
|
caseInsensitiveContentType.length === parserListItem.length ||
|
|
123
123
|
caseInsensitiveContentType.charCodeAt(parserListItem.length) === 59 /* `;` */ ||
|
|
124
|
-
caseInsensitiveContentType.charCodeAt(parserListItem.length) === 32 /* ` ` */
|
|
124
|
+
caseInsensitiveContentType.charCodeAt(parserListItem.length) === 32 /* ` ` */ ||
|
|
125
|
+
caseInsensitiveContentType.charCodeAt(parserListItem.length) === 9 /* `\t` */
|
|
125
126
|
)
|
|
126
127
|
) {
|
|
127
128
|
parser = this.customParsers.get(parserListItem)
|
package/lib/error-handler.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const statusCodes = require('node:http').STATUS_CODES
|
|
4
|
-
const wrapThenable = require('./
|
|
4
|
+
const wrapThenable = require('./wrap-thenable.js')
|
|
5
5
|
const {
|
|
6
6
|
kReplyHeaders,
|
|
7
7
|
kReplyNextErrorHandler,
|
|
@@ -82,7 +82,7 @@ function handleError (reply, error, cb) {
|
|
|
82
82
|
function defaultErrorHandler (error, request, reply) {
|
|
83
83
|
setErrorHeaders(error, reply)
|
|
84
84
|
if (!reply[kReplyHasStatusCode] || reply.statusCode === 200) {
|
|
85
|
-
const statusCode = error.statusCode || error.status
|
|
85
|
+
const statusCode = error && (error.statusCode || error.status)
|
|
86
86
|
reply.code(statusCode >= 400 ? statusCode : 500)
|
|
87
87
|
}
|
|
88
88
|
if (reply.statusCode < 500) {
|
|
@@ -19,7 +19,7 @@ const {
|
|
|
19
19
|
FST_ERR_NOT_FOUND
|
|
20
20
|
} = require('./errors')
|
|
21
21
|
const { createChildLogger } = require('./logger-factory')
|
|
22
|
-
const { getGenReqId } = require('./
|
|
22
|
+
const { getGenReqId } = require('./req-id-gen-factory.js')
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Each fastify instance have a:
|
|
@@ -150,7 +150,9 @@ function fourOhFour (options) {
|
|
|
150
150
|
.map(h => h.bind(this))
|
|
151
151
|
context[hook] = toSet.length ? toSet : null
|
|
152
152
|
}
|
|
153
|
-
context.errorHandler = opts.errorHandler
|
|
153
|
+
context.errorHandler = opts.errorHandler
|
|
154
|
+
? buildErrorHandler(this[kErrorHandler], opts.errorHandler)
|
|
155
|
+
: this[kErrorHandler]
|
|
154
156
|
})
|
|
155
157
|
|
|
156
158
|
if (this[kFourOhFourContext] !== null && prefix === '/') {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const diagnostics = require('node:diagnostics_channel')
|
|
4
4
|
const { validate: validateSchema } = require('./validation')
|
|
5
5
|
const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks')
|
|
6
|
-
const wrapThenable = require('./
|
|
6
|
+
const wrapThenable = require('./wrap-thenable')
|
|
7
7
|
const {
|
|
8
8
|
kReplyIsError,
|
|
9
9
|
kRouteContext,
|
|
@@ -7,6 +7,7 @@ function headRouteOnSendHandler (req, reply, payload, done) {
|
|
|
7
7
|
return
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
// node:stream
|
|
10
11
|
if (typeof payload.resume === 'function') {
|
|
11
12
|
payload.on('error', (err) => {
|
|
12
13
|
reply.log.error({ err }, 'Error on Stream found for HEAD route')
|
|
@@ -16,6 +17,15 @@ function headRouteOnSendHandler (req, reply, payload, done) {
|
|
|
16
17
|
return
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
// node:stream/web
|
|
21
|
+
if (typeof payload.getReader === 'function') {
|
|
22
|
+
payload.cancel('Stream cancelled by HEAD route').catch((err) => {
|
|
23
|
+
reply.log.error({ err }, 'Error on Stream found for HEAD route')
|
|
24
|
+
})
|
|
25
|
+
done(null, null)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
const size = '' + Buffer.byteLength(payload)
|
|
20
30
|
|
|
21
31
|
reply.header('content-length', size)
|
|
@@ -25,7 +35,9 @@ function headRouteOnSendHandler (req, reply, payload, done) {
|
|
|
25
35
|
|
|
26
36
|
function parseHeadOnSendHandlers (onSendHandlers) {
|
|
27
37
|
if (onSendHandlers == null) return headRouteOnSendHandler
|
|
28
|
-
return Array.isArray(onSendHandlers)
|
|
38
|
+
return Array.isArray(onSendHandlers)
|
|
39
|
+
? [...onSendHandlers, headRouteOnSendHandler]
|
|
40
|
+
: [onSendHandlers, headRouteOnSendHandler]
|
|
29
41
|
}
|
|
30
42
|
|
|
31
43
|
module.exports = {
|
|
@@ -19,9 +19,9 @@ const {
|
|
|
19
19
|
const Reply = require('./reply')
|
|
20
20
|
const Request = require('./request')
|
|
21
21
|
const SchemaController = require('./schema-controller')
|
|
22
|
-
const ContentTypeParser = require('./
|
|
22
|
+
const ContentTypeParser = require('./content-type-parser.js')
|
|
23
23
|
const { buildHooks } = require('./hooks')
|
|
24
|
-
const pluginUtils = require('./
|
|
24
|
+
const pluginUtils = require('./plugin-utils.js')
|
|
25
25
|
|
|
26
26
|
// Function that runs the encapsulation magic.
|
|
27
27
|
// Everything that need to be encapsulated must be handled in this function.
|
|
@@ -6,12 +6,12 @@ const kRegisteredPlugins = Symbol.for('registered-plugin')
|
|
|
6
6
|
const {
|
|
7
7
|
kTestInternals
|
|
8
8
|
} = require('./symbols.js')
|
|
9
|
-
const { exist, existReply, existRequest } = require('./decorate')
|
|
9
|
+
const { exist, existReply, existRequest } = require('./decorate.js')
|
|
10
10
|
const {
|
|
11
11
|
FST_ERR_PLUGIN_VERSION_MISMATCH,
|
|
12
12
|
FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE,
|
|
13
13
|
FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER
|
|
14
|
-
} = require('./errors')
|
|
14
|
+
} = require('./errors.js')
|
|
15
15
|
|
|
16
16
|
const rcRegex = /-(?:rc|pre|alpha).+$/u
|
|
17
17
|
|
package/lib/reply.js
CHANGED
|
@@ -31,7 +31,7 @@ const {
|
|
|
31
31
|
preSerializationHookRunner
|
|
32
32
|
} = require('./hooks')
|
|
33
33
|
|
|
34
|
-
const internals = require('./
|
|
34
|
+
const internals = require('./handle-request.js')[Symbol.for('internals')]
|
|
35
35
|
const loggerUtils = require('./logger-factory')
|
|
36
36
|
const now = loggerUtils.now
|
|
37
37
|
const { handleError } = require('./error-handler')
|
|
@@ -166,7 +166,9 @@ Reply.prototype.send = function (payload) {
|
|
|
166
166
|
if (!hasContentType) {
|
|
167
167
|
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.OCTET
|
|
168
168
|
}
|
|
169
|
-
const payloadToSend = Buffer.isBuffer(payload)
|
|
169
|
+
const payloadToSend = Buffer.isBuffer(payload)
|
|
170
|
+
? payload
|
|
171
|
+
: Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength)
|
|
170
172
|
onSendHook(this, payloadToSend)
|
|
171
173
|
return this
|
|
172
174
|
}
|
|
@@ -681,7 +683,7 @@ function logStreamError (logger, err, res) {
|
|
|
681
683
|
|
|
682
684
|
function sendWebStream (payload, res, reply) {
|
|
683
685
|
if (payload.locked) {
|
|
684
|
-
throw FST_ERR_REP_READABLE_STREAM_LOCKED()
|
|
686
|
+
throw new FST_ERR_REP_READABLE_STREAM_LOCKED()
|
|
685
687
|
}
|
|
686
688
|
const nodeStream = Readable.fromWeb(payload)
|
|
687
689
|
sendStream(nodeStream, res, reply)
|
package/lib/request.js
CHANGED
|
@@ -226,6 +226,11 @@ Object.defineProperties(Request.prototype, {
|
|
|
226
226
|
},
|
|
227
227
|
hostname: {
|
|
228
228
|
get () {
|
|
229
|
+
// Check for IPV6 Host
|
|
230
|
+
if (this.host[0] === '[') {
|
|
231
|
+
return this.host.slice(0, this.host.indexOf(']') + 1)
|
|
232
|
+
}
|
|
233
|
+
|
|
229
234
|
return this.host.split(':', 1)[0]
|
|
230
235
|
}
|
|
231
236
|
},
|