fastify 5.8.4 → 5.9.0
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/SECURITY.md +1 -1
- package/SPONSORS.md +6 -4
- package/docs/Guides/Database.md +0 -28
- package/docs/Guides/Ecosystem.md +17 -2
- package/docs/Guides/Serverless.md +2 -2
- package/docs/Guides/Write-Plugin.md +1 -1
- package/docs/Reference/Encapsulation.md +27 -26
- package/docs/Reference/Errors.md +10 -4
- package/docs/Reference/HTTP2.md +10 -10
- package/docs/Reference/Hooks.md +4 -4
- package/docs/Reference/Index.md +14 -16
- package/docs/Reference/LTS.md +12 -13
- package/docs/Reference/Lifecycle.md +9 -8
- package/docs/Reference/Logging.md +44 -39
- package/docs/Reference/Middleware.md +21 -25
- package/docs/Reference/Principles.md +2 -2
- package/docs/Reference/Reply.md +6 -1
- package/docs/Reference/Request.md +27 -16
- package/docs/Reference/Routes.md +5 -2
- package/docs/Reference/Server.md +31 -3
- package/docs/Reference/Type-Providers.md +29 -5
- package/docs/Reference/Validation-and-Serialization.md +15 -2
- package/docs/Reference/Warnings.md +7 -6
- package/eslint.config.js +7 -2
- package/fastify.d.ts +8 -3
- package/fastify.js +43 -14
- package/lib/content-type-parser.js +13 -1
- package/lib/decorate.js +11 -3
- package/lib/error-handler.js +4 -3
- package/lib/error-serializer.js +59 -59
- package/lib/errors.js +16 -1
- package/lib/four-oh-four.js +14 -9
- package/lib/handle-request.js +11 -5
- package/lib/plugin-override.js +2 -1
- package/lib/plugin-utils.js +5 -5
- package/lib/reply.js +63 -8
- package/lib/request.js +23 -19
- package/lib/route.js +20 -6
- package/lib/schema-controller.js +1 -1
- package/lib/schemas.js +37 -30
- package/lib/symbols.js +3 -1
- package/lib/validation.js +1 -13
- package/lib/warnings.js +3 -3
- package/package.json +16 -18
- package/scripts/validate-ecosystem-links.js +1 -0
- package/test/bundler/esbuild/package.json +1 -1
- package/test/close-pipelining.test.js +1 -2
- package/test/custom-http-server.test.js +38 -0
- package/test/decorator-instance-properties.test.js +63 -0
- package/test/diagnostics-channel/async-error-handler.test.js +74 -0
- package/test/hooks.test.js +23 -0
- package/test/http-methods/get.test.js +1 -1
- package/test/http2/plain.test.js +135 -0
- package/test/http2/secure-with-fallback.test.js +1 -1
- package/test/https/https.test.js +1 -2
- package/test/internals/errors.test.js +31 -1
- package/test/internals/plugin.test.js +3 -1
- package/test/internals/request.test.js +27 -3
- package/test/internals/schema-controller-perf.test.js +33 -0
- package/test/logger/logging.test.js +18 -1
- package/test/logger/options.test.js +38 -1
- package/test/reply-error.test.js +1 -1
- package/test/reply-trailers.test.js +70 -0
- package/test/request-media-type.test.js +105 -0
- package/test/request-port.test.js +72 -0
- package/test/route-prefix.test.js +34 -0
- package/test/router-options.test.js +222 -11
- package/test/schema-serialization.test.js +108 -0
- package/test/schema-validation.test.js +45 -0
- package/test/scripts/validate-ecosystem-links.test.js +40 -57
- package/test/throw.test.js +14 -0
- package/test/trust-proxy.test.js +74 -0
- package/test/types/content-type-parser.tst.ts +70 -0
- package/test/types/{decorate-request-reply.test-d.ts → decorate-request-reply.tst.ts} +4 -4
- package/test/types/{dummy-plugin.ts → dummy-plugin.mts} +1 -1
- package/test/types/errors.tst.ts +91 -0
- package/test/types/fastify.tst.ts +351 -0
- package/test/types/hooks.tst.ts +578 -0
- package/test/types/instance.tst.ts +597 -0
- package/test/types/{logger.test-d.ts → logger.tst.ts} +61 -62
- package/test/types/plugin.tst.ts +96 -0
- package/test/types/register.tst.ts +245 -0
- package/test/types/reply.tst.ts +297 -0
- package/test/types/request.tst.ts +199 -0
- package/test/types/route.tst.ts +576 -0
- package/test/types/{schema.test-d.ts → schema.tst.ts} +22 -22
- package/test/types/{serverFactory.test-d.ts → serverFactory.tst.ts} +4 -4
- package/test/types/tsconfig.json +9 -0
- package/test/types/{type-provider.test-d.ts → type-provider.tst.ts} +256 -250
- package/test/types/using.tst.ts +14 -0
- package/types/errors.d.ts +3 -0
- package/types/request.d.ts +23 -2
- package/AGENTS.md +0 -290
- package/test/types/content-type-parser.test-d.ts +0 -72
- package/test/types/errors.test-d.ts +0 -90
- package/test/types/fastify.test-d.ts +0 -352
- package/test/types/hooks.test-d.ts +0 -550
- package/test/types/import.ts +0 -2
- package/test/types/instance.test-d.ts +0 -588
- package/test/types/plugin.test-d.ts +0 -97
- package/test/types/register.test-d.ts +0 -237
- package/test/types/reply.test-d.ts +0 -254
- package/test/types/request.test-d.ts +0 -188
- package/test/types/route.test-d.ts +0 -553
- package/test/types/using.test-d.ts +0 -17
package/lib/reply.js
CHANGED
|
@@ -60,6 +60,7 @@ const {
|
|
|
60
60
|
const decorators = require('./decorate')
|
|
61
61
|
|
|
62
62
|
const toString = Object.prototype.toString
|
|
63
|
+
const HTTP2_WRITE_CHUNK_SIZE = 64 * 1024
|
|
63
64
|
|
|
64
65
|
function Reply (res, request, log) {
|
|
65
66
|
this.raw = res
|
|
@@ -75,6 +76,7 @@ function Reply (res, request, log) {
|
|
|
75
76
|
this.log = log
|
|
76
77
|
}
|
|
77
78
|
Reply.props = []
|
|
79
|
+
Reply.instanceProperties = new Set(['raw', 'request', 'log'])
|
|
78
80
|
|
|
79
81
|
Object.defineProperties(Reply.prototype, {
|
|
80
82
|
[kRouteContext]: {
|
|
@@ -167,7 +169,7 @@ Reply.prototype.send = function (payload) {
|
|
|
167
169
|
// node:stream/web
|
|
168
170
|
typeof payload.getReader === 'function' ||
|
|
169
171
|
// Response
|
|
170
|
-
toString.call(payload) === '[object Response]'
|
|
172
|
+
(typeof payload === 'object' && toString.call(payload) === '[object Response]')
|
|
171
173
|
) {
|
|
172
174
|
onSendHook(this, payload)
|
|
173
175
|
return this
|
|
@@ -593,7 +595,7 @@ function onSendEnd (reply, payload) {
|
|
|
593
595
|
// since Response contain status code, headers and body,
|
|
594
596
|
// we need to update the status, add the headers and use it's body as payload
|
|
595
597
|
// before continuing
|
|
596
|
-
if (toString.call(payload) === '[object Response]') {
|
|
598
|
+
if (payload != null && typeof payload === 'object' && toString.call(payload) === '[object Response]') {
|
|
597
599
|
// https://developer.mozilla.org/en-US/docs/Web/API/Response/status
|
|
598
600
|
if (typeof payload.status === 'number') {
|
|
599
601
|
reply.code(payload.status)
|
|
@@ -677,9 +679,52 @@ function onSendEnd (reply, payload) {
|
|
|
677
679
|
|
|
678
680
|
safeWriteHead(reply, statusCode)
|
|
679
681
|
// write payload first
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
682
|
+
writePayload(payload, res, reply)
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function isHttp2Reply (reply) {
|
|
686
|
+
return reply.request.raw?.httpVersionMajor === 2
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function writePayload (payload, res, reply) {
|
|
690
|
+
if (!isHttp2Reply(reply) || Buffer.byteLength(payload) <= HTTP2_WRITE_CHUNK_SIZE) {
|
|
691
|
+
res.write(payload)
|
|
692
|
+
// then send trailers
|
|
693
|
+
sendTrailer(payload, res, reply)
|
|
694
|
+
return
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
writeHttp2Payload(payload, res, () => {
|
|
698
|
+
// then send trailers
|
|
699
|
+
sendTrailer(payload, res, reply)
|
|
700
|
+
})
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function writeHttp2Payload (payload, res, done) {
|
|
704
|
+
const buffer = Buffer.isBuffer(payload) ? payload : Buffer.from(payload)
|
|
705
|
+
let offset = 0
|
|
706
|
+
|
|
707
|
+
function writeChunk () {
|
|
708
|
+
while (offset < buffer.length) {
|
|
709
|
+
/* c8 ignore next 3 - defensive guard for aborted responses */
|
|
710
|
+
if (res.destroyed || res.writableEnded) {
|
|
711
|
+
return
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const end = Math.min(offset + HTTP2_WRITE_CHUNK_SIZE, buffer.length)
|
|
715
|
+
const shouldContinue = res.write(buffer.subarray(offset, end))
|
|
716
|
+
offset = end
|
|
717
|
+
|
|
718
|
+
if (shouldContinue === false) {
|
|
719
|
+
res.once('drain', writeChunk)
|
|
720
|
+
return
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
done()
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
writeChunk()
|
|
683
728
|
}
|
|
684
729
|
|
|
685
730
|
function logStreamError (logger, err, res) {
|
|
@@ -830,10 +875,12 @@ function sendTrailer (payload, res, reply) {
|
|
|
830
875
|
const trailers = {}
|
|
831
876
|
let handled = 0
|
|
832
877
|
let skipped = true
|
|
878
|
+
let sent = false
|
|
833
879
|
function send () {
|
|
834
880
|
// add trailers when all handler handled
|
|
835
881
|
/* istanbul ignore else */
|
|
836
|
-
if (handled === 0) {
|
|
882
|
+
if (handled === 0 && !sent) {
|
|
883
|
+
sent = true
|
|
837
884
|
res.addTrailers(trailers)
|
|
838
885
|
// we need to properly close the stream
|
|
839
886
|
// after trailers sent
|
|
@@ -846,9 +893,10 @@ function sendTrailer (payload, res, reply) {
|
|
|
846
893
|
skipped = false
|
|
847
894
|
handled--
|
|
848
895
|
|
|
896
|
+
let cbAlreadyCalled = false
|
|
849
897
|
function cb (err, value) {
|
|
850
|
-
|
|
851
|
-
|
|
898
|
+
if (cbAlreadyCalled) return
|
|
899
|
+
cbAlreadyCalled = true
|
|
852
900
|
handled++
|
|
853
901
|
|
|
854
902
|
// we can safely ignore error for trailer
|
|
@@ -914,6 +962,13 @@ function setupResponseListeners (reply) {
|
|
|
914
962
|
}
|
|
915
963
|
}
|
|
916
964
|
|
|
965
|
+
// Fix: release socket._meta so request/reply objects are not retained
|
|
966
|
+
// past the response on keep-alive connections.
|
|
967
|
+
const socket = reply.request.raw.socket
|
|
968
|
+
if (socket && socket._meta && socket._meta.request === reply.request) {
|
|
969
|
+
socket._meta = null
|
|
970
|
+
}
|
|
971
|
+
|
|
917
972
|
if (ctx && ctx.onResponse !== null) {
|
|
918
973
|
onResponseHookRunner(
|
|
919
974
|
ctx.onResponse,
|
package/lib/request.js
CHANGED
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
kSchemaController,
|
|
11
11
|
kOptions,
|
|
12
12
|
kRequestCacheValidateFns,
|
|
13
|
+
kRequestContentType,
|
|
13
14
|
kRouteContext,
|
|
14
15
|
kRequestOriginalUrl,
|
|
15
16
|
kRequestSignal,
|
|
@@ -36,6 +37,7 @@ function Request (id, params, req, query, log, context) {
|
|
|
36
37
|
this.body = undefined
|
|
37
38
|
}
|
|
38
39
|
Request.props = []
|
|
40
|
+
Request.instanceProperties = new Set(['id', 'params', 'raw', 'query', 'log', 'body'])
|
|
39
41
|
|
|
40
42
|
function getTrustProxyFn (tp) {
|
|
41
43
|
if (typeof tp === 'function') {
|
|
@@ -47,16 +49,14 @@ function getTrustProxyFn (tp) {
|
|
|
47
49
|
}
|
|
48
50
|
if (typeof tp === 'number') {
|
|
49
51
|
// Support trusting hop count
|
|
50
|
-
return function (a, i) { return
|
|
52
|
+
return function (a, i) { return i < tp }
|
|
51
53
|
}
|
|
52
54
|
if (typeof tp === 'string') {
|
|
53
55
|
// Support comma-separated tps
|
|
54
56
|
const values = tp.split(',').map(it => it.trim())
|
|
55
|
-
|
|
56
|
-
return function (a, i) { return a != null && trust(a, i) }
|
|
57
|
+
return proxyAddr.compile(values)
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
-
return function (a, i) { return a != null && trust(a, i) }
|
|
59
|
+
return proxyAddr.compile(tp)
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
function buildRequest (R, trustProxy) {
|
|
@@ -119,7 +119,8 @@ function buildRequestWithTrustProxy (R, trustProxy) {
|
|
|
119
119
|
},
|
|
120
120
|
host: {
|
|
121
121
|
get () {
|
|
122
|
-
|
|
122
|
+
const socket = this.raw.socket
|
|
123
|
+
if (this.headers['x-forwarded-host'] && socket != null && proxyFn(socket.remoteAddress, 0)) {
|
|
123
124
|
return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-host'])
|
|
124
125
|
}
|
|
125
126
|
/**
|
|
@@ -133,7 +134,8 @@ function buildRequestWithTrustProxy (R, trustProxy) {
|
|
|
133
134
|
},
|
|
134
135
|
protocol: {
|
|
135
136
|
get () {
|
|
136
|
-
|
|
137
|
+
const socket = this.raw.socket
|
|
138
|
+
if (this.headers['x-forwarded-proto'] && socket != null && proxyFn(socket.remoteAddress, 0)) {
|
|
137
139
|
return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-proto'])
|
|
138
140
|
}
|
|
139
141
|
if (this.socket) {
|
|
@@ -255,19 +257,13 @@ Object.defineProperties(Request.prototype, {
|
|
|
255
257
|
},
|
|
256
258
|
port: {
|
|
257
259
|
get () {
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
const portReg = /(?<port>:\d+)$/
|
|
261
|
+
const host = this.headers.host ?? this.headers[':authority'] ?? ''
|
|
262
|
+
const matches = portReg.exec(host)
|
|
263
|
+
if (matches === null || matches[1] === undefined) {
|
|
264
|
+
return null
|
|
262
265
|
}
|
|
263
|
-
|
|
264
|
-
const host = (this.headers.host ?? this.headers[':authority'] ?? '')
|
|
265
|
-
const portFromHeader = parseInt(host.split(':').slice(-1)[0])
|
|
266
|
-
if (!isNaN(portFromHeader)) {
|
|
267
|
-
return portFromHeader
|
|
268
|
-
}
|
|
269
|
-
// fall back to null
|
|
270
|
-
return null
|
|
266
|
+
return parseInt(matches.groups.port.slice(1), 10)
|
|
271
267
|
}
|
|
272
268
|
},
|
|
273
269
|
protocol: {
|
|
@@ -288,6 +284,14 @@ Object.defineProperties(Request.prototype, {
|
|
|
288
284
|
this.additionalHeaders = headers
|
|
289
285
|
}
|
|
290
286
|
},
|
|
287
|
+
mediaType: {
|
|
288
|
+
get () {
|
|
289
|
+
if (!this[kRequestContentType] && this.headers['content-type'] !== undefined) {
|
|
290
|
+
this[kRequestContentType] = this[kRouteContext].contentTypeParser.getContentType(this.headers['content-type'])
|
|
291
|
+
}
|
|
292
|
+
return this[kRequestContentType]?.mediaType
|
|
293
|
+
}
|
|
294
|
+
},
|
|
291
295
|
getValidationFunction: {
|
|
292
296
|
value: function (httpPartOrSchema) {
|
|
293
297
|
if (typeof httpPartOrSchema === 'string') {
|
package/lib/route.js
CHANGED
|
@@ -24,6 +24,7 @@ const {
|
|
|
24
24
|
FST_ERR_ROUTE_MISSING_HANDLER,
|
|
25
25
|
FST_ERR_ROUTE_METHOD_NOT_SUPPORTED,
|
|
26
26
|
FST_ERR_ROUTE_METHOD_INVALID,
|
|
27
|
+
FST_ERR_ROUTE_LOG_LEVEL_INVALID,
|
|
27
28
|
FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED,
|
|
28
29
|
FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT,
|
|
29
30
|
FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT,
|
|
@@ -68,6 +69,7 @@ const routerKeys = [
|
|
|
68
69
|
'ignoreTrailingSlash',
|
|
69
70
|
'maxParamLength',
|
|
70
71
|
'onBadUrl',
|
|
72
|
+
'onMaxParamLength',
|
|
71
73
|
'querystringParser',
|
|
72
74
|
'useSemicolonDelimiter'
|
|
73
75
|
]
|
|
@@ -284,6 +286,7 @@ function buildRouting (options) {
|
|
|
284
286
|
opts.routePath = path
|
|
285
287
|
opts.prefix = prefix
|
|
286
288
|
opts.logLevel = opts.logLevel || this[kLogLevel]
|
|
289
|
+
validateLogLevelOption(opts.logLevel, opts.method, opts.url, logger)
|
|
287
290
|
|
|
288
291
|
if (this[kLogSerializers] || opts.logSerializers) {
|
|
289
292
|
opts.logSerializers = Object.assign(Object.create(this[kLogSerializers]), opts.logSerializers)
|
|
@@ -365,7 +368,8 @@ function buildRouting (options) {
|
|
|
365
368
|
// any route insertion error created by fastify can be safely ignore
|
|
366
369
|
// because it only duplicate route for head
|
|
367
370
|
if (!context[kRouteByFastify]) {
|
|
368
|
-
const
|
|
371
|
+
const methods = Array.isArray(opts.method) ? opts.method : [opts.method]
|
|
372
|
+
const isDuplicatedRoute = methods.some(method => error.message.includes(`Method '${method}' already declared for route`))
|
|
369
373
|
if (isDuplicatedRoute) {
|
|
370
374
|
throw new FST_ERR_DUPLICATED_ROUTE(opts.method, opts.url)
|
|
371
375
|
}
|
|
@@ -476,12 +480,13 @@ function buildRouting (options) {
|
|
|
476
480
|
res.setHeader('Connection', 'close')
|
|
477
481
|
}
|
|
478
482
|
|
|
479
|
-
//
|
|
480
|
-
|
|
483
|
+
// Load-shedding fast path during drain. server.close() and
|
|
484
|
+
// closeIdleConnections() only reap idle sockets; requests already
|
|
485
|
+
// pipelined or arriving on an active keep-alive connection still reach
|
|
486
|
+
// this point with closing === true. Short-circuiting with 503 avoids
|
|
487
|
+
// running the full handler chain (and any downstream calls) for work
|
|
488
|
+
// the load balancer should redirect elsewhere.
|
|
481
489
|
if (return503OnClosing) {
|
|
482
|
-
// On Node v19 we cannot test this behavior as it won't be necessary
|
|
483
|
-
// anymore. It will close all the idle connections before they reach this
|
|
484
|
-
// stage.
|
|
485
490
|
const headers = {
|
|
486
491
|
'Content-Type': 'application/json',
|
|
487
492
|
'Content-Length': '80'
|
|
@@ -635,6 +640,15 @@ function validateHandlerTimeoutOption (handlerTimeout) {
|
|
|
635
640
|
}
|
|
636
641
|
}
|
|
637
642
|
|
|
643
|
+
function validateLogLevelOption (logLevel, method, path, logger) {
|
|
644
|
+
if (logLevel == null || logLevel === '') return
|
|
645
|
+
if (logger?.levels?.values == null) return
|
|
646
|
+
|
|
647
|
+
if (typeof logLevel !== 'string' || logger.levels.values[logLevel] === undefined) {
|
|
648
|
+
throw new FST_ERR_ROUTE_LOG_LEVEL_INVALID(method, path, logLevel)
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
638
652
|
function runPreParsing (err, request, reply) {
|
|
639
653
|
if (reply.sent === true) return
|
|
640
654
|
if (err != null) {
|
package/lib/schema-controller.js
CHANGED
|
@@ -31,7 +31,7 @@ function buildSchemaController (parentSchemaCtrl, opts) {
|
|
|
31
31
|
bucket: (opts && opts.bucket) || buildSchemas,
|
|
32
32
|
compilersFactory,
|
|
33
33
|
isCustomValidatorCompiler: typeof opts?.compilersFactory?.buildValidator === 'function',
|
|
34
|
-
isCustomSerializerCompiler: typeof opts?.compilersFactory?.
|
|
34
|
+
isCustomSerializerCompiler: typeof opts?.compilersFactory?.buildSerializer === 'function'
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
return new SchemaController(undefined, option)
|
package/lib/schemas.js
CHANGED
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
FST_ERR_SCH_DUPLICATE,
|
|
11
11
|
FST_ERR_SCH_CONTENT_MISSING_SCHEMA
|
|
12
12
|
} = require('./errors')
|
|
13
|
+
const ContentType = require('./content-type')
|
|
13
14
|
|
|
14
15
|
const SCHEMAS_SOURCE = ['params', 'body', 'querystring', 'query', 'headers']
|
|
15
16
|
|
|
@@ -147,52 +148,58 @@ function getSchemaSerializer (context, statusCode, contentType) {
|
|
|
147
148
|
return false
|
|
148
149
|
}
|
|
149
150
|
if (responseSchemaDef[statusCode]) {
|
|
150
|
-
if (responseSchemaDef[statusCode].constructor === Object
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
if (responseSchemaDef[statusCode].constructor === Object) {
|
|
152
|
+
const ct = new ContentType(contentType)
|
|
153
|
+
if (ct.isValid) {
|
|
154
|
+
if (responseSchemaDef[statusCode][ct.mediaType]) {
|
|
155
|
+
return responseSchemaDef[statusCode][ct.mediaType]
|
|
156
|
+
}
|
|
155
157
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
// fallback to match all media-type
|
|
159
|
+
if (responseSchemaDef[statusCode]['*/*']) {
|
|
160
|
+
return responseSchemaDef[statusCode]['*/*']
|
|
161
|
+
}
|
|
160
162
|
|
|
161
|
-
|
|
163
|
+
return false
|
|
164
|
+
}
|
|
162
165
|
}
|
|
163
166
|
return responseSchemaDef[statusCode]
|
|
164
167
|
}
|
|
165
168
|
const fallbackStatusCode = (statusCode + '')[0] + 'xx'
|
|
166
169
|
if (responseSchemaDef[fallbackStatusCode]) {
|
|
167
|
-
if (responseSchemaDef[fallbackStatusCode].constructor === Object
|
|
168
|
-
const
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
if (responseSchemaDef[fallbackStatusCode].constructor === Object) {
|
|
171
|
+
const ct = new ContentType(contentType)
|
|
172
|
+
if (ct.isValid) {
|
|
173
|
+
if (responseSchemaDef[fallbackStatusCode][ct.mediaType]) {
|
|
174
|
+
return responseSchemaDef[fallbackStatusCode][ct.mediaType]
|
|
175
|
+
}
|
|
172
176
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
+
// fallback to match all media-type
|
|
178
|
+
if (responseSchemaDef[fallbackStatusCode]['*/*']) {
|
|
179
|
+
return responseSchemaDef[fallbackStatusCode]['*/*']
|
|
180
|
+
}
|
|
177
181
|
|
|
178
|
-
|
|
182
|
+
return false
|
|
183
|
+
}
|
|
179
184
|
}
|
|
180
185
|
|
|
181
186
|
return responseSchemaDef[fallbackStatusCode]
|
|
182
187
|
}
|
|
183
188
|
if (responseSchemaDef.default) {
|
|
184
|
-
if (responseSchemaDef.default.constructor === Object
|
|
185
|
-
const
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
+
if (responseSchemaDef.default.constructor === Object) {
|
|
190
|
+
const ct = new ContentType(contentType)
|
|
191
|
+
if (ct.isValid) {
|
|
192
|
+
if (responseSchemaDef.default[ct.mediaType]) {
|
|
193
|
+
return responseSchemaDef.default[ct.mediaType]
|
|
194
|
+
}
|
|
189
195
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
196
|
+
// fallback to match all media-type
|
|
197
|
+
if (responseSchemaDef.default['*/*']) {
|
|
198
|
+
return responseSchemaDef.default['*/*']
|
|
199
|
+
}
|
|
194
200
|
|
|
195
|
-
|
|
201
|
+
return false
|
|
202
|
+
}
|
|
196
203
|
}
|
|
197
204
|
|
|
198
205
|
return responseSchemaDef.default
|
package/lib/symbols.js
CHANGED
|
@@ -32,6 +32,7 @@ const keys = {
|
|
|
32
32
|
kRequestPayloadStream: Symbol('fastify.RequestPayloadStream'),
|
|
33
33
|
kRequestAcceptVersion: Symbol('fastify.RequestAcceptVersion'),
|
|
34
34
|
kRequestCacheValidateFns: Symbol('fastify.request.cache.validateFns'),
|
|
35
|
+
kRequestContentType: Symbol('fastify.request.contentType'),
|
|
35
36
|
kRequestOriginalUrl: Symbol('fastify.request.originalUrl'),
|
|
36
37
|
kRequestSignal: Symbol('fastify.request.signal'),
|
|
37
38
|
kHandlerTimeout: Symbol('fastify.handlerTimeout'),
|
|
@@ -65,7 +66,8 @@ const keys = {
|
|
|
65
66
|
kChildLoggerFactory: Symbol('fastify.childLoggerFactory'),
|
|
66
67
|
kHasBeenDecorated: Symbol('fastify.hasBeenDecorated'),
|
|
67
68
|
kKeepAliveConnections: Symbol('fastify.keepAliveConnections'),
|
|
68
|
-
kRouteByFastify: Symbol('fastify.routeByFastify')
|
|
69
|
+
kRouteByFastify: Symbol('fastify.routeByFastify'),
|
|
70
|
+
kDiagnosticsStore: Symbol('fastify.diagnosticsStore')
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
module.exports = keys
|
package/lib/validation.js
CHANGED
|
@@ -162,9 +162,7 @@ function validate (context, request, execution) {
|
|
|
162
162
|
if (typeof context[bodySchema] === 'function') {
|
|
163
163
|
validatorFunction = context[bodySchema]
|
|
164
164
|
} else if (context[bodySchema]) {
|
|
165
|
-
|
|
166
|
-
const contentType = getEssenceMediaType(request.headers['content-type'])
|
|
167
|
-
const contentSchema = context[bodySchema][contentType]
|
|
165
|
+
const contentSchema = context[bodySchema][request.mediaType]
|
|
168
166
|
if (contentSchema) {
|
|
169
167
|
validatorFunction = contentSchema
|
|
170
168
|
}
|
|
@@ -262,16 +260,6 @@ function wrapValidationError (result, dataVar, schemaErrorFormatter) {
|
|
|
262
260
|
return error
|
|
263
261
|
}
|
|
264
262
|
|
|
265
|
-
/**
|
|
266
|
-
* simple function to retrieve the essence media type
|
|
267
|
-
* @param {string} header
|
|
268
|
-
* @returns {string} Mimetype string.
|
|
269
|
-
*/
|
|
270
|
-
function getEssenceMediaType (header) {
|
|
271
|
-
if (!header) return ''
|
|
272
|
-
return header.split(/[ ;]/, 1)[0].trim().toLowerCase()
|
|
273
|
-
}
|
|
274
|
-
|
|
275
263
|
module.exports = {
|
|
276
264
|
symbols: { bodySchema, querystringSchema, responseSchema, paramsSchema, headersSchema },
|
|
277
265
|
compileSchemasForValidation,
|
package/lib/warnings.js
CHANGED
|
@@ -8,9 +8,9 @@ const { createWarning } = require('process-warning')
|
|
|
8
8
|
* - FSTSEC001
|
|
9
9
|
* - FSTDEP022
|
|
10
10
|
*
|
|
11
|
-
* Deprecation Codes FSTDEP001 - FSTDEP021 were used by v4 and MUST NOT
|
|
11
|
+
* Deprecation Codes FSTDEP001 - FSTDEP021 were used by v4 and MUST NOT be reused.
|
|
12
12
|
* - FSTDEP022 is used by v5 and MUST NOT be reused.
|
|
13
|
-
* Warning Codes FSTWRN001 - FSTWRN002 were used by v4 and MUST NOT
|
|
13
|
+
* Warning Codes FSTWRN001 - FSTWRN002 were used by v4 and MUST NOT be reused.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const FSTWRN001 = createWarning({
|
|
@@ -30,7 +30,7 @@ const FSTWRN003 = createWarning({
|
|
|
30
30
|
const FSTWRN004 = createWarning({
|
|
31
31
|
name: 'FastifyWarning',
|
|
32
32
|
code: 'FSTWRN004',
|
|
33
|
-
message: 'It seems that you are overriding an errorHandler in the same scope, which can lead to subtle bugs.',
|
|
33
|
+
message: 'It seems that you are overriding an errorHandler in the same scope, which can lead to subtle bugs. To disable this behavior, set \'allowErrorHandlerOverride\' to false. For more information, visit: https://fastify.dev/docs/latest/Reference/Server/#allowerrorhandleroverride',
|
|
34
34
|
unlimited: true
|
|
35
35
|
})
|
|
36
36
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.0",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
"lint:markdown": "markdownlint-cli2",
|
|
20
20
|
"lint:eslint": "eslint",
|
|
21
21
|
"prepublishOnly": "cross-env PREPUBLISH=true borp --reporter=@jsumners/line-reporter && npm run test:validator:integrity && npm run build:sync-version",
|
|
22
|
-
"test": "npm run lint && npm run unit && npm run test:
|
|
23
|
-
"test:ci": "npm run unit && npm run test:
|
|
24
|
-
"test:report": "npm run lint && npm run unit:report && npm run test:
|
|
22
|
+
"test": "npm run lint && npm run unit && npm run test:types",
|
|
23
|
+
"test:ci": "npm run unit && npm run test:types",
|
|
24
|
+
"test:report": "npm run lint && npm run unit:report && npm run test:types",
|
|
25
25
|
"test:validator:integrity": "npm run build:validation && git diff --quiet --ignore-all-space --ignore-blank-lines --ignore-cr-at-eol lib/error-serializer.js && git diff --quiet --ignore-all-space --ignore-blank-lines --ignore-cr-at-eol lib/config-validator.js",
|
|
26
|
-
"test:
|
|
26
|
+
"test:types": "tstyche",
|
|
27
27
|
"test:watch": "npm run unit -- --watch --coverage-report=none --reporter=terse",
|
|
28
28
|
"unit": "borp",
|
|
29
29
|
"unit:report": "c8 --reporter html borp --reporter=@jsumners/line-reporter",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"contributors": [
|
|
46
46
|
{
|
|
47
47
|
"name": "Tomas Della Vedova",
|
|
48
|
-
"url": "
|
|
48
|
+
"url": "https://delvedor.dev",
|
|
49
49
|
"author": true
|
|
50
50
|
},
|
|
51
51
|
{
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
"name": "Dustin Deus",
|
|
62
|
-
"url": "
|
|
62
|
+
"url": "https://starptech.com",
|
|
63
63
|
"email": "deusdustin@gmail.com"
|
|
64
64
|
},
|
|
65
65
|
{
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
"name": "Trivikram Kamat",
|
|
77
|
-
"url": "
|
|
77
|
+
"url": "https://trivikr.github.io",
|
|
78
78
|
"email": "trivikr.dev@gmail.com"
|
|
79
79
|
},
|
|
80
80
|
{
|
|
@@ -177,27 +177,28 @@
|
|
|
177
177
|
"ajv-i18n": "^4.2.0",
|
|
178
178
|
"ajv-merge-patch": "^5.0.1",
|
|
179
179
|
"autocannon": "^8.0.0",
|
|
180
|
-
"borp": "^0.
|
|
180
|
+
"borp": "^1.0.0",
|
|
181
181
|
"branch-comparer": "^1.1.0",
|
|
182
|
-
"concurrently": "^
|
|
182
|
+
"concurrently": "^10.0.0",
|
|
183
183
|
"cross-env": "^10.0.0",
|
|
184
184
|
"eslint": "^9.0.0",
|
|
185
185
|
"fast-json-body": "^1.1.0",
|
|
186
|
-
"fastify-plugin": "^
|
|
186
|
+
"fastify-plugin": "^6.0.0",
|
|
187
|
+
"fastify-tsconfig": "^3.0.0",
|
|
187
188
|
"fluent-json-schema": "^6.0.0",
|
|
188
189
|
"h2url": "^0.2.0",
|
|
189
190
|
"http-errors": "^2.0.0",
|
|
190
191
|
"joi": "^18.0.1",
|
|
191
192
|
"json-schema-to-ts": "^3.0.1",
|
|
192
193
|
"JSONStream": "^1.3.5",
|
|
193
|
-
"markdownlint-cli2": "^0.
|
|
194
|
+
"markdownlint-cli2": "^0.22.0",
|
|
194
195
|
"neostandard": "^0.12.0",
|
|
195
196
|
"node-forge": "^1.3.1",
|
|
196
197
|
"proxyquire": "^2.1.3",
|
|
197
198
|
"split2": "^4.2.0",
|
|
198
|
-
"
|
|
199
|
+
"tstyche": "^7.0.0",
|
|
199
200
|
"typebox": "^1.0.81",
|
|
200
|
-
"typescript": "~
|
|
201
|
+
"typescript": "~6.0.2",
|
|
201
202
|
"undici": "^7.11.0",
|
|
202
203
|
"vary": "^1.1.2",
|
|
203
204
|
"yup": "^1.4.0"
|
|
@@ -210,7 +211,7 @@
|
|
|
210
211
|
"abstract-logging": "^2.0.1",
|
|
211
212
|
"avvio": "^9.0.0",
|
|
212
213
|
"fast-json-stringify": "^6.0.0",
|
|
213
|
-
"find-my-way": "^9.
|
|
214
|
+
"find-my-way": "^9.6.0",
|
|
214
215
|
"light-my-request": "^6.0.0",
|
|
215
216
|
"pino": "^9.14.0 || ^10.1.0",
|
|
216
217
|
"process-warning": "^5.0.0",
|
|
@@ -218,8 +219,5 @@
|
|
|
218
219
|
"secure-json-parse": "^4.0.0",
|
|
219
220
|
"semver": "^7.6.0",
|
|
220
221
|
"toad-cache": "^3.7.0"
|
|
221
|
-
},
|
|
222
|
-
"tsd": {
|
|
223
|
-
"directory": "test/types"
|
|
224
222
|
}
|
|
225
223
|
}
|
|
@@ -41,8 +41,7 @@ test('Should return 503 while closing - pipelining', async t => {
|
|
|
41
41
|
})
|
|
42
42
|
|
|
43
43
|
test('Should close the socket abruptly - pipelining - return503OnClosing: false', async t => {
|
|
44
|
-
//
|
|
45
|
-
// therefore our socket will be closed
|
|
44
|
+
// Node.js will always invoke server.closeIdleConnections() therefore our socket will be closed
|
|
46
45
|
const fastify = Fastify({
|
|
47
46
|
return503OnClosing: false,
|
|
48
47
|
forceCloseConnections: false
|
|
@@ -63,6 +63,44 @@ async function setup () {
|
|
|
63
63
|
)
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
+
test('Should not make an extra closeIdleConnections call for native servers', async t => {
|
|
67
|
+
const fastify = Fastify({
|
|
68
|
+
forceCloseConnections: 'idle'
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
await fastify.listen({ port: 0 })
|
|
72
|
+
|
|
73
|
+
let called = 0
|
|
74
|
+
fastify.server.closeIdleConnections = function () {
|
|
75
|
+
called++
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await fastify.close()
|
|
79
|
+
|
|
80
|
+
t.assert.strictEqual(called, 1)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('Should preserve the extra closeIdleConnections call for custom servers', async t => {
|
|
84
|
+
let called = 0
|
|
85
|
+
const fastify = Fastify({
|
|
86
|
+
forceCloseConnections: 'idle',
|
|
87
|
+
serverFactory (handler) {
|
|
88
|
+
const server = http.createServer(handler)
|
|
89
|
+
const originalCloseIdleConnections = server.closeIdleConnections.bind(server)
|
|
90
|
+
server.closeIdleConnections = function () {
|
|
91
|
+
called++
|
|
92
|
+
return originalCloseIdleConnections()
|
|
93
|
+
}
|
|
94
|
+
return server
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
await fastify.listen({ port: 0 })
|
|
99
|
+
await fastify.close()
|
|
100
|
+
|
|
101
|
+
t.assert.strictEqual(called, 2)
|
|
102
|
+
})
|
|
103
|
+
|
|
66
104
|
test('Should accept user defined serverFactory and ignore secondary server creation', async t => {
|
|
67
105
|
const server = http.createServer(() => { })
|
|
68
106
|
t.after(() => new Promise(resolve => server.close(resolve)))
|