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.
Files changed (74) hide show
  1. package/.vscode/settings.json +22 -0
  2. package/SECURITY.md +12 -0
  3. package/SPONSORS.md +1 -0
  4. package/build/build-validation.js +1 -1
  5. package/build/sync-version.js +1 -0
  6. package/docs/Guides/Ecosystem.md +5 -0
  7. package/docs/Guides/Fluent-Schema.md +2 -2
  8. package/docs/Reference/Decorators.md +36 -169
  9. package/docs/Reference/Encapsulation.md +5 -1
  10. package/docs/Reference/Plugins.md +11 -2
  11. package/docs/Reference/Reply.md +4 -1
  12. package/docs/Reference/Server.md +39 -1
  13. package/docs/Reference/Type-Providers.md +2 -2
  14. package/docs/Reference/TypeScript.md +136 -0
  15. package/eslint.config.js +18 -2
  16. package/fastify.d.ts +1 -1
  17. package/fastify.js +183 -168
  18. package/lib/{contentTypeParser.js → content-type-parser.js} +2 -1
  19. package/lib/error-handler.js +2 -2
  20. package/lib/{fourOhFour.js → four-oh-four.js} +4 -2
  21. package/lib/{handleRequest.js → handle-request.js} +1 -1
  22. package/lib/{headRoute.js → head-route.js} +13 -1
  23. package/lib/{initialConfigValidation.js → initial-config-validation.js} +1 -1
  24. package/lib/{pluginOverride.js → plugin-override.js} +2 -2
  25. package/lib/{pluginUtils.js → plugin-utils.js} +2 -2
  26. package/lib/reply.js +5 -3
  27. package/lib/request.js +5 -0
  28. package/lib/route.js +20 -9
  29. package/lib/server.js +94 -8
  30. package/lib/symbols.js +1 -0
  31. package/lib/validation.js +9 -1
  32. package/lib/warnings.js +1 -1
  33. package/package.json +8 -8
  34. package/test/500s.test.js +191 -0
  35. package/test/child-logger-factory.test.js +3 -3
  36. package/test/content-parser.test.js +2 -1
  37. package/test/decorator-namespace.test._js_ +1 -1
  38. package/test/diagnostics-channel/error-before-handler.test.js +1 -1
  39. package/test/http2/closing.test.js +88 -0
  40. package/test/internals/content-type-parser.test.js +2 -2
  41. package/test/internals/handle-request.test.js +2 -2
  42. package/test/internals/initial-config.test.js +1 -1
  43. package/test/internals/plugin.test.js +2 -2
  44. package/test/internals/reply.test.js +22 -3
  45. package/test/internals/req-id-gen-factory.test.js +1 -1
  46. package/test/promises.test.js +3 -3
  47. package/test/reply-web-stream-locked.test.js +37 -0
  48. package/test/request-error.test.js +116 -0
  49. package/test/route.6.test.js +20 -1
  50. package/test/route.7.test.js +49 -0
  51. package/test/schema-validation.test.js +27 -4
  52. package/test/server.test.js +22 -4
  53. package/test/set-error-handler.test.js +1 -1
  54. package/test/skip-reply-send.test.js +2 -2
  55. package/test/stream.5.test.js +3 -3
  56. package/test/types/fastify.test-d.ts +70 -18
  57. package/test/types/hooks.test-d.ts +6 -1
  58. package/test/types/instance.test-d.ts +35 -15
  59. package/test/types/logger.test-d.ts +18 -6
  60. package/test/types/plugin.test-d.ts +24 -6
  61. package/test/types/register.test-d.ts +108 -33
  62. package/test/types/reply.test-d.ts +23 -6
  63. package/test/types/request.test-d.ts +25 -6
  64. package/test/types/route.test-d.ts +10 -1
  65. package/test/types/schema.test-d.ts +21 -0
  66. package/test/validation-error-handling.test.js +68 -1
  67. package/test/wrap-thenable.test.js +1 -1
  68. package/types/instance.d.ts +2 -2
  69. package/types/schema.d.ts +1 -1
  70. package/test/check.test.js +0 -219
  71. /package/lib/{configValidator.js → config-validator.js} +0 -0
  72. /package/lib/{reqIdGenFactory.js → req-id-gen-factory.js} +0 -0
  73. /package/lib/{wrapThenable.js → wrap-thenable.js} +0 -0
  74. /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.0'
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/contentTypeParser')
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/pluginUtils')
48
- const { getGenReqId, reqIdGenFactory } = require('./lib/reqIdGenFactory')
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/fourOhFour')
51
- const getSecuredInitialConfig = require('./lib/initialConfigValidation')
52
- const override = require('./lib/pluginOverride')
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 (options) {
103
- // Options validations
104
- if (options && typeof options !== 'object') {
105
- throw new FST_ERR_OPTIONS_NOT_OBJ()
106
- } else {
107
- // Shallow copy options object to prevent mutations outside of this function
108
- options = Object.assign({}, options)
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
- // we need to set this before calling createServer
192
- options.http2SessionTimeout = initialConfig.http2SessionTimeout
193
- const { server, listen } = createServer(options, httpHandler)
194
-
195
- const serverHasCloseAllConnections = typeof server.closeAllConnections === 'function'
196
- const serverHasCloseIdleConnections = typeof server.closeIdleConnections === 'function'
197
-
198
- let forceCloseConnections = options.forceCloseConnections
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(old, Object.assign({}, old.opts, schemaControllerOpts))
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 ? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks : opts.includeMeta
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)
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const statusCodes = require('node:http').STATUS_CODES
4
- const wrapThenable = require('./wrapThenable')
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('./reqIdGenFactory.js')
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 ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) : this[kErrorHandler]
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('./wrapThenable')
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) ? [...onSendHandlers, headRouteOnSendHandler] : [onSendHandlers, headRouteOnSendHandler]
38
+ return Array.isArray(onSendHandlers)
39
+ ? [...onSendHandlers, headRouteOnSendHandler]
40
+ : [onSendHandlers, headRouteOnSendHandler]
29
41
  }
30
42
 
31
43
  module.exports = {
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const validate = require('./configValidator')
3
+ const validate = require('./config-validator')
4
4
  const deepClone = require('rfdc')({ circles: true, proto: false })
5
5
  const { FST_ERR_INIT_OPTS_INVALID } = require('./errors')
6
6
 
@@ -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('./contentTypeParser')
22
+ const ContentTypeParser = require('./content-type-parser.js')
23
23
  const { buildHooks } = require('./hooks')
24
- const pluginUtils = require('./pluginUtils')
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('./handleRequest')[Symbol.for('internals')]
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) ? payload : Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength)
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
  },