fastify 5.6.1 → 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/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/Encapsulation.md +5 -1
- package/docs/Reference/Plugins.md +11 -2
- package/docs/Reference/Reply.md +3 -0
- package/docs/Reference/Server.md +32 -0
- package/docs/Reference/Type-Providers.md +2 -2
- package/eslint.config.js +18 -2
- package/fastify.d.ts +1 -1
- package/fastify.js +179 -169
- 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 +38 -5
- package/lib/validation.js +9 -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/diagnostics-channel/error-before-handler.test.js +1 -1
- 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/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/validation-error-handling.test.js +68 -1
- package/test/wrap-thenable.test.js +1 -1
- package/types/instance.d.ts +2 -2
- 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,23 +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
|
-
let forceCloseConnections = options.forceCloseConnections
|
|
200
|
-
if (forceCloseConnections === 'idle' && !serverHasCloseIdleConnections) {
|
|
201
|
-
throw new FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE()
|
|
202
|
-
} else if (typeof forceCloseConnections !== 'boolean') {
|
|
203
|
-
/* istanbul ignore next: only one branch can be valid in a given Node.js version */
|
|
204
|
-
forceCloseConnections = serverHasCloseIdleConnections ? 'idle' : false
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
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)
|
|
208
115
|
|
|
209
116
|
const setupResponseListeners = Reply.setupResponseListeners
|
|
210
117
|
const schemaController = SchemaController.buildSchemaController(null, options.schemaController)
|
|
@@ -241,7 +148,7 @@ function fastify (options) {
|
|
|
241
148
|
[kOptions]: options,
|
|
242
149
|
[kChildren]: [],
|
|
243
150
|
[kServerBindings]: [],
|
|
244
|
-
[kBodyLimit]: bodyLimit,
|
|
151
|
+
[kBodyLimit]: options.bodyLimit,
|
|
245
152
|
[kRoutePrefix]: '',
|
|
246
153
|
[kLogLevel]: '',
|
|
247
154
|
[kLogSerializers]: null,
|
|
@@ -250,10 +157,10 @@ function fastify (options) {
|
|
|
250
157
|
[kSchemaErrorFormatter]: null,
|
|
251
158
|
[kErrorHandler]: buildErrorHandler(),
|
|
252
159
|
[kErrorHandlerAlreadySet]: false,
|
|
253
|
-
[kChildLoggerFactory]: defaultChildLoggerFactory,
|
|
160
|
+
[kChildLoggerFactory]: options.childLoggerFactory || defaultChildLoggerFactory,
|
|
254
161
|
[kReplySerializerDefault]: null,
|
|
255
162
|
[kContentTypeParser]: new ContentTypeParser(
|
|
256
|
-
bodyLimit,
|
|
163
|
+
options.bodyLimit,
|
|
257
164
|
(options.onProtoPoisoning || defaultInitOptions.onProtoPoisoning),
|
|
258
165
|
(options.onConstructorPoisoning || defaultInitOptions.onConstructorPoisoning)
|
|
259
166
|
),
|
|
@@ -307,7 +214,7 @@ function fastify (options) {
|
|
|
307
214
|
return router.findRoute(options)
|
|
308
215
|
},
|
|
309
216
|
// expose logger instance
|
|
310
|
-
log: logger,
|
|
217
|
+
log: options.logger,
|
|
311
218
|
// type provider
|
|
312
219
|
withTypeProvider,
|
|
313
220
|
// hooks
|
|
@@ -529,7 +436,6 @@ function fastify (options) {
|
|
|
529
436
|
router.setup(options, {
|
|
530
437
|
avvio,
|
|
531
438
|
fourOhFour,
|
|
532
|
-
logger,
|
|
533
439
|
hasLogger,
|
|
534
440
|
setupResponseListeners,
|
|
535
441
|
throwIfAlreadyStarted,
|
|
@@ -716,46 +622,6 @@ function fastify (options) {
|
|
|
716
622
|
return this
|
|
717
623
|
}
|
|
718
624
|
|
|
719
|
-
function defaultClientErrorHandler (err, socket) {
|
|
720
|
-
// In case of a connection reset, the socket has been destroyed and there is nothing that needs to be done.
|
|
721
|
-
// https://nodejs.org/api/http.html#http_event_clienterror
|
|
722
|
-
if (err.code === 'ECONNRESET' || socket.destroyed) {
|
|
723
|
-
return
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
let body, errorCode, errorStatus, errorLabel
|
|
727
|
-
|
|
728
|
-
if (err.code === 'ERR_HTTP_REQUEST_TIMEOUT') {
|
|
729
|
-
errorCode = '408'
|
|
730
|
-
errorStatus = http.STATUS_CODES[errorCode]
|
|
731
|
-
body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}`
|
|
732
|
-
errorLabel = 'timeout'
|
|
733
|
-
} else if (err.code === 'HPE_HEADER_OVERFLOW') {
|
|
734
|
-
errorCode = '431'
|
|
735
|
-
errorStatus = http.STATUS_CODES[errorCode]
|
|
736
|
-
body = `{"error":"${errorStatus}","message":"Exceeded maximum allowed HTTP header size","statusCode":431}`
|
|
737
|
-
errorLabel = 'header_overflow'
|
|
738
|
-
} else {
|
|
739
|
-
errorCode = '400'
|
|
740
|
-
errorStatus = http.STATUS_CODES[errorCode]
|
|
741
|
-
body = `{"error":"${errorStatus}","message":"Client Error","statusCode":400}`
|
|
742
|
-
errorLabel = 'error'
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// Most devs do not know what to do with this error.
|
|
746
|
-
// In the vast majority of cases, it's a network error and/or some
|
|
747
|
-
// config issue on the load balancer side.
|
|
748
|
-
this.log.trace({ err }, `client ${errorLabel}`)
|
|
749
|
-
// Copying standard node behavior
|
|
750
|
-
// https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666
|
|
751
|
-
|
|
752
|
-
// If the socket is not writable, there is no reason to try to send data.
|
|
753
|
-
if (socket.writable) {
|
|
754
|
-
socket.write(`HTTP/1.1 ${errorCode} ${errorStatus}\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
|
|
755
|
-
}
|
|
756
|
-
socket.destroy(err)
|
|
757
|
-
}
|
|
758
|
-
|
|
759
625
|
// If the router does not match any route, every request will land here
|
|
760
626
|
// req and res are Node.js core objects
|
|
761
627
|
function defaultRoute (req, res) {
|
|
@@ -770,9 +636,9 @@ function fastify (options) {
|
|
|
770
636
|
}
|
|
771
637
|
|
|
772
638
|
function onBadUrl (path, req, res) {
|
|
773
|
-
if (frameworkErrors) {
|
|
639
|
+
if (options.frameworkErrors) {
|
|
774
640
|
const id = getGenReqId(onBadUrlContext.server, req)
|
|
775
|
-
const childLogger = createChildLogger(onBadUrlContext, logger, req, id)
|
|
641
|
+
const childLogger = createChildLogger(onBadUrlContext, options.logger, req, id)
|
|
776
642
|
|
|
777
643
|
const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
|
|
778
644
|
const reply = new Reply(res, request, childLogger)
|
|
@@ -781,7 +647,7 @@ function fastify (options) {
|
|
|
781
647
|
childLogger.info({ req: request }, 'incoming request')
|
|
782
648
|
}
|
|
783
649
|
|
|
784
|
-
return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
|
|
650
|
+
return options.frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
|
|
785
651
|
}
|
|
786
652
|
const body = `{"error":"Bad Request","code":"FST_ERR_BAD_URL","message":"'${path}' is not a valid url component","statusCode":400}`
|
|
787
653
|
res.writeHead(400, {
|
|
@@ -795,9 +661,9 @@ function fastify (options) {
|
|
|
795
661
|
if (isAsync === false) return undefined
|
|
796
662
|
return function onAsyncConstraintError (err) {
|
|
797
663
|
if (err) {
|
|
798
|
-
if (frameworkErrors) {
|
|
664
|
+
if (options.frameworkErrors) {
|
|
799
665
|
const id = getGenReqId(onBadUrlContext.server, req)
|
|
800
|
-
const childLogger = createChildLogger(onBadUrlContext, logger, req, id)
|
|
666
|
+
const childLogger = createChildLogger(onBadUrlContext, options.logger, req, id)
|
|
801
667
|
|
|
802
668
|
const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
|
|
803
669
|
const reply = new Reply(res, request, childLogger)
|
|
@@ -806,7 +672,7 @@ function fastify (options) {
|
|
|
806
672
|
childLogger.info({ req: request }, 'incoming request')
|
|
807
673
|
}
|
|
808
674
|
|
|
809
|
-
return frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)
|
|
675
|
+
return options.frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)
|
|
810
676
|
}
|
|
811
677
|
const body = '{"error":"Internal Server Error","message":"Unexpected error from async constraint","statusCode":500}'
|
|
812
678
|
res.writeHead(500, {
|
|
@@ -847,7 +713,10 @@ function fastify (options) {
|
|
|
847
713
|
function setSchemaController (schemaControllerOpts) {
|
|
848
714
|
throwIfAlreadyStarted('Cannot call "setSchemaController"!')
|
|
849
715
|
const old = this[kSchemaController]
|
|
850
|
-
const schemaController = SchemaController.buildSchemaController(
|
|
716
|
+
const schemaController = SchemaController.buildSchemaController(
|
|
717
|
+
old,
|
|
718
|
+
Object.assign({}, old.opts, schemaControllerOpts)
|
|
719
|
+
)
|
|
851
720
|
this[kSchemaController] = schemaController
|
|
852
721
|
this.getSchema = schemaController.getSchema.bind(schemaController)
|
|
853
722
|
this.getSchemas = schemaController.getSchemas.bind(schemaController)
|
|
@@ -889,7 +758,9 @@ function fastify (options) {
|
|
|
889
758
|
|
|
890
759
|
function printRoutes (opts = {}) {
|
|
891
760
|
// includeHooks:true - shortcut to include all supported hooks exported by fastify.Hooks
|
|
892
|
-
opts.includeMeta = opts.includeHooks
|
|
761
|
+
opts.includeMeta = opts.includeHooks
|
|
762
|
+
? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks
|
|
763
|
+
: opts.includeMeta
|
|
893
764
|
return router.printRoutes(opts)
|
|
894
765
|
}
|
|
895
766
|
|
|
@@ -943,6 +814,145 @@ function fastify (options) {
|
|
|
943
814
|
}
|
|
944
815
|
}
|
|
945
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
|
+
|
|
946
956
|
function validateSchemaErrorFormatter (schemaErrorFormatter) {
|
|
947
957
|
if (typeof schemaErrorFormatter !== 'function') {
|
|
948
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
|
},
|