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.
Files changed (61) hide show
  1. package/build/build-validation.js +1 -1
  2. package/build/sync-version.js +1 -0
  3. package/docs/Guides/Ecosystem.md +5 -0
  4. package/docs/Guides/Fluent-Schema.md +2 -2
  5. package/docs/Reference/Encapsulation.md +5 -1
  6. package/docs/Reference/Plugins.md +11 -2
  7. package/docs/Reference/Reply.md +3 -0
  8. package/docs/Reference/Server.md +32 -0
  9. package/docs/Reference/Type-Providers.md +2 -2
  10. package/eslint.config.js +18 -2
  11. package/fastify.d.ts +1 -1
  12. package/fastify.js +179 -169
  13. package/lib/{contentTypeParser.js → content-type-parser.js} +2 -1
  14. package/lib/error-handler.js +2 -2
  15. package/lib/{fourOhFour.js → four-oh-four.js} +4 -2
  16. package/lib/{handleRequest.js → handle-request.js} +1 -1
  17. package/lib/{headRoute.js → head-route.js} +13 -1
  18. package/lib/{initialConfigValidation.js → initial-config-validation.js} +1 -1
  19. package/lib/{pluginOverride.js → plugin-override.js} +2 -2
  20. package/lib/{pluginUtils.js → plugin-utils.js} +2 -2
  21. package/lib/reply.js +5 -3
  22. package/lib/request.js +5 -0
  23. package/lib/route.js +20 -9
  24. package/lib/server.js +38 -5
  25. package/lib/validation.js +9 -1
  26. package/package.json +8 -8
  27. package/test/500s.test.js +191 -0
  28. package/test/child-logger-factory.test.js +3 -3
  29. package/test/content-parser.test.js +2 -1
  30. package/test/diagnostics-channel/error-before-handler.test.js +1 -1
  31. package/test/internals/content-type-parser.test.js +2 -2
  32. package/test/internals/handle-request.test.js +2 -2
  33. package/test/internals/initial-config.test.js +1 -1
  34. package/test/internals/plugin.test.js +2 -2
  35. package/test/internals/reply.test.js +22 -3
  36. package/test/internals/req-id-gen-factory.test.js +1 -1
  37. package/test/promises.test.js +3 -3
  38. package/test/reply-web-stream-locked.test.js +37 -0
  39. package/test/request-error.test.js +116 -0
  40. package/test/route.6.test.js +20 -1
  41. package/test/route.7.test.js +49 -0
  42. package/test/schema-validation.test.js +27 -4
  43. package/test/server.test.js +22 -4
  44. package/test/stream.5.test.js +3 -3
  45. package/test/types/fastify.test-d.ts +70 -18
  46. package/test/types/hooks.test-d.ts +6 -1
  47. package/test/types/instance.test-d.ts +35 -15
  48. package/test/types/logger.test-d.ts +18 -6
  49. package/test/types/plugin.test-d.ts +24 -6
  50. package/test/types/register.test-d.ts +108 -33
  51. package/test/types/reply.test-d.ts +23 -6
  52. package/test/types/request.test-d.ts +25 -6
  53. package/test/types/route.test-d.ts +10 -1
  54. package/test/validation-error-handling.test.js +68 -1
  55. package/test/wrap-thenable.test.js +1 -1
  56. package/types/instance.d.ts +2 -2
  57. package/test/check.test.js +0 -219
  58. /package/lib/{configValidator.js → config-validator.js} +0 -0
  59. /package/lib/{reqIdGenFactory.js → req-id-gen-factory.js} +0 -0
  60. /package/lib/{wrapThenable.js → wrap-thenable.js} +0 -0
  61. /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.1'
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,23 +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
- const serverHasCloseHttp2Sessions = typeof server.closeHttp2Sessions === 'function'
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(old, Object.assign({}, old.opts, schemaControllerOpts))
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 ? 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
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)
@@ -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
  },