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
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { defaultInitOptions } = require('./initial-config-validation')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Internal logger layer that wraps Fastify's log calls with
|
|
7
|
+
* request-lifecycle awareness (disable-check, level selection).
|
|
8
|
+
* Keeps the surface intentionally small so hot paths stay fast.
|
|
9
|
+
*
|
|
10
|
+
* Users can extend this class to customize internal log lines.
|
|
11
|
+
*/
|
|
12
|
+
class LogController {
|
|
13
|
+
/**
|
|
14
|
+
* @param {object} [options]
|
|
15
|
+
* @param {boolean | ((req: object) => boolean)} [options.disableRequestLogging=false]
|
|
16
|
+
* When `true` (or a function returning `true`), per-request log lines
|
|
17
|
+
* (incoming, completed, errors) are suppressed.
|
|
18
|
+
* @param {string} [options.requestIdLogLabel='reqId']
|
|
19
|
+
* The label used for the request identifier when logging the request.
|
|
20
|
+
*/
|
|
21
|
+
constructor (options) {
|
|
22
|
+
const opts = options || {}
|
|
23
|
+
this.disableRequestLogging = opts.disableRequestLogging || defaultInitOptions.disableRequestLogging
|
|
24
|
+
this.isDisableRequestLoggingFunction = typeof this.disableRequestLogging === 'function'
|
|
25
|
+
this.requestIdLogLabel = opts.requestIdLogLabel || defaultInitOptions.requestIdLogLabel
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Checks whether request logging is disabled for the given request.
|
|
30
|
+
*
|
|
31
|
+
* @param {object} req Raw or Fastify request object.
|
|
32
|
+
* @returns {boolean} `true` when logging should be skipped.
|
|
33
|
+
*/
|
|
34
|
+
isLogDisabled (req) {
|
|
35
|
+
return this.isDisableRequestLoggingFunction
|
|
36
|
+
? this.disableRequestLogging(req)
|
|
37
|
+
: this.disableRequestLogging
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Logs an incoming request at `info` level.
|
|
42
|
+
*
|
|
43
|
+
* @param {object} request Fastify request object.
|
|
44
|
+
* @param {object} reply Fastify reply object.
|
|
45
|
+
* @param {object} [metadata] Extra contextual data (unused).
|
|
46
|
+
*/
|
|
47
|
+
incomingRequest (request, reply, metadata) {
|
|
48
|
+
if (this.isLogDisabled(request)) { return }
|
|
49
|
+
|
|
50
|
+
request.log.info({ req: request }, 'incoming request')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Logs the outcome of a completed request.
|
|
55
|
+
* Uses `error` level when an error is present, `info` otherwise.
|
|
56
|
+
*
|
|
57
|
+
* @param {Error | null} error Error that occurred during the response, if any.
|
|
58
|
+
* @param {object} request Fastify request object.
|
|
59
|
+
* @param {object} reply Fastify reply object.
|
|
60
|
+
* @param {object} [metadata] Extra contextual data (unused).
|
|
61
|
+
*/
|
|
62
|
+
requestCompleted (error, request, reply, metadata) {
|
|
63
|
+
if (this.isLogDisabled(request)) { return }
|
|
64
|
+
|
|
65
|
+
if (error) {
|
|
66
|
+
reply.log.error({ res: reply, err: error, responseTime: reply.elapsedTime }, 'request errored')
|
|
67
|
+
} else {
|
|
68
|
+
reply.log.info({ res: reply, responseTime: reply.elapsedTime }, 'request completed')
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Logs an error handled by the default error handler.
|
|
74
|
+
* Uses `error` for 5xx status codes, `info` for 4xx.
|
|
75
|
+
*
|
|
76
|
+
* @param {Error} error The error being handled.
|
|
77
|
+
* @param {object} request Fastify request object.
|
|
78
|
+
* @param {object} reply Fastify reply object (must have `statusCode`).
|
|
79
|
+
* @param {object} [metadata] Extra contextual data (unused).
|
|
80
|
+
*/
|
|
81
|
+
defaultErrorLog (error, request, reply, metadata) {
|
|
82
|
+
if (this.isLogDisabled(request)) { return }
|
|
83
|
+
|
|
84
|
+
if (reply.statusCode >= 500) {
|
|
85
|
+
reply.log.error({ req: request, res: reply, err: error }, error?.message)
|
|
86
|
+
} else {
|
|
87
|
+
reply.log.info({ res: reply, err: error }, error?.message)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Logs stream-level errors that occur after headers have been sent.
|
|
93
|
+
* Premature closes are logged at `info`; other errors at `warn`.
|
|
94
|
+
*
|
|
95
|
+
* @param {Error} error The stream error.
|
|
96
|
+
* @param {object} request Fastify request object.
|
|
97
|
+
* @param {object} reply Fastify reply object.
|
|
98
|
+
* @param {object} [metadata] Extra contextual data (unused).
|
|
99
|
+
*/
|
|
100
|
+
streamError (error, request, reply, metadata) {
|
|
101
|
+
if (this.isLogDisabled(request)) { return }
|
|
102
|
+
|
|
103
|
+
if (error.code === 'ERR_STREAM_PREMATURE_CLOSE') {
|
|
104
|
+
reply.log.info({ res: reply }, 'stream closed prematurely')
|
|
105
|
+
} else {
|
|
106
|
+
reply.log.warn({ err: error }, 'response terminated with an error with headers already sent')
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Logs a "route not found" message at `info` level.
|
|
112
|
+
* Used by the default 404 handler.
|
|
113
|
+
*
|
|
114
|
+
* @param {object} request Fastify request object.
|
|
115
|
+
* @param {object} reply Fastify reply object.
|
|
116
|
+
* @param {object} [metadata] Extra contextual data (unused).
|
|
117
|
+
*/
|
|
118
|
+
routeNotFound (request, reply, metadata) {
|
|
119
|
+
if (this.isLogDisabled(request)) { return }
|
|
120
|
+
|
|
121
|
+
const { url, method } = request.raw
|
|
122
|
+
request.log.info(`Route ${method}:${url} not found`)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Logs a warning when writeHead fails during error handling.
|
|
127
|
+
*
|
|
128
|
+
* @param {Error} error The error thrown by writeHead.
|
|
129
|
+
* @param {object} request Fastify request object.
|
|
130
|
+
* @param {object} reply Fastify reply object.
|
|
131
|
+
* @param {object} [metadata] Extra contextual data (unused).
|
|
132
|
+
*/
|
|
133
|
+
writeHeadError (error, request, reply, metadata) {
|
|
134
|
+
if (this.isLogDisabled(request)) { return }
|
|
135
|
+
|
|
136
|
+
reply.log.warn(
|
|
137
|
+
{ req: request, res: reply, err: error },
|
|
138
|
+
error?.message
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Logs an error when the serializer for a given status code fails.
|
|
144
|
+
*
|
|
145
|
+
* @param {Error} error The serialization error.
|
|
146
|
+
* @param {object} request Fastify request object.
|
|
147
|
+
* @param {object} reply Fastify reply object.
|
|
148
|
+
* @param {object} metadata Extra contextual data.
|
|
149
|
+
* @param {number} metadata.statusCode The status code that triggered the serializer.
|
|
150
|
+
*/
|
|
151
|
+
serializerError (error, request, reply, metadata) {
|
|
152
|
+
if (this.isLogDisabled(request)) { return }
|
|
153
|
+
|
|
154
|
+
reply.log.error({ err: error, statusCode: metadata.statusCode }, 'The serializer for the given status code failed')
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Logs a 503 Service Unavailable when the server is closing.
|
|
159
|
+
* Always emitted (not gated by `disableRequestLogging`).
|
|
160
|
+
*
|
|
161
|
+
* @param {import('../fastify').FastifyBaseLogger} logger
|
|
162
|
+
* @param {import('../fastify').FastifyInstance} server
|
|
163
|
+
*/
|
|
164
|
+
serviceUnavailable (logger, server) {
|
|
165
|
+
logger.info({ res: { statusCode: 503 } }, 'request aborted - refusing to accept new requests as server is closing')
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = { LogController }
|
package/lib/logger-factory.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { performance } = require('node:perf_hooks')
|
|
4
|
+
|
|
3
5
|
const {
|
|
4
6
|
FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED,
|
|
5
7
|
FST_ERR_LOG_INVALID_LOGGER_CONFIG,
|
|
6
8
|
FST_ERR_LOG_INVALID_LOGGER_INSTANCE,
|
|
7
|
-
FST_ERR_LOG_INVALID_LOGGER
|
|
9
|
+
FST_ERR_LOG_INVALID_LOGGER,
|
|
10
|
+
FST_ERR_LOG_INVALID_LOG_CONTROLLER
|
|
8
11
|
} = require('./errors')
|
|
12
|
+
const {
|
|
13
|
+
kLogController
|
|
14
|
+
} = require('./symbols.js')
|
|
15
|
+
const { LogController } = require('./log-controller.js')
|
|
9
16
|
|
|
10
17
|
/**
|
|
11
18
|
* Utility for creating a child logger with the appropriate bindings, logger factory
|
|
@@ -21,7 +28,7 @@ const {
|
|
|
21
28
|
*/
|
|
22
29
|
function createChildLogger (context, logger, req, reqId, loggerOpts) {
|
|
23
30
|
const loggerBindings = {
|
|
24
|
-
[context.requestIdLogLabel]: reqId
|
|
31
|
+
[context.server[kLogController].requestIdLogLabel]: reqId
|
|
25
32
|
}
|
|
26
33
|
const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req)
|
|
27
34
|
|
|
@@ -122,15 +129,29 @@ function createLogger (options) {
|
|
|
122
129
|
return { logger, hasLogger: true }
|
|
123
130
|
}
|
|
124
131
|
|
|
132
|
+
function createLogController (options) {
|
|
133
|
+
const userController = options.logController
|
|
134
|
+
if (!userController) {
|
|
135
|
+
return new LogController(options)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (userController instanceof LogController) {
|
|
139
|
+
return userController
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
throw new FST_ERR_LOG_INVALID_LOG_CONTROLLER(typeof userController)
|
|
143
|
+
}
|
|
144
|
+
|
|
125
145
|
function now () {
|
|
126
|
-
|
|
127
|
-
return (ts[0] * 1e3) + (ts[1] / 1e6)
|
|
146
|
+
return performance.now()
|
|
128
147
|
}
|
|
129
148
|
|
|
130
149
|
module.exports = {
|
|
131
150
|
createChildLogger,
|
|
132
151
|
defaultChildLoggerFactory,
|
|
152
|
+
createLogController,
|
|
133
153
|
createLogger,
|
|
154
|
+
LogController,
|
|
134
155
|
validateLogger,
|
|
135
156
|
now
|
|
136
157
|
}
|
package/lib/reply.js
CHANGED
|
@@ -16,7 +16,6 @@ const {
|
|
|
16
16
|
kReplyHasStatusCode,
|
|
17
17
|
kReplyIsRunningOnErrorHook,
|
|
18
18
|
kReplyNextErrorHandler,
|
|
19
|
-
kDisableRequestLogging,
|
|
20
19
|
kSchemaResponse,
|
|
21
20
|
kReplyCacheSerializeFns,
|
|
22
21
|
kSchemaController,
|
|
@@ -24,7 +23,8 @@ const {
|
|
|
24
23
|
kRouteContext,
|
|
25
24
|
kTimeoutTimer,
|
|
26
25
|
kOnAbort,
|
|
27
|
-
kRequestSignal
|
|
26
|
+
kRequestSignal,
|
|
27
|
+
kLogController
|
|
28
28
|
} = require('./symbols.js')
|
|
29
29
|
const {
|
|
30
30
|
onSendHookRunner,
|
|
@@ -58,6 +58,7 @@ const {
|
|
|
58
58
|
FST_ERR_DEC_UNDECLARED
|
|
59
59
|
} = require('./errors')
|
|
60
60
|
const decorators = require('./decorate')
|
|
61
|
+
const ContentType = require('./content-type.js')
|
|
61
62
|
|
|
62
63
|
const toString = Object.prototype.toString
|
|
63
64
|
const HTTP2_WRITE_CHUNK_SIZE = 64 * 1024
|
|
@@ -135,6 +136,14 @@ Reply.prototype.hijack = function () {
|
|
|
135
136
|
this.request[kOnAbort] = null
|
|
136
137
|
}
|
|
137
138
|
}
|
|
139
|
+
// Clear socket._meta so hijacked replies (e.g. WebSocket upgrades) do not
|
|
140
|
+
// retain request/reply objects for the lifetime of the keep-alive socket
|
|
141
|
+
// when an onTimeout hook is registered.
|
|
142
|
+
const socket = this.request.raw.socket
|
|
143
|
+
if (socket?._meta?.request === this.request) {
|
|
144
|
+
socket.removeListener('timeout', socket._meta.onTimeout)
|
|
145
|
+
socket._meta = null
|
|
146
|
+
}
|
|
138
147
|
return this
|
|
139
148
|
}
|
|
140
149
|
|
|
@@ -159,8 +168,8 @@ Reply.prototype.send = function (payload) {
|
|
|
159
168
|
return this
|
|
160
169
|
}
|
|
161
170
|
|
|
162
|
-
const contentType = this.getHeader('content-type')
|
|
163
|
-
const hasContentType = contentType
|
|
171
|
+
const contentType = ContentType.from(this.getHeader('content-type'))
|
|
172
|
+
const hasContentType = contentType.isEmpty === false
|
|
164
173
|
|
|
165
174
|
if (payload !== null) {
|
|
166
175
|
if (
|
|
@@ -201,18 +210,17 @@ Reply.prototype.send = function (payload) {
|
|
|
201
210
|
payload = this[kReplySerializer](payload)
|
|
202
211
|
|
|
203
212
|
// The indexOf below also matches custom json mimetypes such as 'application/hal+json' or 'application/ld+json'
|
|
204
|
-
} else if (!hasContentType || contentType.indexOf('json') !== -1) {
|
|
213
|
+
} else if (!hasContentType || contentType.mediaType.indexOf('json') !== -1) {
|
|
205
214
|
if (!hasContentType) {
|
|
206
215
|
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON
|
|
207
|
-
} else if (contentType.
|
|
216
|
+
} else if (contentType.parameters.has('charset') === false) {
|
|
217
|
+
// The lookup is case-insensitive because HTTP parameter names are
|
|
218
|
+
// case-insensitive (RFC 9110 section 5.6.6).
|
|
208
219
|
// If user doesn't set charset, we will set charset to utf-8
|
|
209
|
-
const customContentType = contentType.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
} else {
|
|
214
|
-
this[kReplyHeaders]['content-type'] = `${customContentType}; charset=utf-8`
|
|
215
|
-
}
|
|
220
|
+
const customContentType = contentType.toString()
|
|
221
|
+
// ContentType.toString already handle the trailing ;
|
|
222
|
+
// We can safely append the charset parameter to the end of the string
|
|
223
|
+
this[kReplyHeaders]['content-type'] = `${customContentType}; charset=utf-8`
|
|
216
224
|
}
|
|
217
225
|
|
|
218
226
|
if (typeof payload !== 'string') {
|
|
@@ -688,6 +696,12 @@ function isHttp2Reply (reply) {
|
|
|
688
696
|
|
|
689
697
|
function writePayload (payload, res, reply) {
|
|
690
698
|
if (!isHttp2Reply(reply) || Buffer.byteLength(payload) <= HTTP2_WRITE_CHUNK_SIZE) {
|
|
699
|
+
if (reply[kReplyTrailers] === null) {
|
|
700
|
+
// end(payload) corks the socket, so headers, payload and the
|
|
701
|
+
// finishing chunk are flushed with a single write
|
|
702
|
+
res.end(payload, null, null) // avoid ArgumentsAdaptorTrampoline from V8
|
|
703
|
+
return
|
|
704
|
+
}
|
|
691
705
|
res.write(payload)
|
|
692
706
|
// then send trailers
|
|
693
707
|
sendTrailer(payload, res, reply)
|
|
@@ -727,14 +741,8 @@ function writeHttp2Payload (payload, res, done) {
|
|
|
727
741
|
writeChunk()
|
|
728
742
|
}
|
|
729
743
|
|
|
730
|
-
function logStreamError (
|
|
731
|
-
|
|
732
|
-
if (!logger[kDisableRequestLogging]) {
|
|
733
|
-
logger.info({ res }, 'stream closed prematurely')
|
|
734
|
-
}
|
|
735
|
-
} else {
|
|
736
|
-
logger.warn({ err }, 'response terminated with an error with headers already sent')
|
|
737
|
-
}
|
|
744
|
+
function logStreamError (err, reply, res) {
|
|
745
|
+
reply.server[kLogController].streamError(err, reply.request, reply)
|
|
738
746
|
}
|
|
739
747
|
|
|
740
748
|
function sendWebStream (payload, res, reply) {
|
|
@@ -751,7 +759,7 @@ function sendWebStream (payload, res, reply) {
|
|
|
751
759
|
if (sourceOpen) {
|
|
752
760
|
if (err != null && res.headersSent && !errorLogged) {
|
|
753
761
|
errorLogged = true
|
|
754
|
-
logStreamError(
|
|
762
|
+
logStreamError(err, reply, res)
|
|
755
763
|
}
|
|
756
764
|
reader.cancel().catch(noop)
|
|
757
765
|
}
|
|
@@ -799,7 +807,7 @@ function sendWebStream (payload, res, reply) {
|
|
|
799
807
|
if (res.headersSent || reply.request.raw.aborted === true) {
|
|
800
808
|
if (!errorLogged) {
|
|
801
809
|
errorLogged = true
|
|
802
|
-
logStreamError(
|
|
810
|
+
logStreamError(err, reply, reply)
|
|
803
811
|
}
|
|
804
812
|
res.destroy()
|
|
805
813
|
} else {
|
|
@@ -823,7 +831,7 @@ function sendStream (payload, res, reply) {
|
|
|
823
831
|
if (res.headersSent || reply.request.raw.aborted === true) {
|
|
824
832
|
if (!errorLogged) {
|
|
825
833
|
errorLogged = true
|
|
826
|
-
logStreamError(
|
|
834
|
+
logStreamError(err, reply, reply)
|
|
827
835
|
}
|
|
828
836
|
res.destroy()
|
|
829
837
|
} else {
|
|
@@ -837,7 +845,7 @@ function sendStream (payload, res, reply) {
|
|
|
837
845
|
if (sourceOpen) {
|
|
838
846
|
if (err != null && res.headersSent && !errorLogged) {
|
|
839
847
|
errorLogged = true
|
|
840
|
-
logStreamError(
|
|
848
|
+
logStreamError(err, reply, res)
|
|
841
849
|
}
|
|
842
850
|
if (typeof payload.destroy === 'function') {
|
|
843
851
|
payload.destroy()
|
|
@@ -966,6 +974,7 @@ function setupResponseListeners (reply) {
|
|
|
966
974
|
// past the response on keep-alive connections.
|
|
967
975
|
const socket = reply.request.raw.socket
|
|
968
976
|
if (socket && socket._meta && socket._meta.request === reply.request) {
|
|
977
|
+
socket.removeListener('timeout', socket._meta.onTimeout)
|
|
969
978
|
socket._meta = null
|
|
970
979
|
}
|
|
971
980
|
|
|
@@ -986,25 +995,7 @@ function setupResponseListeners (reply) {
|
|
|
986
995
|
}
|
|
987
996
|
|
|
988
997
|
function onResponseCallback (err, request, reply) {
|
|
989
|
-
|
|
990
|
-
return
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
const responseTime = reply.elapsedTime
|
|
994
|
-
|
|
995
|
-
if (err != null) {
|
|
996
|
-
reply.log.error({
|
|
997
|
-
res: reply,
|
|
998
|
-
err,
|
|
999
|
-
responseTime
|
|
1000
|
-
}, 'request errored')
|
|
1001
|
-
return
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
reply.log.info({
|
|
1005
|
-
res: reply,
|
|
1006
|
-
responseTime
|
|
1007
|
-
}, 'request completed')
|
|
998
|
+
reply.server[kLogController].requestCompleted(err, request, reply)
|
|
1008
999
|
}
|
|
1009
1000
|
|
|
1010
1001
|
function buildReply (R) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { kGenReqId } = require('./symbols.js')
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* @callback GenerateRequestId
|
|
5
7
|
* @param {Object} req
|
|
@@ -22,7 +24,8 @@ function reqIdGenFactory (requestIdHeader, optGenReqId) {
|
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
function getGenReqId (contextServer, req) {
|
|
25
|
-
|
|
27
|
+
// direct symbol access skips the `genReqId` getter indirection
|
|
28
|
+
return contextServer[kGenReqId](req)
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
function buildDefaultGenReqId () {
|
package/lib/request.js
CHANGED
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
} = require('./symbols')
|
|
19
19
|
const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION, FST_ERR_DEC_UNDECLARED } = require('./errors')
|
|
20
20
|
const decorators = require('./decorate')
|
|
21
|
+
const ContentType = require('./content-type')
|
|
21
22
|
|
|
22
23
|
const HTTP_PART_SYMBOL_MAP = {
|
|
23
24
|
body: kSchemaBody,
|
|
@@ -258,8 +259,7 @@ Object.defineProperties(Request.prototype, {
|
|
|
258
259
|
port: {
|
|
259
260
|
get () {
|
|
260
261
|
const portReg = /(?<port>:\d+)$/
|
|
261
|
-
const
|
|
262
|
-
const matches = portReg.exec(host)
|
|
262
|
+
const matches = portReg.exec(this.host)
|
|
263
263
|
if (matches === null || matches[1] === undefined) {
|
|
264
264
|
return null
|
|
265
265
|
}
|
|
@@ -287,7 +287,7 @@ Object.defineProperties(Request.prototype, {
|
|
|
287
287
|
mediaType: {
|
|
288
288
|
get () {
|
|
289
289
|
if (!this[kRequestContentType] && this.headers['content-type'] !== undefined) {
|
|
290
|
-
this[kRequestContentType] =
|
|
290
|
+
this[kRequestContentType] = ContentType.from(this.headers['content-type'])
|
|
291
291
|
}
|
|
292
292
|
return this[kRequestContentType]?.mediaType
|
|
293
293
|
}
|
package/lib/route.js
CHANGED
|
@@ -43,7 +43,6 @@ const {
|
|
|
43
43
|
kReplySerializerDefault,
|
|
44
44
|
kReplyIsError,
|
|
45
45
|
kRequestPayloadStream,
|
|
46
|
-
kDisableRequestLogging,
|
|
47
46
|
kSchemaErrorFormatter,
|
|
48
47
|
kErrorHandler,
|
|
49
48
|
kHasBeenDecorated,
|
|
@@ -52,10 +51,11 @@ const {
|
|
|
52
51
|
kRouteContext,
|
|
53
52
|
kRequestSignal,
|
|
54
53
|
kTimeoutTimer,
|
|
55
|
-
kOnAbort
|
|
54
|
+
kOnAbort,
|
|
55
|
+
kLogController
|
|
56
56
|
} = require('./symbols.js')
|
|
57
57
|
const { buildErrorHandler } = require('./error-handler')
|
|
58
|
-
const { createChildLogger } = require('./logger-factory.js')
|
|
58
|
+
const { createChildLogger, defaultChildLoggerFactory } = require('./logger-factory.js')
|
|
59
59
|
const { getGenReqId } = require('./req-id-gen-factory.js')
|
|
60
60
|
const { FSTDEP022 } = require('./warnings')
|
|
61
61
|
|
|
@@ -83,8 +83,6 @@ function buildRouting (options) {
|
|
|
83
83
|
let hasLogger
|
|
84
84
|
let setupResponseListeners
|
|
85
85
|
let throwIfAlreadyStarted
|
|
86
|
-
let disableRequestLogging
|
|
87
|
-
let disableRequestLoggingFn
|
|
88
86
|
let ignoreTrailingSlash
|
|
89
87
|
let ignoreDuplicateSlashes
|
|
90
88
|
let return503OnClosing
|
|
@@ -107,10 +105,6 @@ function buildRouting (options) {
|
|
|
107
105
|
throwIfAlreadyStarted = fastifyArgs.throwIfAlreadyStarted
|
|
108
106
|
|
|
109
107
|
globalExposeHeadRoutes = options.exposeHeadRoutes
|
|
110
|
-
disableRequestLogging = options.disableRequestLogging
|
|
111
|
-
if (typeof disableRequestLogging === 'function') {
|
|
112
|
-
disableRequestLoggingFn = options.disableRequestLogging
|
|
113
|
-
}
|
|
114
108
|
|
|
115
109
|
ignoreTrailingSlash = options.routerOptions.ignoreTrailingSlash
|
|
116
110
|
ignoreDuplicateSlashes = options.routerOptions.ignoreDuplicateSlashes
|
|
@@ -185,7 +179,7 @@ function buildRouting (options) {
|
|
|
185
179
|
|
|
186
180
|
function findRoute (options) {
|
|
187
181
|
const route = router.find(
|
|
188
|
-
options.method,
|
|
182
|
+
options.method?.toUpperCase() ?? '',
|
|
189
183
|
options.url || '',
|
|
190
184
|
options.constraints
|
|
191
185
|
)
|
|
@@ -463,16 +457,21 @@ function buildRouting (options) {
|
|
|
463
457
|
function routeHandler (req, res, params, context, query) {
|
|
464
458
|
const id = getGenReqId(context.server, req)
|
|
465
459
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
460
|
+
let childLogger
|
|
461
|
+
if (hasLogger === false && context.childLoggerFactory === defaultChildLoggerFactory) {
|
|
462
|
+
// the null logger children are the logger itself, so we can skip
|
|
463
|
+
// the child logger creation altogether
|
|
464
|
+
childLogger = logger
|
|
465
|
+
} else {
|
|
466
|
+
const loggerOpts = {
|
|
467
|
+
level: context.logLevel
|
|
468
|
+
}
|
|
469
469
|
|
|
470
|
-
|
|
471
|
-
|
|
470
|
+
if (context.logSerializers) {
|
|
471
|
+
loggerOpts.serializers = context.logSerializers
|
|
472
|
+
}
|
|
473
|
+
childLogger = createChildLogger(context, logger, req, id, loggerOpts)
|
|
472
474
|
}
|
|
473
|
-
const childLogger = createChildLogger(context, logger, req, id, loggerOpts)
|
|
474
|
-
// Set initial value; will be re-evaluated after FastifyRequest is constructed if it's a function
|
|
475
|
-
childLogger[kDisableRequestLogging] = disableRequestLoggingFn ? false : disableRequestLogging
|
|
476
475
|
|
|
477
476
|
if (closing === true) {
|
|
478
477
|
/* istanbul ignore next mac, windows */
|
|
@@ -493,7 +492,9 @@ function buildRouting (options) {
|
|
|
493
492
|
}
|
|
494
493
|
res.writeHead(503, headers)
|
|
495
494
|
res.end('{"error":"Service Unavailable","message":"Service Unavailable","statusCode":503}')
|
|
496
|
-
|
|
495
|
+
|
|
496
|
+
context.server[kLogController].serviceUnavailable(childLogger, context.server)
|
|
497
|
+
|
|
497
498
|
return
|
|
498
499
|
}
|
|
499
500
|
}
|
|
@@ -518,16 +519,7 @@ function buildRouting (options) {
|
|
|
518
519
|
const request = new context.Request(id, params, req, query, childLogger, context)
|
|
519
520
|
const reply = new context.Reply(res, request, childLogger)
|
|
520
521
|
|
|
521
|
-
|
|
522
|
-
// so the caller has access to decorations and customizations
|
|
523
|
-
const resolvedDisableRequestLogging = disableRequestLoggingFn
|
|
524
|
-
? disableRequestLoggingFn(request)
|
|
525
|
-
: disableRequestLogging
|
|
526
|
-
childLogger[kDisableRequestLogging] = resolvedDisableRequestLogging
|
|
527
|
-
|
|
528
|
-
if (resolvedDisableRequestLogging === false) {
|
|
529
|
-
childLogger.info({ req: request }, 'incoming request')
|
|
530
|
-
}
|
|
522
|
+
context.server[kLogController].incomingRequest(request, reply)
|
|
531
523
|
|
|
532
524
|
// Handler timeout setup — only when configured (zero overhead otherwise)
|
|
533
525
|
const handlerTimeout = context.handlerTimeout
|
|
@@ -558,6 +550,13 @@ function buildRouting (options) {
|
|
|
558
550
|
setupResponseListeners(reply)
|
|
559
551
|
}
|
|
560
552
|
|
|
553
|
+
if (context.onTimeout !== null) {
|
|
554
|
+
if (!request.raw.socket._meta) {
|
|
555
|
+
request.raw.socket.on('timeout', handleTimeout)
|
|
556
|
+
}
|
|
557
|
+
request.raw.socket._meta = { context, request, reply, onTimeout: handleTimeout }
|
|
558
|
+
}
|
|
559
|
+
|
|
561
560
|
if (context.onRequest !== null) {
|
|
562
561
|
onRequestHookRunner(
|
|
563
562
|
context.onRequest,
|
|
@@ -581,13 +580,6 @@ function buildRouting (options) {
|
|
|
581
580
|
}
|
|
582
581
|
})
|
|
583
582
|
}
|
|
584
|
-
|
|
585
|
-
if (context.onTimeout !== null) {
|
|
586
|
-
if (!request.raw.socket._meta) {
|
|
587
|
-
request.raw.socket.on('timeout', handleTimeout)
|
|
588
|
-
}
|
|
589
|
-
request.raw.socket._meta = { context, request, reply }
|
|
590
|
-
}
|
|
591
583
|
}
|
|
592
584
|
}
|
|
593
585
|
|
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/symbols.js
CHANGED
|
@@ -13,7 +13,6 @@ const keys = {
|
|
|
13
13
|
kContentTypeParser: Symbol('fastify.contentTypeParser'),
|
|
14
14
|
kState: Symbol('fastify.state'),
|
|
15
15
|
kOptions: Symbol('fastify.options'),
|
|
16
|
-
kDisableRequestLogging: Symbol('fastify.disableRequestLogging'),
|
|
17
16
|
kPluginNameChain: Symbol('fastify.pluginNameChain'),
|
|
18
17
|
kRouteContext: Symbol('fastify.context'),
|
|
19
18
|
kGenReqId: Symbol('fastify.genReqId'),
|
|
@@ -67,6 +66,7 @@ const keys = {
|
|
|
67
66
|
kHasBeenDecorated: Symbol('fastify.hasBeenDecorated'),
|
|
68
67
|
kKeepAliveConnections: Symbol('fastify.keepAliveConnections'),
|
|
69
68
|
kRouteByFastify: Symbol('fastify.routeByFastify'),
|
|
69
|
+
kLogController: Symbol('fastify.logController'),
|
|
70
70
|
kDiagnosticsStore: Symbol('fastify.diagnosticsStore')
|
|
71
71
|
}
|
|
72
72
|
|
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
|
}
|
|
@@ -146,6 +146,15 @@ function validateParam (validatorFunction, request, paramName) {
|
|
|
146
146
|
function validate (context, request, execution) {
|
|
147
147
|
const runExecution = execution === undefined
|
|
148
148
|
|
|
149
|
+
if (runExecution &&
|
|
150
|
+
context[paramsSchema] === undefined &&
|
|
151
|
+
context[bodySchema] === undefined &&
|
|
152
|
+
context[querystringSchema] === undefined &&
|
|
153
|
+
context[headersSchema] === undefined) {
|
|
154
|
+
// the route has no validation schemas
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
|
|
149
158
|
if (runExecution || !execution.skipParams) {
|
|
150
159
|
const params = validateParam(context[paramsSchema], request, 'params')
|
|
151
160
|
if (params) {
|