fastify 5.10.0 → 5.11.1
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/.pi/bash-confirm-whitelist.json +180 -0
- package/SPONSORS.md +1 -0
- package/docs/Guides/Ecosystem.md +2 -2
- package/docs/Guides/Getting-Started.md +8 -8
- package/docs/Reference/Errors.md +4 -0
- package/docs/Reference/Reply.md +2 -2
- package/docs/Reference/Request.md +11 -11
- package/docs/Reference/Server.md +34 -17
- package/docs/Reference/TypeScript.md +5 -7
- package/docs/Reference/Validation-and-Serialization.md +13 -2
- package/docs/Reference/Warnings.md +3 -2
- package/eslint.config.js +1 -1
- package/fastify.d.ts +57 -13
- package/fastify.js +17 -5
- package/lib/content-type.js +36 -18
- package/lib/errors.js +10 -0
- package/lib/handle-request.js +35 -7
- package/lib/hooks.js +5 -1
- package/lib/route.js +1 -1
- package/lib/schemas.js +3 -3
- package/lib/validation.js +1 -1
- package/lib/warnings.js +11 -2
- package/package.json +2 -2
- package/test/close.test.js +39 -0
- package/test/content-type.test.js +31 -4
- package/test/find-route.test.js +35 -0
- package/test/fix-6411.test.js +65 -0
- package/test/http-methods/custom-http-methods.test.js +61 -0
- package/test/internals/all.test.js +2 -1
- package/test/internals/errors.test.js +21 -1
- package/test/internals/reply.test.js +22 -0
- 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/types/errors.tst.ts +2 -0
- package/test/types/fastify.tst.ts +11 -0
- package/test/types/instance.tst.ts +2 -0
- package/test/types/route.tst.ts +3 -3
- package/test/validation-error-handling.test.js +34 -0
- package/types/content-type-parser.d.ts +26 -6
- package/types/errors.d.ts +2 -0
- package/types/hooks.d.ts +137 -76
- package/types/instance.d.ts +153 -47
- package/types/logger.d.ts +36 -3
- 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.d.ts
CHANGED
|
@@ -69,6 +69,7 @@ declare namespace fastify {
|
|
|
69
69
|
Logger extends FastifyBaseLogger = FastifyBaseLogger
|
|
70
70
|
> = FastifyServerOptions<Server, Logger> & {
|
|
71
71
|
https: https.ServerOptions | null
|
|
72
|
+
http2?: false
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
export type FastifyHttpOptions<
|
|
@@ -76,9 +77,12 @@ declare namespace fastify {
|
|
|
76
77
|
Logger extends FastifyBaseLogger = FastifyBaseLogger
|
|
77
78
|
> = FastifyServerOptions<Server, Logger> & {
|
|
78
79
|
http?: http.ServerOptions | null
|
|
80
|
+
http2?: false
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
type FindMyWayVersion<RawServer extends RawServerBase> = RawServer extends http.Server
|
|
83
|
+
type FindMyWayVersion<RawServer extends RawServerBase> = RawServer extends http.Server
|
|
84
|
+
? HTTPVersion.V1
|
|
85
|
+
: HTTPVersion.V2
|
|
82
86
|
type FindMyWayConfigForServer<RawServer extends RawServerBase> = FindMyWayConfig<FindMyWayVersion<RawServer>>
|
|
83
87
|
|
|
84
88
|
export interface ConnectionError extends Error {
|
|
@@ -163,15 +167,39 @@ declare namespace fastify {
|
|
|
163
167
|
};
|
|
164
168
|
return503OnClosing?: boolean,
|
|
165
169
|
ajv?: Parameters<BuildCompilerFromPool>[1],
|
|
166
|
-
frameworkErrors?: <
|
|
170
|
+
frameworkErrors?: <
|
|
171
|
+
RequestGeneric extends RequestGenericInterface = RequestGenericInterface,
|
|
172
|
+
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
|
|
173
|
+
SchemaCompiler extends FastifySchema = FastifySchema
|
|
174
|
+
>(
|
|
167
175
|
error: FastifyError,
|
|
168
|
-
req: FastifyRequest<
|
|
169
|
-
|
|
176
|
+
req: FastifyRequest<
|
|
177
|
+
RequestGeneric,
|
|
178
|
+
RawServer,
|
|
179
|
+
RawRequestDefaultExpression<RawServer>,
|
|
180
|
+
FastifySchema,
|
|
181
|
+
TypeProvider
|
|
182
|
+
>,
|
|
183
|
+
res: FastifyReply<
|
|
184
|
+
RequestGeneric,
|
|
185
|
+
RawServer,
|
|
186
|
+
RawRequestDefaultExpression<RawServer>,
|
|
187
|
+
RawReplyDefaultExpression<RawServer>,
|
|
188
|
+
FastifyContextConfig,
|
|
189
|
+
SchemaCompiler,
|
|
190
|
+
TypeProvider
|
|
191
|
+
>
|
|
170
192
|
) => void,
|
|
171
193
|
rewriteUrl?: (
|
|
172
194
|
// The RawRequestDefaultExpression, RawReplyDefaultExpression, and FastifyTypeProviderDefault parameters
|
|
173
195
|
// should be narrowed further but those generic parameters are not passed to this FastifyServerOptions type
|
|
174
|
-
this: FastifyInstance<
|
|
196
|
+
this: FastifyInstance<
|
|
197
|
+
RawServer,
|
|
198
|
+
RawRequestDefaultExpression<RawServer>,
|
|
199
|
+
RawReplyDefaultExpression<RawServer>,
|
|
200
|
+
Logger,
|
|
201
|
+
FastifyTypeProviderDefault
|
|
202
|
+
>,
|
|
175
203
|
req: RawRequestDefaultExpression<RawServer>
|
|
176
204
|
) => string,
|
|
177
205
|
schemaErrorFormatter?: SchemaErrorFormatter,
|
|
@@ -198,13 +226,25 @@ declare namespace fastify {
|
|
|
198
226
|
FastifyListenOptions, FastifyInstance, PrintRoutesOptions, // './types/instance'
|
|
199
227
|
FastifyLoggerOptions, FastifyBaseLogger, FastifyLoggerInstance, FastifyLogFn, LogLevel, // './types/logger'
|
|
200
228
|
FastifyRequestContext, FastifyContextConfig, FastifyReplyContext, // './types/context'
|
|
201
|
-
RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions,
|
|
229
|
+
RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions,
|
|
230
|
+
RouteShorthandOptionsWithHandler, RouteGenericInterface, // './types/route'
|
|
202
231
|
FastifyRegister, FastifyRegisterOptions, RegisterOptions, // './types/register'
|
|
203
|
-
FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser,
|
|
232
|
+
FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser,
|
|
233
|
+
ProtoAction, ConstructorAction, // './types/content-type-parser'
|
|
204
234
|
FastifyError, // '@fastify/error'
|
|
205
235
|
FastifySchema, FastifySchemaValidationError, FastifySchemaCompiler, FastifySerializerCompiler, // './types/schema'
|
|
206
|
-
HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault,
|
|
207
|
-
|
|
236
|
+
HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault,
|
|
237
|
+
ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault,
|
|
238
|
+
// './types/utils'
|
|
239
|
+
DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler,
|
|
240
|
+
onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler,
|
|
241
|
+
onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler,
|
|
242
|
+
onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler,
|
|
243
|
+
onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler,
|
|
244
|
+
preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler,
|
|
245
|
+
preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler,
|
|
246
|
+
preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler, preCloseAsyncHookHandler,
|
|
247
|
+
preCloseHookHandler, // './types/hooks'
|
|
208
248
|
FastifyServerFactory, FastifyServerFactoryHandler, // './types/serverFactory'
|
|
209
249
|
FastifyTypeProvider, FastifyTypeProviderDefault, SafePromiseLike, // './types/type-provider'
|
|
210
250
|
FastifyErrorCodes // './types/errors'
|
|
@@ -232,7 +272,8 @@ declare function fastify<
|
|
|
232
272
|
Reply extends RawReplyDefaultExpression<Server> = RawReplyDefaultExpression<Server>,
|
|
233
273
|
Logger extends FastifyBaseLogger = FastifyBaseLogger,
|
|
234
274
|
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
|
|
235
|
-
> (opts: fastify.FastifyHttp2SecureOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger,
|
|
275
|
+
> (opts: fastify.FastifyHttp2SecureOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger,
|
|
276
|
+
TypeProvider> & SafePromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
|
|
236
277
|
|
|
237
278
|
declare function fastify<
|
|
238
279
|
Server extends http2.Http2Server,
|
|
@@ -240,7 +281,8 @@ declare function fastify<
|
|
|
240
281
|
Reply extends RawReplyDefaultExpression<Server> = RawReplyDefaultExpression<Server>,
|
|
241
282
|
Logger extends FastifyBaseLogger = FastifyBaseLogger,
|
|
242
283
|
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
|
|
243
|
-
> (opts: fastify.FastifyHttp2Options<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger,
|
|
284
|
+
> (opts: fastify.FastifyHttp2Options<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger,
|
|
285
|
+
TypeProvider> & SafePromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
|
|
244
286
|
|
|
245
287
|
declare function fastify<
|
|
246
288
|
Server extends https.Server,
|
|
@@ -248,7 +290,8 @@ declare function fastify<
|
|
|
248
290
|
Reply extends RawReplyDefaultExpression<Server> = RawReplyDefaultExpression<Server>,
|
|
249
291
|
Logger extends FastifyBaseLogger = FastifyBaseLogger,
|
|
250
292
|
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
|
|
251
|
-
> (opts: fastify.FastifyHttpsOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger,
|
|
293
|
+
> (opts: fastify.FastifyHttpsOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger,
|
|
294
|
+
TypeProvider> & SafePromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
|
|
252
295
|
|
|
253
296
|
declare function fastify<
|
|
254
297
|
Server extends http.Server,
|
|
@@ -256,7 +299,8 @@ declare function fastify<
|
|
|
256
299
|
Reply extends RawReplyDefaultExpression<Server> = RawReplyDefaultExpression<Server>,
|
|
257
300
|
Logger extends FastifyBaseLogger = FastifyBaseLogger,
|
|
258
301
|
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
|
|
259
|
-
> (opts?: fastify.FastifyHttpOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger,
|
|
302
|
+
> (opts?: fastify.FastifyHttpOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger,
|
|
303
|
+
TypeProvider> & SafePromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
|
|
260
304
|
|
|
261
305
|
// CJS export
|
|
262
306
|
// const fastify = require('fastify')
|
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')
|
|
@@ -80,7 +80,7 @@ const {
|
|
|
80
80
|
} = errorCodes
|
|
81
81
|
|
|
82
82
|
const { buildErrorHandler } = require('./lib/error-handler.js')
|
|
83
|
-
const { FSTWRN004, FSTDEP023, FSTDEP024 } = require('./lib/warnings.js')
|
|
83
|
+
const { FSTWRN004, FSTDEP023, FSTDEP024, FSTDEP025 } = require('./lib/warnings.js')
|
|
84
84
|
|
|
85
85
|
const initChannel = diagnostics.channel('fastify.initialization')
|
|
86
86
|
|
|
@@ -143,7 +143,9 @@ function fastify (serverOptions) {
|
|
|
143
143
|
'OPTIONS',
|
|
144
144
|
'PATCH',
|
|
145
145
|
'PUT',
|
|
146
|
-
'POST'
|
|
146
|
+
'POST',
|
|
147
|
+
// RFC 10008
|
|
148
|
+
'QUERY'
|
|
147
149
|
])
|
|
148
150
|
},
|
|
149
151
|
[kOptions]: options,
|
|
@@ -200,6 +202,9 @@ function fastify (serverOptions) {
|
|
|
200
202
|
options: function _options (url, options, handler) {
|
|
201
203
|
return router.prepareRoute.call(this, { method: 'OPTIONS', url, options, handler })
|
|
202
204
|
},
|
|
205
|
+
query: function _query (url, options, handler) {
|
|
206
|
+
return router.prepareRoute.call(this, { method: 'QUERY', url, options, handler })
|
|
207
|
+
},
|
|
203
208
|
all: function _all (url, options, handler) {
|
|
204
209
|
return router.prepareRoute.call(this, { method: this.supportedMethods, url, options, handler })
|
|
205
210
|
},
|
|
@@ -387,7 +392,7 @@ function fastify (serverOptions) {
|
|
|
387
392
|
if (forceCloseConnections === 'idle' && options.serverFactory) {
|
|
388
393
|
instance.server.closeIdleConnections()
|
|
389
394
|
/* istanbul ignore next: Cannot test this without Node.js core support */
|
|
390
|
-
} else if (serverHasCloseAllConnections && forceCloseConnections) {
|
|
395
|
+
} else if (serverHasCloseAllConnections && forceCloseConnections === true) {
|
|
391
396
|
instance.server.closeAllConnections()
|
|
392
397
|
} else if (forceCloseConnections === true) {
|
|
393
398
|
for (const conn of fastify[kKeepAliveConnections]) {
|
|
@@ -818,11 +823,18 @@ function fastify (serverOptions) {
|
|
|
818
823
|
return this
|
|
819
824
|
}
|
|
820
825
|
|
|
821
|
-
function addHttpMethod (method, { hasBody = false } = {}) {
|
|
826
|
+
function addHttpMethod (method, { hasBody = false, overrideExisting = false } = {}) {
|
|
822
827
|
if (typeof method !== 'string' || http.METHODS.indexOf(method) === -1) {
|
|
823
828
|
throw new FST_ERR_ROUTE_METHOD_INVALID()
|
|
824
829
|
}
|
|
825
830
|
|
|
831
|
+
const alreadyExists = this[kSupportedHTTPMethods].bodyless.has(method) ||
|
|
832
|
+
this[kSupportedHTTPMethods].bodywith.has(method)
|
|
833
|
+
|
|
834
|
+
if (alreadyExists && !overrideExisting) {
|
|
835
|
+
FSTDEP025(method)
|
|
836
|
+
}
|
|
837
|
+
|
|
826
838
|
if (hasBody === true) {
|
|
827
839
|
this[kSupportedHTTPMethods].bodywith.add(method)
|
|
828
840
|
this[kSupportedHTTPMethods].bodyless.delete(method)
|
package/lib/content-type.js
CHANGED
|
@@ -3,15 +3,37 @@
|
|
|
3
3
|
const { LruMap: Lru } = require('toad-cache')
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* keyValuePairsReg
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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.
|
|
10
21
|
*
|
|
11
22
|
* @see https://httpwg.org/specs/rfc9110.html#parameter
|
|
12
23
|
* @type {RegExp}
|
|
13
24
|
*/
|
|
14
|
-
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
|
|
15
37
|
|
|
16
38
|
/**
|
|
17
39
|
* typeNameReg is used to validate that the first part of the media-type
|
|
@@ -140,21 +162,17 @@ class ContentType {
|
|
|
140
162
|
const key = matches[1].toLowerCase()
|
|
141
163
|
// Parameter values might or might not be case-sensitive,
|
|
142
164
|
// depending on the semantics of the parameter name.
|
|
143
|
-
|
|
144
|
-
if (value
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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')
|
|
149
173
|
}
|
|
150
|
-
// We should probably verify the value matches a quoted string
|
|
151
|
-
// (https://httpwg.org/specs/rfc9110.html#rule.quoted-string) value.
|
|
152
|
-
// But we are not really doing much with the parameter values, so we
|
|
153
|
-
// are omitting that at this time.
|
|
154
|
-
this.#parameters.set(key, value.slice(1, value.length - 1))
|
|
155
|
-
} else {
|
|
156
|
-
this.#parameters.set(key, value)
|
|
157
174
|
}
|
|
175
|
+
this.#parameters.set(key, value)
|
|
158
176
|
matches = keyValuePairsReg.exec(paramsList)
|
|
159
177
|
}
|
|
160
178
|
}
|
package/lib/errors.js
CHANGED
|
@@ -455,6 +455,16 @@ const codes = {
|
|
|
455
455
|
500,
|
|
456
456
|
TypeError
|
|
457
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
|
+
),
|
|
458
468
|
|
|
459
469
|
/**
|
|
460
470
|
* again listen when close server
|
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,
|
|
@@ -37,13 +41,30 @@ function handleRequest (err, request, reply) {
|
|
|
37
41
|
const headers = request.headers
|
|
38
42
|
const ctHeader = headers['content-type']
|
|
39
43
|
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
(
|
|
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
|
+
}
|
|
45
54
|
|
|
46
|
-
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)) {
|
|
47
68
|
// Request has no body to parse
|
|
48
69
|
handler(request, reply)
|
|
49
70
|
return
|
|
@@ -88,6 +109,13 @@ function handler (request, reply) {
|
|
|
88
109
|
}
|
|
89
110
|
}
|
|
90
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
|
+
|
|
91
119
|
function preValidationCallback (err, request, reply) {
|
|
92
120
|
if (reply.sent === true) return
|
|
93
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) {
|
package/lib/route.js
CHANGED
package/lib/schemas.js
CHANGED
|
@@ -149,7 +149,7 @@ function getSchemaSerializer (context, statusCode, contentType) {
|
|
|
149
149
|
}
|
|
150
150
|
if (responseSchemaDef[statusCode]) {
|
|
151
151
|
if (responseSchemaDef[statusCode].constructor === Object) {
|
|
152
|
-
const ct =
|
|
152
|
+
const ct = ContentType.from(contentType)
|
|
153
153
|
if (ct.isValid) {
|
|
154
154
|
if (responseSchemaDef[statusCode][ct.mediaType]) {
|
|
155
155
|
return responseSchemaDef[statusCode][ct.mediaType]
|
|
@@ -168,7 +168,7 @@ function getSchemaSerializer (context, statusCode, contentType) {
|
|
|
168
168
|
const fallbackStatusCode = (statusCode + '')[0] + 'xx'
|
|
169
169
|
if (responseSchemaDef[fallbackStatusCode]) {
|
|
170
170
|
if (responseSchemaDef[fallbackStatusCode].constructor === Object) {
|
|
171
|
-
const ct =
|
|
171
|
+
const ct = ContentType.from(contentType)
|
|
172
172
|
if (ct.isValid) {
|
|
173
173
|
if (responseSchemaDef[fallbackStatusCode][ct.mediaType]) {
|
|
174
174
|
return responseSchemaDef[fallbackStatusCode][ct.mediaType]
|
|
@@ -187,7 +187,7 @@ function getSchemaSerializer (context, statusCode, contentType) {
|
|
|
187
187
|
}
|
|
188
188
|
if (responseSchemaDef.default) {
|
|
189
189
|
if (responseSchemaDef.default.constructor === Object) {
|
|
190
|
-
const ct =
|
|
190
|
+
const ct = ContentType.from(contentType)
|
|
191
191
|
if (ct.isValid) {
|
|
192
192
|
if (responseSchemaDef.default[ct.mediaType]) {
|
|
193
193
|
return responseSchemaDef.default[ct.mediaType]
|
package/lib/validation.js
CHANGED
|
@@ -138,7 +138,7 @@ function validateParam (validatorFunction, request, paramName) {
|
|
|
138
138
|
function answer (ret) {
|
|
139
139
|
if (ret === false) return validatorFunction.errors
|
|
140
140
|
if (ret && ret.error) return ret.error
|
|
141
|
-
if (ret && ret
|
|
141
|
+
if (ret && typeof ret === 'object' && 'value' in ret) request[paramName] = ret.value
|
|
142
142
|
return false
|
|
143
143
|
}
|
|
144
144
|
}
|
package/lib/warnings.js
CHANGED
|
@@ -9,9 +9,10 @@ const { createWarning } = require('process-warning')
|
|
|
9
9
|
* - FSTDEP022
|
|
10
10
|
* - FSTDEP023
|
|
11
11
|
* - FSTDEP024
|
|
12
|
+
* - FSTDEP025
|
|
12
13
|
*
|
|
13
14
|
* Deprecation Codes FSTDEP001 - FSTDEP021 were used by v4 and MUST NOT be reused.
|
|
14
|
-
* - FSTDEP022
|
|
15
|
+
* - FSTDEP022 - FSTDEP025 are used by v5 and MUST NOT be reused.
|
|
15
16
|
* Warning Codes FSTWRN001 - FSTWRN002 were used by v4 and MUST NOT be reused.
|
|
16
17
|
*/
|
|
17
18
|
|
|
@@ -64,6 +65,13 @@ const FSTDEP024 = createWarning({
|
|
|
64
65
|
unlimited: true
|
|
65
66
|
})
|
|
66
67
|
|
|
68
|
+
const FSTDEP025 = createWarning({
|
|
69
|
+
name: 'FastifyDeprecation',
|
|
70
|
+
code: 'FSTDEP025',
|
|
71
|
+
message: 'Calling addHttpMethod for existing method "%s" without { overrideExisting: true } is deprecated. In Fastify v6, this will throw an error.',
|
|
72
|
+
unlimited: true
|
|
73
|
+
})
|
|
74
|
+
|
|
67
75
|
module.exports = {
|
|
68
76
|
FSTWRN001,
|
|
69
77
|
FSTWRN003,
|
|
@@ -71,5 +79,6 @@ module.exports = {
|
|
|
71
79
|
FSTSEC001,
|
|
72
80
|
FSTDEP022,
|
|
73
81
|
FSTDEP023,
|
|
74
|
-
FSTDEP024
|
|
82
|
+
FSTDEP024,
|
|
83
|
+
FSTDEP025
|
|
75
84
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.11.1",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
"joi": "^18.0.1",
|
|
192
192
|
"json-schema-to-ts": "^3.0.1",
|
|
193
193
|
"JSONStream": "^1.3.5",
|
|
194
|
-
"markdownlint-cli2": "^0.
|
|
194
|
+
"markdownlint-cli2": "^0.23.0",
|
|
195
195
|
"neostandard": "^0.12.0",
|
|
196
196
|
"node-forge": "^1.3.1",
|
|
197
197
|
"proxyquire": "^2.1.3",
|
package/test/close.test.js
CHANGED
|
@@ -704,3 +704,42 @@ test('preClose execution order', (t, done) => {
|
|
|
704
704
|
})
|
|
705
705
|
})
|
|
706
706
|
})
|
|
707
|
+
|
|
708
|
+
test('does not destroy connections with in-flight requests (forceCloseConnections - idle)', { skip: noSupport }, async t => {
|
|
709
|
+
const fastify = Fastify({ forceCloseConnections: 'idle' })
|
|
710
|
+
|
|
711
|
+
fastify.get('/', async () => {
|
|
712
|
+
// the server starts closing while this request is still being served
|
|
713
|
+
fastify.close()
|
|
714
|
+
await sleep(200)
|
|
715
|
+
return { hello: 'world' }
|
|
716
|
+
})
|
|
717
|
+
|
|
718
|
+
await fastify.listen({ port: 0 })
|
|
719
|
+
|
|
720
|
+
const client = new Client('http://localhost:' + fastify.server.address().port)
|
|
721
|
+
t.after(() => client.close())
|
|
722
|
+
|
|
723
|
+
const response = await client.request({ path: '/', method: 'GET' })
|
|
724
|
+
t.assert.strictEqual(response.statusCode, 200)
|
|
725
|
+
t.assert.deepStrictEqual(await response.body.json(), { hello: 'world' })
|
|
726
|
+
})
|
|
727
|
+
|
|
728
|
+
test('does not destroy connections with in-flight requests (default options)', async t => {
|
|
729
|
+
const fastify = Fastify()
|
|
730
|
+
|
|
731
|
+
fastify.get('/', async () => {
|
|
732
|
+
fastify.close()
|
|
733
|
+
await sleep(200)
|
|
734
|
+
return { hello: 'world' }
|
|
735
|
+
})
|
|
736
|
+
|
|
737
|
+
await fastify.listen({ port: 0 })
|
|
738
|
+
|
|
739
|
+
const client = new Client('http://localhost:' + fastify.server.address().port)
|
|
740
|
+
t.after(() => client.close())
|
|
741
|
+
|
|
742
|
+
const response = await client.request({ path: '/', method: 'GET' })
|
|
743
|
+
t.assert.strictEqual(response.statusCode, 200)
|
|
744
|
+
t.assert.deepStrictEqual(await response.body.json(), { hello: 'world' })
|
|
745
|
+
})
|
|
@@ -161,12 +161,11 @@ describe('ContentType class', () => {
|
|
|
161
161
|
t.assert.equal(found.mediaType, 'application/json')
|
|
162
162
|
t.assert.equal(found.type, 'application')
|
|
163
163
|
t.assert.equal(found.subtype, 'json')
|
|
164
|
-
t.assert.equal(found.parameters.size,
|
|
164
|
+
t.assert.equal(found.parameters.size, 2)
|
|
165
165
|
|
|
166
166
|
const expected = [
|
|
167
167
|
['charset', 'utf-8'],
|
|
168
|
-
['foo', 'BaR']
|
|
169
|
-
['baz', 'invalid quoted string']
|
|
168
|
+
['foo', 'BaR']
|
|
170
169
|
]
|
|
171
170
|
t.assert.deepStrictEqual(
|
|
172
171
|
Array.from(found.parameters.entries()),
|
|
@@ -175,9 +174,37 @@ describe('ContentType class', () => {
|
|
|
175
174
|
|
|
176
175
|
t.assert.equal(
|
|
177
176
|
found.toString(),
|
|
178
|
-
'application/json; charset="utf-8"; foo="BaR"
|
|
177
|
+
'application/json; charset="utf-8"; foo="BaR"'
|
|
179
178
|
)
|
|
180
179
|
})
|
|
180
|
+
|
|
181
|
+
test('preserves a semicolon inside a quoted parameter value', (t) => {
|
|
182
|
+
// RFC 9110 §5.6.6: ';' is a literal qdtext octet inside a quoted-string
|
|
183
|
+
// and does not terminate the parameter value.
|
|
184
|
+
const found = new ContentType('application/json; name="foo;bar"; charset=utf-8')
|
|
185
|
+
t.assert.equal(found.isValid, true)
|
|
186
|
+
t.assert.equal(found.mediaType, 'application/json')
|
|
187
|
+
t.assert.equal(found.parameters.get('name'), 'foo;bar')
|
|
188
|
+
t.assert.equal(found.parameters.get('charset'), 'utf-8')
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('does not leak a fake parameter out of a quoted value', (t) => {
|
|
192
|
+
// A `key=value;` sequence inside a quoted-string is opaque content of the
|
|
193
|
+
// enclosing value, not a subsequent parameter.
|
|
194
|
+
const found = new ContentType('application/json; name="a=b;charset=fake"; boundary=xyz')
|
|
195
|
+
t.assert.equal(found.isValid, true)
|
|
196
|
+
t.assert.equal(found.parameters.get('name'), 'a=b;charset=fake')
|
|
197
|
+
t.assert.equal(found.parameters.get('boundary'), 'xyz')
|
|
198
|
+
t.assert.equal(found.parameters.has('charset'), false)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
test('unescapes a quoted-pair inside a quoted-string', (t) => {
|
|
202
|
+
// RFC 9110 §5.6.4: a quoted-pair MUST be handled as if replaced by
|
|
203
|
+
// the octet following the backslash.
|
|
204
|
+
const found = new ContentType('application/json; name="he said \\"hi\\""')
|
|
205
|
+
t.assert.equal(found.isValid, true)
|
|
206
|
+
t.assert.equal(found.parameters.get('name'), 'he said "hi"')
|
|
207
|
+
})
|
|
181
208
|
})
|
|
182
209
|
|
|
183
210
|
describe('ContentType class cache', () => {
|
package/test/find-route.test.js
CHANGED
|
@@ -66,6 +66,22 @@ test('findRoute should return null when when url is not passed', t => {
|
|
|
66
66
|
}), null)
|
|
67
67
|
})
|
|
68
68
|
|
|
69
|
+
test('findRoute should return null when method is not passed', t => {
|
|
70
|
+
t.plan(1)
|
|
71
|
+
const fastify = Fastify()
|
|
72
|
+
|
|
73
|
+
fastify.get('/artists/:artistId', {
|
|
74
|
+
schema: {
|
|
75
|
+
params: { artistId: { type: 'integer' } }
|
|
76
|
+
},
|
|
77
|
+
handler: (req, reply) => reply.send(typeof req.params.artistId)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
t.assert.strictEqual(fastify.findRoute({
|
|
81
|
+
url: '/artists/:artistId'
|
|
82
|
+
}), null)
|
|
83
|
+
})
|
|
84
|
+
|
|
69
85
|
test('findRoute should return null when route cannot be found due to a different path', t => {
|
|
70
86
|
t.plan(1)
|
|
71
87
|
const fastify = Fastify()
|
|
@@ -103,6 +119,25 @@ test('findRoute should return the route when found', t => {
|
|
|
103
119
|
t.assert.strictEqual(route.params.artistId, ':artistId')
|
|
104
120
|
})
|
|
105
121
|
|
|
122
|
+
test('findRoute should find a route even if method is not uppercased', t => {
|
|
123
|
+
t.plan(2)
|
|
124
|
+
const fastify = Fastify()
|
|
125
|
+
|
|
126
|
+
fastify.get('/artists/:artistId', {
|
|
127
|
+
schema: {
|
|
128
|
+
params: { artistId: { type: 'integer' } }
|
|
129
|
+
},
|
|
130
|
+
handler: (req, reply) => reply.send(typeof req.params.artistId)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const route = fastify.findRoute({
|
|
134
|
+
method: 'get',
|
|
135
|
+
url: '/artists/:artistId'
|
|
136
|
+
})
|
|
137
|
+
t.assert.notStrictEqual(route, null)
|
|
138
|
+
t.assert.strictEqual(route.params.artistId, ':artistId')
|
|
139
|
+
})
|
|
140
|
+
|
|
106
141
|
test('findRoute should work correctly when used within plugins', (t, done) => {
|
|
107
142
|
t.plan(1)
|
|
108
143
|
const fastify = Fastify()
|