fastify 5.9.0 → 5.11.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/PROJECT_CHARTER.md +1 -1
- package/README.md +7 -10
- package/SPONSORS.md +1 -0
- package/build/build-validation.js +2 -2
- package/docs/Guides/Delay-Accepting-Requests.md +1 -1
- package/docs/Guides/Ecosystem.md +12 -9
- package/docs/Guides/Getting-Started.md +10 -10
- package/docs/Guides/Migration-Guide-V4.md +2 -2
- package/docs/Guides/Migration-Guide-V5.md +1 -1
- package/docs/Guides/Plugins-Guide.md +1 -1
- package/docs/Guides/Prototype-Poisoning.md +3 -3
- package/docs/Guides/Serverless.md +6 -13
- package/docs/Guides/Style-Guide.md +1 -1
- package/docs/Reference/Errors.md +6 -0
- package/docs/Reference/Hooks.md +1 -1
- package/docs/Reference/Logging.md +3 -0
- package/docs/Reference/Reply.md +2 -2
- package/docs/Reference/Request.md +13 -13
- package/docs/Reference/Server.md +117 -17
- package/docs/Reference/Type-Providers.md +24 -4
- package/docs/Reference/TypeScript.md +8 -10
- package/docs/Reference/Warnings.md +5 -0
- package/eslint.config.js +1 -1
- package/fastify.d.ts +60 -13
- package/fastify.js +28 -23
- package/lib/content-type-parser.js +1 -11
- package/lib/content-type.js +70 -19
- package/lib/context.js +0 -3
- package/lib/error-handler.js +7 -26
- package/lib/errors.js +17 -0
- package/lib/four-oh-four.js +8 -10
- package/lib/handle-request.js +37 -8
- package/lib/hooks.js +5 -1
- package/lib/log-controller.js +169 -0
- package/lib/logger-factory.js +25 -4
- package/lib/reply.js +35 -44
- package/lib/req-id-gen-factory.js +4 -1
- package/lib/request.js +3 -3
- package/lib/route.js +28 -36
- package/lib/schemas.js +3 -3
- package/lib/symbols.js +1 -1
- package/lib/validation.js +10 -1
- package/lib/warnings.js +19 -1
- package/package.json +4 -4
- package/test/content-type.test.js +51 -4
- package/test/find-route.test.js +35 -0
- package/test/fix-6411.test.js +65 -0
- package/test/genReqId.test.js +24 -0
- package/test/hooks.test.js +71 -0
- package/test/internals/all.test.js +2 -1
- package/test/internals/errors.test.js +21 -1
- package/test/internals/logger.test.js +322 -0
- package/test/internals/reply.test.js +57 -4
- package/test/internals/request.test.js +10 -7
- package/test/logger/logging.test.js +40 -1
- package/test/reply-error.test.js +35 -0
- package/test/rfc-10008.test.js +147 -0
- package/test/route-shorthand.test.js +14 -4
- package/test/schema-serialization.test.js +33 -0
- package/test/stream.4.test.js +2 -2
- package/test/trust-proxy.test.js +49 -30
- package/test/types/errors.tst.ts +2 -0
- package/test/types/route.tst.ts +3 -3
- package/types/content-type-parser.d.ts +26 -6
- package/types/errors.d.ts +3 -0
- package/types/hooks.d.ts +137 -76
- package/types/instance.d.ts +152 -47
- package/types/logger.d.ts +57 -2
- package/types/plugin.d.ts +7 -3
- package/types/register.d.ts +53 -10
- package/types/reply.d.ts +62 -14
- package/types/route.d.ts +68 -34
- package/types/type-provider.d.ts +29 -8
- package/types/utils.d.ts +2 -2
package/fastify.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const VERSION = '5.
|
|
3
|
+
const VERSION = '5.11.0'
|
|
4
4
|
|
|
5
5
|
const Avvio = require('avvio')
|
|
6
6
|
const http = require('node:http')
|
|
@@ -33,7 +33,8 @@ const {
|
|
|
33
33
|
kChildLoggerFactory,
|
|
34
34
|
kGenReqId,
|
|
35
35
|
kErrorHandlerAlreadySet,
|
|
36
|
-
kHandlerTimeout
|
|
36
|
+
kHandlerTimeout,
|
|
37
|
+
kLogController
|
|
37
38
|
} = require('./lib/symbols.js')
|
|
38
39
|
|
|
39
40
|
const { createServer } = require('./lib/server')
|
|
@@ -44,7 +45,7 @@ const decorator = require('./lib/decorate')
|
|
|
44
45
|
const ContentTypeParser = require('./lib/content-type-parser.js')
|
|
45
46
|
const SchemaController = require('./lib/schema-controller')
|
|
46
47
|
const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks')
|
|
47
|
-
const { createChildLogger, defaultChildLoggerFactory, createLogger } = require('./lib/logger-factory')
|
|
48
|
+
const { createChildLogger, defaultChildLoggerFactory, createLogger, createLogController, LogController } = require('./lib/logger-factory')
|
|
48
49
|
const pluginUtils = require('./lib/plugin-utils.js')
|
|
49
50
|
const { getGenReqId, reqIdGenFactory } = require('./lib/req-id-gen-factory.js')
|
|
50
51
|
const { buildRouting, validateBodyLimitOption, buildRouterOptions } = require('./lib/route')
|
|
@@ -79,7 +80,7 @@ const {
|
|
|
79
80
|
} = errorCodes
|
|
80
81
|
|
|
81
82
|
const { buildErrorHandler } = require('./lib/error-handler.js')
|
|
82
|
-
const { FSTWRN004 } = require('./lib/warnings.js')
|
|
83
|
+
const { FSTWRN004, FSTDEP023, FSTDEP024 } = require('./lib/warnings.js')
|
|
83
84
|
|
|
84
85
|
const initChannel = diagnostics.channel('fastify.initialization')
|
|
85
86
|
|
|
@@ -90,7 +91,7 @@ function fastify (serverOptions) {
|
|
|
90
91
|
const {
|
|
91
92
|
options,
|
|
92
93
|
genReqId,
|
|
93
|
-
|
|
94
|
+
logController,
|
|
94
95
|
hasLogger,
|
|
95
96
|
initialConfig
|
|
96
97
|
} = processOptions(serverOptions, defaultRoute, onBadUrl, onMaxParamLength)
|
|
@@ -142,7 +143,9 @@ function fastify (serverOptions) {
|
|
|
142
143
|
'OPTIONS',
|
|
143
144
|
'PATCH',
|
|
144
145
|
'PUT',
|
|
145
|
-
'POST'
|
|
146
|
+
'POST',
|
|
147
|
+
// RFC 10008
|
|
148
|
+
'QUERY'
|
|
146
149
|
])
|
|
147
150
|
},
|
|
148
151
|
[kOptions]: options,
|
|
@@ -199,6 +202,9 @@ function fastify (serverOptions) {
|
|
|
199
202
|
options: function _options (url, options, handler) {
|
|
200
203
|
return router.prepareRoute.call(this, { method: 'OPTIONS', url, options, handler })
|
|
201
204
|
},
|
|
205
|
+
query: function _query (url, options, handler) {
|
|
206
|
+
return router.prepareRoute.call(this, { method: 'QUERY', url, options, handler })
|
|
207
|
+
},
|
|
202
208
|
all: function _all (url, options, handler) {
|
|
203
209
|
return router.prepareRoute.call(this, { method: this.supportedMethods, url, options, handler })
|
|
204
210
|
},
|
|
@@ -216,6 +222,7 @@ function fastify (serverOptions) {
|
|
|
216
222
|
},
|
|
217
223
|
// expose logger instance
|
|
218
224
|
log: options.logger,
|
|
225
|
+
[kLogController]: logController,
|
|
219
226
|
// type provider
|
|
220
227
|
withTypeProvider,
|
|
221
228
|
// hooks
|
|
@@ -643,10 +650,7 @@ function fastify (serverOptions) {
|
|
|
643
650
|
const request = new Request(id, null, req, null, childLogger, routeEventContext)
|
|
644
651
|
const reply = new Reply(res, request, childLogger)
|
|
645
652
|
|
|
646
|
-
|
|
647
|
-
if (resolvedDisableRequestLogging === false) {
|
|
648
|
-
childLogger.info({ req: request }, 'incoming request')
|
|
649
|
-
}
|
|
653
|
+
routeEventContext.server[kLogController].incomingRequest(request, reply)
|
|
650
654
|
|
|
651
655
|
return options.frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
|
|
652
656
|
}
|
|
@@ -671,10 +675,7 @@ function fastify (serverOptions) {
|
|
|
671
675
|
const request = new Request(id, null, req, null, childLogger, routeEventContext)
|
|
672
676
|
const reply = new Reply(res, request, childLogger)
|
|
673
677
|
|
|
674
|
-
|
|
675
|
-
if (resolvedDisableRequestLogging === false) {
|
|
676
|
-
childLogger.info({ req: request }, 'incoming request')
|
|
677
|
-
}
|
|
678
|
+
routeEventContext.server[kLogController].incomingRequest(request, reply)
|
|
678
679
|
|
|
679
680
|
return options.frameworkErrors(new FST_ERR_MAX_PARAM_LENGTH(path), request, reply)
|
|
680
681
|
}
|
|
@@ -702,10 +703,7 @@ function fastify (serverOptions) {
|
|
|
702
703
|
const request = new Request(id, null, req, null, childLogger, routeEventContext)
|
|
703
704
|
const reply = new Reply(res, request, childLogger)
|
|
704
705
|
|
|
705
|
-
|
|
706
|
-
if (resolvedDisableRequestLogging === false) {
|
|
707
|
-
childLogger.info({ req: request }, 'incoming request')
|
|
708
|
-
}
|
|
706
|
+
routeEventContext.server[kLogController].incomingRequest(request, reply)
|
|
709
707
|
|
|
710
708
|
return options.frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)
|
|
711
709
|
}
|
|
@@ -876,9 +874,13 @@ function processOptions (options, defaultRoute, onBadUrl, onMaxParamLength) {
|
|
|
876
874
|
|
|
877
875
|
const requestIdHeader = typeof options.requestIdHeader === 'string' && options.requestIdHeader.length !== 0 ? options.requestIdHeader.toLowerCase() : (options.requestIdHeader === true && 'request-id')
|
|
878
876
|
const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId)
|
|
879
|
-
|
|
877
|
+
if (options.requestIdLogLabel !== undefined) {
|
|
878
|
+
FSTDEP024()
|
|
879
|
+
}
|
|
880
880
|
options.bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit
|
|
881
|
-
|
|
881
|
+
if (options.disableRequestLogging !== undefined) {
|
|
882
|
+
FSTDEP023()
|
|
883
|
+
}
|
|
882
884
|
|
|
883
885
|
const ajvOptions = Object.assign({
|
|
884
886
|
customOptions: {},
|
|
@@ -894,6 +896,10 @@ function processOptions (options, defaultRoute, onBadUrl, onMaxParamLength) {
|
|
|
894
896
|
|
|
895
897
|
const { logger, hasLogger } = createLogger(options)
|
|
896
898
|
|
|
899
|
+
// the internal logger uses the input logger to execute the logging. This allows the user
|
|
900
|
+
// to customize every internal log line
|
|
901
|
+
const logController = createLogController(options)
|
|
902
|
+
|
|
897
903
|
// Update the options with the fixed values
|
|
898
904
|
options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout
|
|
899
905
|
options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout
|
|
@@ -901,8 +907,6 @@ function processOptions (options, defaultRoute, onBadUrl, onMaxParamLength) {
|
|
|
901
907
|
options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout
|
|
902
908
|
options.logger = logger
|
|
903
909
|
options.requestIdHeader = requestIdHeader
|
|
904
|
-
options.requestIdLogLabel = requestIdLogLabel
|
|
905
|
-
options.disableRequestLogging = disableRequestLogging
|
|
906
910
|
options.ajv = ajvOptions
|
|
907
911
|
options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler
|
|
908
912
|
options.allowErrorHandlerOverride = options.allowErrorHandlerOverride ?? defaultInitOptions.allowErrorHandlerOverride
|
|
@@ -930,7 +934,7 @@ function processOptions (options, defaultRoute, onBadUrl, onMaxParamLength) {
|
|
|
930
934
|
return {
|
|
931
935
|
options,
|
|
932
936
|
genReqId,
|
|
933
|
-
|
|
937
|
+
logController,
|
|
934
938
|
hasLogger,
|
|
935
939
|
initialConfig
|
|
936
940
|
}
|
|
@@ -1010,5 +1014,6 @@ function validateSchemaErrorFormatter (schemaErrorFormatter) {
|
|
|
1010
1014
|
*/
|
|
1011
1015
|
module.exports = fastify
|
|
1012
1016
|
module.exports.errorCodes = errorCodes
|
|
1017
|
+
module.exports.LogController = LogController
|
|
1013
1018
|
module.exports.fastify = fastify
|
|
1014
1019
|
module.exports.default = fastify
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { AsyncResource } = require('node:async_hooks')
|
|
4
|
-
const { FifoMap: Fifo
|
|
4
|
+
const { FifoMap: Fifo } = require('toad-cache')
|
|
5
5
|
const { parse: secureJsonParse } = require('secure-json-parse')
|
|
6
6
|
const ContentType = require('./content-type')
|
|
7
7
|
const {
|
|
@@ -39,15 +39,6 @@ function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning)
|
|
|
39
39
|
this.parserList = ['application/json', 'text/plain']
|
|
40
40
|
this.parserRegExpList = []
|
|
41
41
|
this.cache = new Fifo(100)
|
|
42
|
-
this.ctCache = new Lru(100)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
ContentTypeParser.prototype.getContentType = function (raw) {
|
|
46
|
-
let ct = this.ctCache.get(raw)
|
|
47
|
-
if (ct !== undefined) return ct
|
|
48
|
-
ct = new ContentType(raw)
|
|
49
|
-
this.ctCache.set(raw, ct)
|
|
50
|
-
return ct
|
|
51
42
|
}
|
|
52
43
|
|
|
53
44
|
ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
|
|
@@ -169,7 +160,6 @@ ContentTypeParser.prototype.removeAll = function () {
|
|
|
169
160
|
this.parserRegExpList = []
|
|
170
161
|
this.parserList = []
|
|
171
162
|
this.cache = new Fifo(100)
|
|
172
|
-
this.ctCache = new Lru(100)
|
|
173
163
|
}
|
|
174
164
|
|
|
175
165
|
ContentTypeParser.prototype.remove = function (contentType) {
|
package/lib/content-type.js
CHANGED
|
@@ -1,15 +1,39 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { LruMap: Lru } = require('toad-cache')
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
|
-
* keyValuePairsReg
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* keyValuePairsReg matches a single `parameter-name "=" parameter-value`
|
|
7
|
+
* at a parameter boundary, following RFC 9110 §5.6.6 grammar:
|
|
8
|
+
*
|
|
9
|
+
* parameter = parameter-name "=" parameter-value
|
|
10
|
+
* parameter-name = token
|
|
11
|
+
* parameter-value = ( token / quoted-string )
|
|
12
|
+
* quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
|
|
13
|
+
* qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
|
|
14
|
+
* quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
|
|
15
|
+
* obs-text = %x80-FF
|
|
16
|
+
*
|
|
17
|
+
* The value alternative accepts either a bare token or a full quoted-string
|
|
18
|
+
* whose content matches the qdtext / quoted-pair character ranges. Because
|
|
19
|
+
* the quoted-string branch is aware of `\"` as an escape, embedded `"` and
|
|
20
|
+
* `;` characters inside a quoted value do not terminate the value.
|
|
8
21
|
*
|
|
9
22
|
* @see https://httpwg.org/specs/rfc9110.html#parameter
|
|
10
23
|
* @type {RegExp}
|
|
11
24
|
*/
|
|
12
|
-
const keyValuePairsReg = /(?:^|;)\s*([\w!#$%&'*+.^`|~-]+)=([
|
|
25
|
+
const keyValuePairsReg = /(?:^|;)\s*([\w!#$%&'*+.^`|~-]+)=("(?:[\t\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\t\u0020-\u00ff])*"|[\w!#$%&'*+.^`|~-]+)/gu
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* quotedPairReg matches a quoted-pair (a backslash followed by a single
|
|
29
|
+
* quoted-pair-allowed octet) inside a quoted-string value. Per RFC 9110
|
|
30
|
+
* §5.6.4, recipients that process the value of a quoted-string MUST handle
|
|
31
|
+
* a quoted-pair as if it were replaced by the octet following the backslash.
|
|
32
|
+
*
|
|
33
|
+
* @see https://httpwg.org/specs/rfc9110.html#quoted.string
|
|
34
|
+
* @type {RegExp}
|
|
35
|
+
*/
|
|
36
|
+
const quotedPairReg = /\\([\t\u0020-\u00ff])/gu
|
|
13
37
|
|
|
14
38
|
/**
|
|
15
39
|
* typeNameReg is used to validate that the first part of the media-type
|
|
@@ -35,6 +59,12 @@ const typeNameReg = /^[\w!#$%&'*+.^`|~-]+$/
|
|
|
35
59
|
*/
|
|
36
60
|
const subtypeNameReg = /^[\w!#$%&'*+.^`|~-]+\s*$/
|
|
37
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Content-Type internal shared cache
|
|
64
|
+
* @type {Lru<ContentType>}
|
|
65
|
+
*/
|
|
66
|
+
const cache = new Lru(100)
|
|
67
|
+
|
|
38
68
|
/**
|
|
39
69
|
* ContentType parses and represents the value of the content-type header.
|
|
40
70
|
*
|
|
@@ -49,6 +79,27 @@ class ContentType {
|
|
|
49
79
|
#parameters = new Map()
|
|
50
80
|
#string
|
|
51
81
|
|
|
82
|
+
/**
|
|
83
|
+
* The shared cache of ContentType instances. The cache is used to avoid
|
|
84
|
+
* creating multiple instances of ContentType for the same header value.
|
|
85
|
+
* @type {Lru<ContentType>}
|
|
86
|
+
*/
|
|
87
|
+
static get cache () { return cache }
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create a ContentType instance from a header value. If the value has been
|
|
91
|
+
* previously parsed, the cached instance will be returned.
|
|
92
|
+
* @param {string} headerValue
|
|
93
|
+
* @returns {ContentType | undefined}
|
|
94
|
+
*/
|
|
95
|
+
static from (headerValue) {
|
|
96
|
+
let contentType = cache.get(headerValue)
|
|
97
|
+
if (contentType !== undefined) return contentType
|
|
98
|
+
contentType = new ContentType(headerValue)
|
|
99
|
+
cache.set(headerValue, contentType)
|
|
100
|
+
return contentType
|
|
101
|
+
}
|
|
102
|
+
|
|
52
103
|
constructor (headerValue) {
|
|
53
104
|
if (headerValue == null || headerValue === '' || headerValue === 'undefined') {
|
|
54
105
|
return
|
|
@@ -106,22 +157,22 @@ class ContentType {
|
|
|
106
157
|
|
|
107
158
|
let matches = keyValuePairsReg.exec(paramsList)
|
|
108
159
|
while (matches) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
160
|
+
// https://httpwg.org/specs/rfc9110.html#parameter
|
|
161
|
+
// Parameter names are case-insensitive.
|
|
162
|
+
const key = matches[1].toLowerCase()
|
|
163
|
+
// Parameter values might or might not be case-sensitive,
|
|
164
|
+
// depending on the semantics of the parameter name.
|
|
165
|
+
let value = matches[2]
|
|
166
|
+
if (value.charCodeAt(0) === 0x22 /* " */) {
|
|
167
|
+
// Strip the surrounding DQUOTEs and unescape quoted-pairs per
|
|
168
|
+
// RFC 9110 §5.6.4. The regex guarantees the value starts and ends
|
|
169
|
+
// with `"` and only contains permitted qdtext / quoted-pair octets.
|
|
170
|
+
value = value.slice(1, -1)
|
|
171
|
+
if (value.indexOf('\\') !== -1) {
|
|
172
|
+
value = value.replace(quotedPairReg, '$1')
|
|
116
173
|
}
|
|
117
|
-
// We should probably verify the value matches a quoted string
|
|
118
|
-
// (https://httpwg.org/specs/rfc9110.html#rule.quoted-string) value.
|
|
119
|
-
// But we are not really doing much with the parameter values, so we
|
|
120
|
-
// are omitting that at this time.
|
|
121
|
-
this.#parameters.set(key, value.slice(1, value.length - 1))
|
|
122
|
-
} else {
|
|
123
|
-
this.#parameters.set(key, value)
|
|
124
174
|
}
|
|
175
|
+
this.#parameters.set(key, value)
|
|
125
176
|
matches = keyValuePairsReg.exec(paramsList)
|
|
126
177
|
}
|
|
127
178
|
}
|
package/lib/context.js
CHANGED
|
@@ -6,7 +6,6 @@ const {
|
|
|
6
6
|
kSchemaErrorFormatter,
|
|
7
7
|
kErrorHandler,
|
|
8
8
|
kChildLoggerFactory,
|
|
9
|
-
kOptions,
|
|
10
9
|
kReply,
|
|
11
10
|
kRequest,
|
|
12
11
|
kBodyLimit,
|
|
@@ -24,7 +23,6 @@ function Context ({
|
|
|
24
23
|
schema,
|
|
25
24
|
handler,
|
|
26
25
|
config,
|
|
27
|
-
requestIdLogLabel,
|
|
28
26
|
childLoggerFactory,
|
|
29
27
|
errorHandler,
|
|
30
28
|
bodyLimit,
|
|
@@ -56,7 +54,6 @@ function Context ({
|
|
|
56
54
|
this.onRequestAbort = null
|
|
57
55
|
this.config = config
|
|
58
56
|
this.errorHandler = errorHandler || server[kErrorHandler]
|
|
59
|
-
this.requestIdLogLabel = requestIdLogLabel || server[kOptions].requestIdLogLabel
|
|
60
57
|
this.childLoggerFactory = childLoggerFactory || server[kChildLoggerFactory]
|
|
61
58
|
this._middie = null
|
|
62
59
|
this._parserOptions = {
|
package/lib/error-handler.js
CHANGED
|
@@ -8,7 +8,7 @@ const {
|
|
|
8
8
|
kReplyNextErrorHandler,
|
|
9
9
|
kReplyIsRunningOnErrorHook,
|
|
10
10
|
kRouteContext,
|
|
11
|
-
|
|
11
|
+
kLogController,
|
|
12
12
|
kDiagnosticsStore
|
|
13
13
|
} = require('./symbols.js')
|
|
14
14
|
|
|
@@ -36,12 +36,7 @@ function handleError (reply, error, cb) {
|
|
|
36
36
|
try {
|
|
37
37
|
reply.raw.writeHead(reply.raw.statusCode, reply[kReplyHeaders])
|
|
38
38
|
} catch (error) {
|
|
39
|
-
|
|
40
|
-
reply.log.warn(
|
|
41
|
-
{ req: reply.request, res: reply, err: error },
|
|
42
|
-
error?.message
|
|
43
|
-
)
|
|
44
|
-
}
|
|
39
|
+
reply.server[kLogController].writeHeadError(error, reply.request, reply)
|
|
45
40
|
reply.raw.writeHead(reply.raw.statusCode)
|
|
46
41
|
}
|
|
47
42
|
reply.raw.end(payload)
|
|
@@ -83,21 +78,7 @@ function handleError (reply, error, cb) {
|
|
|
83
78
|
function defaultErrorHandler (error, request, reply) {
|
|
84
79
|
setErrorHeaders(error, reply)
|
|
85
80
|
setErrorStatusCode(reply, error)
|
|
86
|
-
|
|
87
|
-
if (!reply.log[kDisableRequestLogging]) {
|
|
88
|
-
reply.log.info(
|
|
89
|
-
{ res: reply, err: error },
|
|
90
|
-
error?.message
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
} else {
|
|
94
|
-
if (!reply.log[kDisableRequestLogging]) {
|
|
95
|
-
reply.log.error(
|
|
96
|
-
{ req: request, res: reply, err: error },
|
|
97
|
-
error?.message
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
81
|
+
request.server[kLogController].defaultErrorLog(error, request, reply)
|
|
101
82
|
reply.send(error)
|
|
102
83
|
}
|
|
103
84
|
|
|
@@ -123,10 +104,10 @@ function fallbackErrorHandler (error, reply, cb) {
|
|
|
123
104
|
}))
|
|
124
105
|
}
|
|
125
106
|
} catch (err) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
107
|
+
// error is always FST_ERR_SCH_SERIALIZATION_BUILD
|
|
108
|
+
// because this is called from route/compileSchemasForSerialization
|
|
109
|
+
reply.server[kLogController].serializerError(err, reply.request, reply, { statusCode: res.statusCode })
|
|
110
|
+
|
|
130
111
|
reply.code(500)
|
|
131
112
|
payload = serializeError(new FST_ERR_FAILED_ERROR_SERIALIZATION(err.message, error.message))
|
|
132
113
|
}
|
package/lib/errors.js
CHANGED
|
@@ -243,6 +243,13 @@ const codes = {
|
|
|
243
243
|
TypeError
|
|
244
244
|
),
|
|
245
245
|
|
|
246
|
+
FST_ERR_LOG_INVALID_LOG_CONTROLLER: createError(
|
|
247
|
+
'FST_ERR_LOG_INVALID_LOG_CONTROLLER',
|
|
248
|
+
"logController option must be an instance of LogController, got '%s'.",
|
|
249
|
+
500,
|
|
250
|
+
TypeError
|
|
251
|
+
),
|
|
252
|
+
|
|
246
253
|
/**
|
|
247
254
|
* reply
|
|
248
255
|
*/
|
|
@@ -448,6 +455,16 @@ const codes = {
|
|
|
448
455
|
500,
|
|
449
456
|
TypeError
|
|
450
457
|
),
|
|
458
|
+
FST_ERR_ROUTE_MISSING_CONTENT_TYPE: createError(
|
|
459
|
+
'FST_ERR_ROUTE_MISSING_CONTENT_TYPE',
|
|
460
|
+
"Method '%s' must provide a 'Content-Type' header.",
|
|
461
|
+
400
|
|
462
|
+
),
|
|
463
|
+
FST_ERR_ROUTE_MISSING_CONTENT: createError(
|
|
464
|
+
'FST_ERR_ROUTE_MISSING_CONTENT',
|
|
465
|
+
"Method '%s' must provide a request body.",
|
|
466
|
+
400
|
|
467
|
+
),
|
|
451
468
|
|
|
452
469
|
/**
|
|
453
470
|
* again listen when close server
|
package/lib/four-oh-four.js
CHANGED
|
@@ -11,7 +11,8 @@ const {
|
|
|
11
11
|
kFourOhFourLevelInstance,
|
|
12
12
|
kFourOhFourContext,
|
|
13
13
|
kHooks,
|
|
14
|
-
kErrorHandler
|
|
14
|
+
kErrorHandler,
|
|
15
|
+
kLogController
|
|
15
16
|
} = require('./symbols.js')
|
|
16
17
|
const { lifecycleHooks } = require('./hooks')
|
|
17
18
|
const { buildErrorHandler } = require('./error-handler.js')
|
|
@@ -29,7 +30,7 @@ const { getGenReqId } = require('./req-id-gen-factory.js')
|
|
|
29
30
|
* kFourOhFourContext: the context in the reply object where the handler will be executed
|
|
30
31
|
*/
|
|
31
32
|
function fourOhFour (options) {
|
|
32
|
-
const { logger
|
|
33
|
+
const { logger } = options
|
|
33
34
|
|
|
34
35
|
// 404 router, used for handling encapsulated 404 handlers
|
|
35
36
|
const router = FindMyWay({
|
|
@@ -52,14 +53,10 @@ function fourOhFour (options) {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
function basic404 (request, reply) {
|
|
56
|
+
request.server[kLogController].routeNotFound(request, reply)
|
|
55
57
|
const { url, method } = request.raw
|
|
56
|
-
const message = `Route ${method}:${url} not found`
|
|
57
|
-
const resolvedDisableRequestLogging = typeof disableRequestLogging === 'function' ? disableRequestLogging(request.raw) : disableRequestLogging
|
|
58
|
-
if (!resolvedDisableRequestLogging) {
|
|
59
|
-
request.log.info(message)
|
|
60
|
-
}
|
|
61
58
|
reply.code(404).send({
|
|
62
|
-
message
|
|
59
|
+
message: `Route ${method}:${url} not found`,
|
|
63
60
|
error: 'Not Found',
|
|
64
61
|
statusCode: 404
|
|
65
62
|
})
|
|
@@ -181,11 +178,12 @@ function fourOhFour (options) {
|
|
|
181
178
|
const id = getGenReqId(fourOhFourContext.server, req)
|
|
182
179
|
const childLogger = createChildLogger(fourOhFourContext, logger, req, id)
|
|
183
180
|
|
|
184
|
-
childLogger.info({ req }, 'incoming request')
|
|
185
|
-
|
|
186
181
|
const request = new Request(id, null, req, null, childLogger, fourOhFourContext)
|
|
187
182
|
const reply = new Reply(res, request, childLogger)
|
|
188
183
|
|
|
184
|
+
fourOhFourContext.server[kLogController].incomingRequest(request, reply)
|
|
185
|
+
|
|
186
|
+
// Since this could be a bug, we log it without checking if the logging is disabled
|
|
189
187
|
request.log.warn('the default handler for 404 did not catch this, this is likely a fastify bug, please report it')
|
|
190
188
|
request.log.warn(router.prettyPrint())
|
|
191
189
|
reply.code(404).send(new FST_ERR_NOT_FOUND())
|
package/lib/handle-request.js
CHANGED
|
@@ -4,7 +4,11 @@ const diagnostics = require('node:diagnostics_channel')
|
|
|
4
4
|
const wrapThenable = require('./wrap-thenable')
|
|
5
5
|
const { validate: validateSchema } = require('./validation')
|
|
6
6
|
const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks')
|
|
7
|
-
const {
|
|
7
|
+
const {
|
|
8
|
+
FST_ERR_CTP_INVALID_MEDIA_TYPE,
|
|
9
|
+
FST_ERR_ROUTE_MISSING_CONTENT_TYPE,
|
|
10
|
+
FST_ERR_ROUTE_MISSING_CONTENT
|
|
11
|
+
} = require('./errors')
|
|
8
12
|
const { setErrorStatusCode } = require('./error-status')
|
|
9
13
|
const {
|
|
10
14
|
kReplyIsError,
|
|
@@ -14,6 +18,7 @@ const {
|
|
|
14
18
|
kRequestContentType,
|
|
15
19
|
kDiagnosticsStore
|
|
16
20
|
} = require('./symbols')
|
|
21
|
+
const ContentType = require('./content-type')
|
|
17
22
|
|
|
18
23
|
const channels = diagnostics.tracingChannel('fastify.request.handler')
|
|
19
24
|
|
|
@@ -36,13 +41,30 @@ function handleRequest (err, request, reply) {
|
|
|
36
41
|
const headers = request.headers
|
|
37
42
|
const ctHeader = headers['content-type']
|
|
38
43
|
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
(
|
|
44
|
+
if (method === 'QUERY') {
|
|
45
|
+
if (ctHeader === undefined) {
|
|
46
|
+
// https://datatracker.ietf.org/doc/html/rfc10008#section-2
|
|
47
|
+
// Servers MUST fail the request if the Content-Type request field
|
|
48
|
+
// ([HTTP], Section 8.3) is missing or is inconsistent with the
|
|
49
|
+
// request content.
|
|
50
|
+
reply[kReplyIsError] = true
|
|
51
|
+
reply.status(400).send(new FST_ERR_ROUTE_MISSING_CONTENT_TYPE(method))
|
|
52
|
+
return
|
|
53
|
+
}
|
|
44
54
|
|
|
45
|
-
if (isEmptyBody) {
|
|
55
|
+
if (isEmptyBody(headers)) {
|
|
56
|
+
// https://datatracker.ietf.org/doc/html/rfc10008#section-2
|
|
57
|
+
// Servers MUST fail the request if the Content-Type request field
|
|
58
|
+
// ([HTTP], Section 8.3) is missing or is inconsistent with the
|
|
59
|
+
// request content.
|
|
60
|
+
reply[kReplyIsError] = true
|
|
61
|
+
reply.status(400).send(new FST_ERR_ROUTE_MISSING_CONTENT(method))
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (ctHeader === undefined) {
|
|
67
|
+
if (isEmptyBody(headers)) {
|
|
46
68
|
// Request has no body to parse
|
|
47
69
|
handler(request, reply)
|
|
48
70
|
return
|
|
@@ -55,7 +77,7 @@ function handleRequest (err, request, reply) {
|
|
|
55
77
|
// Conditional assignment to avoid creating a new ContentType instance
|
|
56
78
|
// It can be assigned when accessing the .mediaType in hooks
|
|
57
79
|
if (!request[kRequestContentType]) {
|
|
58
|
-
request[kRequestContentType] =
|
|
80
|
+
request[kRequestContentType] = ContentType.from(ctHeader)
|
|
59
81
|
}
|
|
60
82
|
if (request[kRequestContentType].isValid === false) {
|
|
61
83
|
reply[kReplyIsError] = true
|
|
@@ -87,6 +109,13 @@ function handler (request, reply) {
|
|
|
87
109
|
}
|
|
88
110
|
}
|
|
89
111
|
|
|
112
|
+
function isEmptyBody (headers) {
|
|
113
|
+
const contentLength = headers['content-length']
|
|
114
|
+
const transferEncoding = headers['transfer-encoding']
|
|
115
|
+
return transferEncoding === undefined &&
|
|
116
|
+
(contentLength === undefined || contentLength === '0')
|
|
117
|
+
}
|
|
118
|
+
|
|
90
119
|
function preValidationCallback (err, request, reply) {
|
|
91
120
|
if (reply.sent === true) return
|
|
92
121
|
|
package/lib/hooks.js
CHANGED
|
@@ -306,7 +306,11 @@ function onSendHookRunner (functions, request, reply, payload, cb) {
|
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
function handleResolve (newPayload) {
|
|
309
|
-
|
|
309
|
+
try {
|
|
310
|
+
next(null, newPayload)
|
|
311
|
+
} catch (err) {
|
|
312
|
+
cb(err, request, reply, payload)
|
|
313
|
+
}
|
|
310
314
|
}
|
|
311
315
|
|
|
312
316
|
function handleReject (err) {
|