fastify 4.19.2 → 4.21.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/.c8rc.json +8 -0
- package/.taprc +3 -2
- package/README.md +2 -1
- package/SECURITY.md +9 -0
- package/docs/Guides/Prototype-Poisoning.md +2 -2
- package/docs/Reference/Errors.md +39 -17
- package/docs/Reference/Logging.md +1 -1
- package/docs/Reference/Plugins.md +4 -0
- package/docs/Reference/Routes.md +8 -0
- package/docs/Reference/Server.md +230 -178
- package/docs/Reference/TypeScript.md +1 -1
- package/fastify.d.ts +3 -2
- package/fastify.js +36 -17
- package/lib/context.js +6 -0
- package/lib/errors.js +51 -20
- package/lib/fourOhFour.js +5 -9
- package/lib/handleRequest.js +3 -5
- package/lib/hooks.js +91 -25
- package/lib/logger.js +40 -3
- package/lib/reply.js +19 -13
- package/lib/reqIdGenFactory.js +18 -3
- package/lib/route.js +14 -61
- package/lib/schema-controller.js +2 -0
- package/lib/server.js +23 -8
- package/lib/symbols.js +1 -0
- package/package.json +8 -10
- package/test/500s.test.js +22 -0
- package/test/async-await.test.js +1 -1
- package/test/childLoggerFactory.test.js +91 -0
- package/test/encapsulated-child-logger-factory.test.js +69 -0
- package/test/fastify-instance.test.js +43 -10
- package/test/inject.test.js +1 -2
- package/test/internals/errors.test.js +843 -0
- package/test/internals/hookRunner.test.js +22 -8
- package/test/internals/initialConfig.test.js +9 -2
- package/test/internals/reply.test.js +82 -45
- package/test/internals/reqIdGenFactory.test.js +129 -0
- package/test/internals/request-validate.test.js +40 -1
- package/test/internals/request.test.js +14 -4
- package/test/reply-error.test.js +25 -0
- package/test/request-id.test.js +131 -0
- package/test/route.test.js +135 -0
- package/test/serial/logger.0.test.js +6 -1
- package/test/server.test.js +64 -2
- package/test/stream.test.js +4 -4
- package/test/types/errors.test-d.ts +82 -0
- package/test/types/fastify.test-d.ts +4 -0
- package/test/types/instance.test-d.ts +37 -0
- package/test/types/reply.test-d.ts +26 -0
- package/test/types/route.test-d.ts +3 -0
- package/test/types/type-provider.test-d.ts +56 -0
- package/types/errors.d.ts +29 -23
- package/types/instance.d.ts +33 -7
- package/types/logger.d.ts +25 -0
- package/types/reply.d.ts +8 -6
- package/types/route.d.ts +2 -1
- package/types/type-provider.d.ts +2 -1
- package/types/utils.d.ts +9 -0
package/lib/logger.js
CHANGED
|
@@ -107,27 +107,64 @@ function createLogger (options) {
|
|
|
107
107
|
* of a Fastify compatible logger.
|
|
108
108
|
*
|
|
109
109
|
* @param {object} logger Object to validate.
|
|
110
|
+
* @param {boolean?} strict `true` if the object must be a logger (always throw if any methods missing)
|
|
110
111
|
*
|
|
111
112
|
* @returns {boolean} `true` when the logger meets the requirements.
|
|
112
113
|
*
|
|
113
114
|
* @throws {FST_ERR_LOG_INVALID_LOGGER} When the logger object is
|
|
114
115
|
* missing required methods.
|
|
115
116
|
*/
|
|
116
|
-
function validateLogger (logger) {
|
|
117
|
+
function validateLogger (logger, strict) {
|
|
117
118
|
const methods = ['info', 'error', 'debug', 'fatal', 'warn', 'trace', 'child']
|
|
118
|
-
const missingMethods =
|
|
119
|
+
const missingMethods = logger
|
|
120
|
+
? methods.filter(method => !logger[method] || typeof logger[method] !== 'function')
|
|
121
|
+
: methods
|
|
119
122
|
|
|
120
123
|
if (!missingMethods.length) {
|
|
121
124
|
return true
|
|
122
|
-
} else if (missingMethods.length === methods.length) {
|
|
125
|
+
} else if ((missingMethods.length === methods.length) && !strict) {
|
|
123
126
|
return false
|
|
124
127
|
} else {
|
|
125
128
|
throw FST_ERR_LOG_INVALID_LOGGER(missingMethods.join(','))
|
|
126
129
|
}
|
|
127
130
|
}
|
|
128
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Utility for creating a child logger with the appropriate bindings, logger factory
|
|
134
|
+
* and validation.
|
|
135
|
+
* @param {object} context
|
|
136
|
+
* @param {import('../fastify').FastifyBaseLogger} logger
|
|
137
|
+
* @param {import('../fastify').RawRequestDefaultExpression<any>} req
|
|
138
|
+
* @param {string} reqId
|
|
139
|
+
* @param {import('../types/logger.js').ChildLoggerOptions?} loggerOpts
|
|
140
|
+
*/
|
|
141
|
+
function createChildLogger (context, logger, req, reqId, loggerOpts) {
|
|
142
|
+
const loggerBindings = {
|
|
143
|
+
[context.requestIdLogLabel]: reqId
|
|
144
|
+
}
|
|
145
|
+
const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req)
|
|
146
|
+
|
|
147
|
+
// Optimisation: bypass validation if the factory is our own default factory
|
|
148
|
+
if (context.childLoggerFactory !== defaultChildLoggerFactory) {
|
|
149
|
+
validateLogger(child, true) // throw if the child is not a valid logger
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return child
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @param {import('../fastify.js').FastifyBaseLogger} logger
|
|
157
|
+
* @param {import('../types/logger.js').Bindings} bindings
|
|
158
|
+
* @param {import('../types/logger.js').ChildLoggerOptions} opts
|
|
159
|
+
*/
|
|
160
|
+
function defaultChildLoggerFactory (logger, bindings, opts) {
|
|
161
|
+
return logger.child(bindings, opts)
|
|
162
|
+
}
|
|
163
|
+
|
|
129
164
|
module.exports = {
|
|
130
165
|
createLogger,
|
|
166
|
+
createChildLogger,
|
|
167
|
+
defaultChildLoggerFactory,
|
|
131
168
|
serializers,
|
|
132
169
|
now
|
|
133
170
|
}
|
package/lib/reply.js
CHANGED
|
@@ -23,7 +23,7 @@ const {
|
|
|
23
23
|
kOptions,
|
|
24
24
|
kRouteContext
|
|
25
25
|
} = require('./symbols.js')
|
|
26
|
-
const {
|
|
26
|
+
const { onSendHookRunner, onResponseHookRunner, onPreHandlerHookRunner } = require('./hooks')
|
|
27
27
|
|
|
28
28
|
const internals = require('./handleRequest')[Symbol.for('internals')]
|
|
29
29
|
const loggerUtils = require('./logger')
|
|
@@ -97,7 +97,7 @@ Object.defineProperties(Reply.prototype, {
|
|
|
97
97
|
|
|
98
98
|
// We throw only if sent was overwritten from Fastify
|
|
99
99
|
if (this.sent && this[kReplyHijacked]) {
|
|
100
|
-
throw new FST_ERR_REP_ALREADY_SENT()
|
|
100
|
+
throw new FST_ERR_REP_ALREADY_SENT(this.request.url, this.request.method)
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
this[kReplyHijacked] = true
|
|
@@ -124,7 +124,7 @@ Reply.prototype.send = function (payload) {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
if (this.sent) {
|
|
127
|
-
this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT() }
|
|
127
|
+
this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT(this.request.url, this.request.method) })
|
|
128
128
|
return this
|
|
129
129
|
}
|
|
130
130
|
|
|
@@ -539,6 +539,18 @@ function wrapOnSendEnd (err, request, reply, payload) {
|
|
|
539
539
|
}
|
|
540
540
|
}
|
|
541
541
|
|
|
542
|
+
function safeWriteHead (reply, statusCode) {
|
|
543
|
+
const res = reply.raw
|
|
544
|
+
try {
|
|
545
|
+
res.writeHead(statusCode, reply[kReplyHeaders])
|
|
546
|
+
} catch (err) {
|
|
547
|
+
if (err.code === 'ERR_HTTP_HEADERS_SENT') {
|
|
548
|
+
reply.log.warn(`Reply was already sent, did you forget to "return reply" in the "${reply.request.raw.url}" (${reply.request.raw.method}) route?`)
|
|
549
|
+
}
|
|
550
|
+
throw err
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
542
554
|
function onSendEnd (reply, payload) {
|
|
543
555
|
const res = reply.raw
|
|
544
556
|
const req = reply.request
|
|
@@ -569,7 +581,7 @@ function onSendEnd (reply, payload) {
|
|
|
569
581
|
reply[kReplyHeaders]['content-length'] = '0'
|
|
570
582
|
}
|
|
571
583
|
|
|
572
|
-
|
|
584
|
+
safeWriteHead(reply, statusCode)
|
|
573
585
|
sendTrailer(payload, res, reply)
|
|
574
586
|
return
|
|
575
587
|
}
|
|
@@ -594,7 +606,7 @@ function onSendEnd (reply, payload) {
|
|
|
594
606
|
}
|
|
595
607
|
}
|
|
596
608
|
|
|
597
|
-
|
|
609
|
+
safeWriteHead(reply, statusCode)
|
|
598
610
|
// write payload first
|
|
599
611
|
res.write(payload)
|
|
600
612
|
// then send trailers
|
|
@@ -755,9 +767,8 @@ function setupResponseListeners (reply) {
|
|
|
755
767
|
const ctx = reply[kRouteContext]
|
|
756
768
|
|
|
757
769
|
if (ctx && ctx.onResponse !== null) {
|
|
758
|
-
|
|
770
|
+
onResponseHookRunner(
|
|
759
771
|
ctx.onResponse,
|
|
760
|
-
onResponseIterator,
|
|
761
772
|
reply.request,
|
|
762
773
|
reply,
|
|
763
774
|
onResponseCallback
|
|
@@ -771,10 +782,6 @@ function setupResponseListeners (reply) {
|
|
|
771
782
|
reply.raw.on('error', onResFinished)
|
|
772
783
|
}
|
|
773
784
|
|
|
774
|
-
function onResponseIterator (fn, request, reply, next) {
|
|
775
|
-
return fn(request, reply, next)
|
|
776
|
-
}
|
|
777
|
-
|
|
778
785
|
function onResponseCallback (err, request, reply) {
|
|
779
786
|
if (reply.log[kDisableRequestLogging]) {
|
|
780
787
|
return
|
|
@@ -839,9 +846,8 @@ function notFound (reply) {
|
|
|
839
846
|
|
|
840
847
|
// preHandler hook
|
|
841
848
|
if (reply[kRouteContext].preHandler !== null) {
|
|
842
|
-
|
|
849
|
+
onPreHandlerHookRunner(
|
|
843
850
|
reply[kRouteContext].preHandler,
|
|
844
|
-
hookIterator,
|
|
845
851
|
reply.request,
|
|
846
852
|
reply,
|
|
847
853
|
internals.preHandlerCallback
|
package/lib/reqIdGenFactory.js
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* @callback GenerateRequestId
|
|
5
|
+
* @param {Object} req
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} [requestIdHeader]
|
|
11
|
+
* @param {GenerateRequestId} [optGenReqId]
|
|
12
|
+
* @returns {GenerateRequestId}
|
|
13
|
+
*/
|
|
14
|
+
function reqIdGenFactory (requestIdHeader, optGenReqId) {
|
|
4
15
|
// 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8).
|
|
5
16
|
// With this upper bound, if you'll be generating 1k ids/sec, you're going to hit it in ~25 days.
|
|
6
17
|
// This is very likely to happen in real-world applications, hence the limit is enforced.
|
|
7
18
|
// Growing beyond this value will make the id generation slower and cause a deopt.
|
|
8
19
|
// In the worst cases, it will become a float, losing accuracy.
|
|
9
20
|
const maxInt = 2147483647
|
|
21
|
+
|
|
10
22
|
let nextReqId = 0
|
|
11
|
-
function defaultGenReqId (
|
|
23
|
+
function defaultGenReqId (_req) {
|
|
12
24
|
nextReqId = (nextReqId + 1) & maxInt
|
|
13
25
|
return `req-${nextReqId.toString(36)}`
|
|
14
26
|
}
|
|
@@ -16,7 +28,6 @@ module.exports = function (requestIdHeader, optGenReqId) {
|
|
|
16
28
|
const genReqId = optGenReqId || defaultGenReqId
|
|
17
29
|
|
|
18
30
|
if (requestIdHeader) {
|
|
19
|
-
// requestIdHeader = typeof requestIdHeader === 'string' ? requestIdHeader : 'request-id'
|
|
20
31
|
return function (req) {
|
|
21
32
|
return req.headers[requestIdHeader] || genReqId(req)
|
|
22
33
|
}
|
|
@@ -24,3 +35,7 @@ module.exports = function (requestIdHeader, optGenReqId) {
|
|
|
24
35
|
|
|
25
36
|
return genReqId
|
|
26
37
|
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
reqIdGenFactory
|
|
41
|
+
}
|
package/lib/route.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const FindMyWay = require('find-my-way')
|
|
4
4
|
const Context = require('./context')
|
|
5
5
|
const handleRequest = require('./handleRequest')
|
|
6
|
-
const {
|
|
6
|
+
const { onRequestAbortHookRunner, lifecycleHooks, preParsingHookRunner, onTimeoutHookRunner, onRequestHookRunner } = require('./hooks')
|
|
7
7
|
const { supportedMethods } = require('./httpMethods')
|
|
8
8
|
const { normalizeSchema } = require('./schemas')
|
|
9
9
|
const { parseHeadOnSendHandlers } = require('./headRoute')
|
|
@@ -20,7 +20,6 @@ const {
|
|
|
20
20
|
FST_ERR_DEFAULT_ROUTE_INVALID_TYPE,
|
|
21
21
|
FST_ERR_DUPLICATED_ROUTE,
|
|
22
22
|
FST_ERR_INVALID_URL,
|
|
23
|
-
FST_ERR_SEND_UNDEFINED_ERR,
|
|
24
23
|
FST_ERR_HOOK_INVALID_HANDLER,
|
|
25
24
|
FST_ERR_ROUTE_OPTIONS_NOT_OBJ,
|
|
26
25
|
FST_ERR_ROUTE_DUPLICATED_HANDLER,
|
|
@@ -51,13 +50,13 @@ const {
|
|
|
51
50
|
kRouteContext
|
|
52
51
|
} = require('./symbols.js')
|
|
53
52
|
const { buildErrorHandler } = require('./error-handler')
|
|
53
|
+
const { createChildLogger } = require('./logger')
|
|
54
54
|
|
|
55
55
|
function buildRouting (options) {
|
|
56
56
|
const router = FindMyWay(options.config)
|
|
57
57
|
|
|
58
58
|
let avvio
|
|
59
59
|
let fourOhFour
|
|
60
|
-
let requestIdLogLabel
|
|
61
60
|
let logger
|
|
62
61
|
let hasLogger
|
|
63
62
|
let setupResponseListeners
|
|
@@ -74,6 +73,10 @@ function buildRouting (options) {
|
|
|
74
73
|
let closing = false
|
|
75
74
|
|
|
76
75
|
return {
|
|
76
|
+
/**
|
|
77
|
+
* @param {import('../fastify').FastifyServerOptions} options
|
|
78
|
+
* @param {*} fastifyArgs
|
|
79
|
+
*/
|
|
77
80
|
setup (options, fastifyArgs) {
|
|
78
81
|
avvio = fastifyArgs.avvio
|
|
79
82
|
fourOhFour = fastifyArgs.fourOhFour
|
|
@@ -84,7 +87,6 @@ function buildRouting (options) {
|
|
|
84
87
|
validateHTTPVersion = fastifyArgs.validateHTTPVersion
|
|
85
88
|
|
|
86
89
|
globalExposeHeadRoutes = options.exposeHeadRoutes
|
|
87
|
-
requestIdLogLabel = options.requestIdLogLabel
|
|
88
90
|
genReqId = options.genReqId
|
|
89
91
|
disableRequestLogging = options.disableRequestLogging
|
|
90
92
|
ignoreTrailingSlash = options.ignoreTrailingSlash
|
|
@@ -168,7 +170,10 @@ function buildRouting (options) {
|
|
|
168
170
|
) !== null
|
|
169
171
|
}
|
|
170
172
|
|
|
171
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Route management
|
|
175
|
+
* @param {{ options: import('../fastify').RouteOptions, isFastify: boolean }}
|
|
176
|
+
*/
|
|
172
177
|
function route ({ options, isFastify }) {
|
|
173
178
|
// Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator
|
|
174
179
|
const opts = { ...options }
|
|
@@ -282,6 +287,7 @@ function buildRouting (options) {
|
|
|
282
287
|
handler: opts.handler.bind(this),
|
|
283
288
|
config,
|
|
284
289
|
errorHandler: opts.errorHandler,
|
|
290
|
+
childLoggerFactory: opts.childLoggerFactory,
|
|
285
291
|
bodyLimit: opts.bodyLimit,
|
|
286
292
|
logLevel: opts.logLevel,
|
|
287
293
|
logSerializers: opts.logSerializers,
|
|
@@ -399,10 +405,6 @@ function buildRouting (options) {
|
|
|
399
405
|
function routeHandler (req, res, params, context, query) {
|
|
400
406
|
const id = genReqId(req)
|
|
401
407
|
|
|
402
|
-
const loggerBinding = {
|
|
403
|
-
[requestIdLogLabel]: id
|
|
404
|
-
}
|
|
405
|
-
|
|
406
408
|
const loggerOpts = {
|
|
407
409
|
level: context.logLevel
|
|
408
410
|
}
|
|
@@ -410,7 +412,7 @@ function buildRouting (options) {
|
|
|
410
412
|
if (context.logSerializers) {
|
|
411
413
|
loggerOpts.serializers = context.logSerializers
|
|
412
414
|
}
|
|
413
|
-
const childLogger =
|
|
415
|
+
const childLogger = createChildLogger(context, logger, req, id, loggerOpts)
|
|
414
416
|
childLogger[kDisableRequestLogging] = disableRequestLogging
|
|
415
417
|
|
|
416
418
|
// TODO: The check here should be removed once https://github.com/nodejs/node/issues/43115 resolve in core.
|
|
@@ -477,9 +479,8 @@ function buildRouting (options) {
|
|
|
477
479
|
}
|
|
478
480
|
|
|
479
481
|
if (context.onRequest !== null) {
|
|
480
|
-
|
|
482
|
+
onRequestHookRunner(
|
|
481
483
|
context.onRequest,
|
|
482
|
-
hookIterator,
|
|
483
484
|
request,
|
|
484
485
|
reply,
|
|
485
486
|
runPreParsing
|
|
@@ -494,7 +495,6 @@ function buildRouting (options) {
|
|
|
494
495
|
if (req.aborted) {
|
|
495
496
|
onRequestAbortHookRunner(
|
|
496
497
|
context.onRequestAbort,
|
|
497
|
-
hookIterator,
|
|
498
498
|
request,
|
|
499
499
|
handleOnRequestAbortHooksErrors.bind(null, reply)
|
|
500
500
|
)
|
|
@@ -519,9 +519,8 @@ function handleOnRequestAbortHooksErrors (reply, err) {
|
|
|
519
519
|
|
|
520
520
|
function handleTimeout () {
|
|
521
521
|
const { context, request, reply } = this._meta
|
|
522
|
-
|
|
522
|
+
onTimeoutHookRunner(
|
|
523
523
|
context.onTimeout,
|
|
524
|
-
hookIterator,
|
|
525
524
|
request,
|
|
526
525
|
reply,
|
|
527
526
|
noop
|
|
@@ -570,52 +569,6 @@ function runPreParsing (err, request, reply) {
|
|
|
570
569
|
}
|
|
571
570
|
}
|
|
572
571
|
|
|
573
|
-
function preParsingHookRunner (functions, request, reply, cb) {
|
|
574
|
-
let i = 0
|
|
575
|
-
|
|
576
|
-
function next (err, stream) {
|
|
577
|
-
if (reply.sent) {
|
|
578
|
-
return
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
if (typeof stream !== 'undefined') {
|
|
582
|
-
request[kRequestPayloadStream] = stream
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
if (err || i === functions.length) {
|
|
586
|
-
cb(err, request, reply)
|
|
587
|
-
return
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
const fn = functions[i++]
|
|
591
|
-
let result
|
|
592
|
-
try {
|
|
593
|
-
result = fn(request, reply, request[kRequestPayloadStream], next)
|
|
594
|
-
} catch (error) {
|
|
595
|
-
next(error)
|
|
596
|
-
return
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
if (result && typeof result.then === 'function') {
|
|
600
|
-
result.then(handleResolve, handleReject)
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
function handleResolve (stream) {
|
|
605
|
-
next(null, stream)
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function handleReject (err) {
|
|
609
|
-
if (!err) {
|
|
610
|
-
err = new FST_ERR_SEND_UNDEFINED_ERR()
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
next(err)
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
next(null, request[kRequestPayloadStream])
|
|
617
|
-
}
|
|
618
|
-
|
|
619
572
|
/**
|
|
620
573
|
* Used within the route handler as a `net.Socket.close` event handler.
|
|
621
574
|
* The purpose is to remove a socket from the tracked sockets collection when
|
package/lib/schema-controller.js
CHANGED
|
@@ -50,6 +50,8 @@ class SchemaController {
|
|
|
50
50
|
this.schemaBucket = this.opts.bucket(parent.getSchemas())
|
|
51
51
|
this.validatorCompiler = parent.getValidatorCompiler()
|
|
52
52
|
this.serializerCompiler = parent.getSerializerCompiler()
|
|
53
|
+
this.isCustomValidatorCompiler = parent.isCustomValidatorCompiler
|
|
54
|
+
this.isCustomSerializerCompiler = parent.isCustomSerializerCompiler
|
|
53
55
|
this.parent = parent
|
|
54
56
|
} else {
|
|
55
57
|
this.schemaBucket = this.opts.bucket()
|
package/lib/server.js
CHANGED
|
@@ -9,7 +9,8 @@ const { kState, kOptions, kServerBindings } = require('./symbols')
|
|
|
9
9
|
const {
|
|
10
10
|
FST_ERR_HTTP2_INVALID_VERSION,
|
|
11
11
|
FST_ERR_REOPENED_CLOSE_SERVER,
|
|
12
|
-
FST_ERR_REOPENED_SERVER
|
|
12
|
+
FST_ERR_REOPENED_SERVER,
|
|
13
|
+
FST_ERR_LISTEN_OPTIONS_INVALID
|
|
13
14
|
} = require('./errors')
|
|
14
15
|
|
|
15
16
|
module.exports.createServer = createServer
|
|
@@ -22,8 +23,6 @@ function defaultResolveServerListeningText (address) {
|
|
|
22
23
|
function createServer (options, httpHandler) {
|
|
23
24
|
const server = getServerInstance(options, httpHandler)
|
|
24
25
|
|
|
25
|
-
return { server, listen }
|
|
26
|
-
|
|
27
26
|
// `this` is the Fastify object
|
|
28
27
|
function listen (listenOptions, ...args) {
|
|
29
28
|
let cb = args.slice(-1).pop()
|
|
@@ -48,6 +47,20 @@ function createServer (options, httpHandler) {
|
|
|
48
47
|
} else {
|
|
49
48
|
listenOptions.cb = cb
|
|
50
49
|
}
|
|
50
|
+
if (listenOptions.signal) {
|
|
51
|
+
if (typeof listenOptions.signal.on !== 'function' && typeof listenOptions.signal.addEventListener !== 'function') {
|
|
52
|
+
throw new FST_ERR_LISTEN_OPTIONS_INVALID('Invalid options.signal')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (listenOptions.signal.aborted) {
|
|
56
|
+
this.close()
|
|
57
|
+
} else {
|
|
58
|
+
const onAborted = () => {
|
|
59
|
+
this.close()
|
|
60
|
+
}
|
|
61
|
+
listenOptions.signal.addEventListener('abort', onAborted, { once: true })
|
|
62
|
+
}
|
|
63
|
+
}
|
|
51
64
|
|
|
52
65
|
// If we have a path specified, don't default host to 'localhost' so we don't end up listening
|
|
53
66
|
// on both path and host
|
|
@@ -99,6 +112,8 @@ function createServer (options, httpHandler) {
|
|
|
99
112
|
|
|
100
113
|
this.ready(listenCallback.call(this, server, listenOptions))
|
|
101
114
|
}
|
|
115
|
+
|
|
116
|
+
return { server, listen }
|
|
102
117
|
}
|
|
103
118
|
|
|
104
119
|
function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, onListen) {
|
|
@@ -138,17 +153,16 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o
|
|
|
138
153
|
// pace through the `onClose` hook.
|
|
139
154
|
// It also allows them to handle possible connections already
|
|
140
155
|
// attached to them if any.
|
|
156
|
+
/* c8 ignore next 20 */
|
|
141
157
|
this.onClose((instance, done) => {
|
|
142
158
|
if (instance[kState].listening) {
|
|
143
159
|
// No new TCP connections are accepted
|
|
144
160
|
// We swallow any error from the secondary
|
|
145
161
|
// server
|
|
146
162
|
secondaryServer.close(() => done())
|
|
147
|
-
/* istanbul ignore next: Cannot test this without Node.js core support */
|
|
148
163
|
if (serverOpts.forceCloseConnections === 'idle') {
|
|
149
164
|
// Not needed in Node 19
|
|
150
165
|
secondaryServer.closeIdleConnections()
|
|
151
|
-
/* istanbul ignore next: Cannot test this without Node.js core support */
|
|
152
166
|
} else if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections) {
|
|
153
167
|
secondaryServer.closeAllConnections()
|
|
154
168
|
}
|
|
@@ -216,9 +230,10 @@ function listenCallback (server, listenOptions) {
|
|
|
216
230
|
}
|
|
217
231
|
|
|
218
232
|
server.once('error', wrap)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
233
|
+
if (!this[kState].closing) {
|
|
234
|
+
server.listen(listenOptions, wrap)
|
|
235
|
+
this[kState].listening = true
|
|
236
|
+
}
|
|
222
237
|
}
|
|
223
238
|
}
|
|
224
239
|
|
package/lib/symbols.js
CHANGED
|
@@ -55,6 +55,7 @@ const keys = {
|
|
|
55
55
|
// This symbol is only meant to be used for fastify tests and should not be used for any other purpose
|
|
56
56
|
kTestInternals: Symbol('fastify.testInternals'),
|
|
57
57
|
kErrorHandler: Symbol('fastify.errorHandler'),
|
|
58
|
+
kChildLoggerFactory: Symbol('fastify.childLoggerFactory'),
|
|
58
59
|
kHasBeenDecorated: Symbol('fastify.hasBeenDecorated'),
|
|
59
60
|
kKeepAliveConnections: Symbol('fastify.keepAliveConnections'),
|
|
60
61
|
kRouteByFastify: Symbol('fastify.routeByFastify')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.21.0",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -10,23 +10,22 @@
|
|
|
10
10
|
"benchmark": "npx concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"npx autocannon -c 100 -d 30 -p 10 localhost:3000/\"",
|
|
11
11
|
"benchmark:parser": "npx concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"npx autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"",
|
|
12
12
|
"build:validation": "node build/build-error-serializer.js && node build/build-validation.js",
|
|
13
|
-
"coverage": "
|
|
14
|
-
"coverage:ci": "
|
|
15
|
-
"coverage:ci-check-coverage": "
|
|
16
|
-
"license-checker": "license-checker --production --onlyAllow=\"MIT;ISC;BSD-3-Clause;BSD-2-Clause\"",
|
|
13
|
+
"coverage": "npm run unit -- --coverage-report=html",
|
|
14
|
+
"coverage:ci": "c8 --reporter=lcov tap --coverage-report=html --no-browser --no-check-coverage",
|
|
15
|
+
"coverage:ci-check-coverage": "c8 check-coverage --branches 100 --functions 100 --lines 100 --statements 100",
|
|
17
16
|
"lint": "npm run lint:standard && npm run lint:typescript && npm run lint:markdown",
|
|
18
17
|
"lint:fix": "standard --fix",
|
|
19
18
|
"lint:markdown": "markdownlint-cli2",
|
|
20
19
|
"lint:standard": "standard | snazzy",
|
|
21
20
|
"lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts",
|
|
22
21
|
"prepublishOnly": "cross-env PREPUBLISH=true tap --no-check-coverage test/build/**.test.js && npm run test:validator:integrity",
|
|
23
|
-
"test": "npm run lint &&
|
|
22
|
+
"test": "npm run lint && npm run unit && npm run test:typescript",
|
|
24
23
|
"test:ci": "npm run unit -- --cov --coverage-report=lcovonly && npm run test:typescript",
|
|
25
24
|
"test:report": "npm run lint && npm run unit:report && npm run test:typescript",
|
|
26
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/configValidator.js",
|
|
27
26
|
"test:typescript": "tsc test/types/import.ts && tsd",
|
|
28
27
|
"test:watch": "npm run unit -- -w --no-coverage-report -R terse",
|
|
29
|
-
"unit": "tap",
|
|
28
|
+
"unit": "c8 tap",
|
|
30
29
|
"unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml",
|
|
31
30
|
"unit:report": "tap --cov --coverage-report=html --coverage-report=cobertura | tee out.tap"
|
|
32
31
|
},
|
|
@@ -134,7 +133,7 @@
|
|
|
134
133
|
"homepage": "https://www.fastify.io/",
|
|
135
134
|
"devDependencies": {
|
|
136
135
|
"@fastify/pre-commit": "^2.0.2",
|
|
137
|
-
"@sinclair/typebox": "^0.
|
|
136
|
+
"@sinclair/typebox": "^0.29.1",
|
|
138
137
|
"@sinonjs/fake-timers": "^11.0.0",
|
|
139
138
|
"@types/node": "^20.1.0",
|
|
140
139
|
"@typescript-eslint/eslint-plugin": "^5.59.2",
|
|
@@ -145,6 +144,7 @@
|
|
|
145
144
|
"ajv-i18n": "^4.2.0",
|
|
146
145
|
"ajv-merge-patch": "^5.0.1",
|
|
147
146
|
"branch-comparer": "^1.1.0",
|
|
147
|
+
"c8": "^8.0.0",
|
|
148
148
|
"cross-env": "^7.0.3",
|
|
149
149
|
"eslint": "^8.39.0",
|
|
150
150
|
"eslint-config-standard": "^17.0.0",
|
|
@@ -161,10 +161,8 @@
|
|
|
161
161
|
"joi": "^17.9.2",
|
|
162
162
|
"json-schema-to-ts": "^2.9.1",
|
|
163
163
|
"JSONStream": "^1.3.5",
|
|
164
|
-
"license-checker": "^25.0.1",
|
|
165
164
|
"markdownlint-cli2": "^0.8.1",
|
|
166
165
|
"proxyquire": "^2.1.3",
|
|
167
|
-
"pump": "^3.0.0",
|
|
168
166
|
"self-cert": "^2.0.0",
|
|
169
167
|
"send": "^0.18.0",
|
|
170
168
|
"simple-get": "^4.0.1",
|
package/test/500s.test.js
CHANGED
|
@@ -9,6 +9,7 @@ test('default 500', t => {
|
|
|
9
9
|
t.plan(4)
|
|
10
10
|
|
|
11
11
|
const fastify = Fastify()
|
|
12
|
+
t.teardown(fastify.close.bind(fastify))
|
|
12
13
|
|
|
13
14
|
fastify.get('/', function (req, reply) {
|
|
14
15
|
reply.send(new Error('kaboom'))
|
|
@@ -33,6 +34,7 @@ test('custom 500', t => {
|
|
|
33
34
|
t.plan(6)
|
|
34
35
|
|
|
35
36
|
const fastify = Fastify()
|
|
37
|
+
t.teardown(fastify.close.bind(fastify))
|
|
36
38
|
|
|
37
39
|
fastify.get('/', function (req, reply) {
|
|
38
40
|
reply.send(new Error('kaboom'))
|
|
@@ -62,6 +64,7 @@ test('encapsulated 500', t => {
|
|
|
62
64
|
t.plan(10)
|
|
63
65
|
|
|
64
66
|
const fastify = Fastify()
|
|
67
|
+
t.teardown(fastify.close.bind(fastify))
|
|
65
68
|
|
|
66
69
|
fastify.get('/', function (req, reply) {
|
|
67
70
|
reply.send(new Error('kaboom'))
|
|
@@ -113,6 +116,7 @@ test('custom 500 with hooks', t => {
|
|
|
113
116
|
t.plan(7)
|
|
114
117
|
|
|
115
118
|
const fastify = Fastify()
|
|
119
|
+
t.teardown(fastify.close.bind(fastify))
|
|
116
120
|
|
|
117
121
|
fastify.get('/', function (req, reply) {
|
|
118
122
|
reply.send(new Error('kaboom'))
|
|
@@ -166,3 +170,21 @@ test('cannot set errorHandler after binding', t => {
|
|
|
166
170
|
}
|
|
167
171
|
})
|
|
168
172
|
})
|
|
173
|
+
|
|
174
|
+
test('cannot set childLoggerFactory after binding', t => {
|
|
175
|
+
t.plan(2)
|
|
176
|
+
|
|
177
|
+
const fastify = Fastify()
|
|
178
|
+
t.teardown(fastify.close.bind(fastify))
|
|
179
|
+
|
|
180
|
+
fastify.listen({ port: 0 }, err => {
|
|
181
|
+
t.error(err)
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
fastify.setChildLoggerFactory(() => { })
|
|
185
|
+
t.fail()
|
|
186
|
+
} catch (e) {
|
|
187
|
+
t.pass()
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
})
|
package/test/async-await.test.js
CHANGED
|
@@ -124,7 +124,7 @@ test('ignore the result of the promise if reply.send is called beforehand (objec
|
|
|
124
124
|
})
|
|
125
125
|
|
|
126
126
|
test('server logs an error if reply.send is called and a value is returned via async/await', t => {
|
|
127
|
-
const lines = ['incoming request', 'request completed', 'Reply already sent']
|
|
127
|
+
const lines = ['incoming request', 'request completed', 'Reply was already sent, did you forget to "return reply" in "/" (GET)?']
|
|
128
128
|
t.plan(lines.length + 2)
|
|
129
129
|
|
|
130
130
|
const splitStream = split(JSON.parse)
|