fastify 4.25.2 → 4.26.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/EXPENSE_POLICY.md +105 -0
- package/GOVERNANCE.md +2 -103
- package/LICENSE +1 -1
- package/README.md +13 -9
- package/SECURITY.md +2 -157
- package/SPONSORS.md +20 -0
- package/build/build-validation.js +3 -1
- package/docs/Guides/Ecosystem.md +29 -9
- package/docs/Guides/Getting-Started.md +16 -3
- package/docs/Guides/Style-Guide.md +7 -7
- package/docs/Reference/Decorators.md +1 -1
- package/docs/Reference/Errors.md +63 -1
- package/docs/Reference/Hooks.md +1 -1
- package/docs/Reference/Logging.md +3 -3
- package/docs/Reference/Reply.md +70 -1
- package/docs/Reference/Server.md +90 -0
- package/docs/Reference/Warnings.md +17 -2
- package/fastify.d.ts +3 -2
- package/fastify.js +25 -7
- package/lib/configValidator.js +62 -33
- package/lib/contentTypeParser.js +9 -2
- package/lib/error-handler.js +1 -1
- package/lib/error-serializer.js +30 -29
- package/lib/errors.js +6 -1
- package/lib/fourOhFour.js +4 -3
- package/lib/hooks.js +1 -5
- package/lib/reply.js +68 -10
- package/lib/reqIdGenFactory.js +5 -0
- package/lib/route.js +22 -6
- package/lib/schema-controller.js +37 -4
- package/lib/symbols.js +1 -0
- package/lib/warnings.js +6 -0
- package/package.json +18 -6
- package/test/async_hooks.test.js +69 -0
- package/test/findRoute.test.js +135 -0
- package/test/genReqId.test.js +392 -0
- package/test/hooks.on-listen.test.js +66 -14
- package/test/hooks.on-ready.test.js +1 -1
- package/test/internals/errors.test.js +17 -7
- package/test/internals/initialConfig.test.js +7 -3
- package/test/internals/reply.test.js +80 -5
- package/test/plugin.4.test.js +3 -3
- package/test/pretty-print.test.js +1 -1
- package/test/schema-serialization.test.js +41 -0
- package/test/schema-validation.test.js +115 -6
- package/test/serialize-response.test.js +187 -0
- package/test/types/instance.test-d.ts +14 -1
- package/test/types/reply.test-d.ts +4 -2
- package/test/types/request.test-d.ts +1 -1
- package/test/types/route.test-d.ts +15 -1
- package/test/useSemicolonDelimiter.test.js +113 -0
- package/test/web-api.test.js +208 -0
- package/types/instance.d.ts +23 -10
- package/types/reply.d.ts +4 -0
- package/types/request.d.ts +5 -4
package/lib/configValidator.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"use strict";
|
|
4
4
|
module.exports = validate10;
|
|
5
5
|
module.exports.default = validate10;
|
|
6
|
-
const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"jsonShorthand":{"type":"boolean","default":true},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"enum":[false]},{"type":"string"}],"default":"request-id"},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"versioning":{"type":"object","additionalProperties":true,"required":["storage","deriveVersion"],"properties":{"storage":{},"deriveVersion":{}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}};
|
|
6
|
+
const schema11 = {"type":"object","additionalProperties":false,"properties":{"connectionTimeout":{"type":"integer","default":0},"keepAliveTimeout":{"type":"integer","default":72000},"forceCloseConnections":{"oneOf":[{"type":"string","pattern":"idle"},{"type":"boolean"}]},"maxRequestsPerSocket":{"type":"integer","default":0,"nullable":true},"requestTimeout":{"type":"integer","default":0},"bodyLimit":{"type":"integer","default":1048576},"caseSensitive":{"type":"boolean","default":true},"allowUnsafeRegex":{"type":"boolean","default":false},"http2":{"type":"boolean"},"https":{"if":{"not":{"oneOf":[{"type":"boolean"},{"type":"null"},{"type":"object","additionalProperties":false,"required":["allowHTTP1"],"properties":{"allowHTTP1":{"type":"boolean"}}}]}},"then":{"setDefaultValue":true}},"ignoreTrailingSlash":{"type":"boolean","default":false},"ignoreDuplicateSlashes":{"type":"boolean","default":false},"disableRequestLogging":{"type":"boolean","default":false},"jsonShorthand":{"type":"boolean","default":true},"maxParamLength":{"type":"integer","default":100},"onProtoPoisoning":{"type":"string","default":"error"},"onConstructorPoisoning":{"type":"string","default":"error"},"pluginTimeout":{"type":"integer","default":10000},"requestIdHeader":{"anyOf":[{"enum":[false]},{"type":"string"}],"default":"request-id"},"requestIdLogLabel":{"type":"string","default":"reqId"},"http2SessionTimeout":{"type":"integer","default":72000},"exposeHeadRoutes":{"type":"boolean","default":true},"useSemicolonDelimiter":{"type":"boolean","default":true},"versioning":{"type":"object","additionalProperties":true,"required":["storage","deriveVersion"],"properties":{"storage":{},"deriveVersion":{}}},"constraints":{"type":"object","additionalProperties":{"type":"object","required":["name","storage","validate","deriveConstraint"],"additionalProperties":true,"properties":{"name":{"type":"string"},"storage":{},"validate":{},"deriveConstraint":{}}}}}};
|
|
7
7
|
const func2 = Object.prototype.hasOwnProperty;
|
|
8
8
|
const pattern0 = new RegExp("idle", "u");
|
|
9
9
|
|
|
@@ -69,6 +69,9 @@ data.http2SessionTimeout = 72000;
|
|
|
69
69
|
if(data.exposeHeadRoutes === undefined){
|
|
70
70
|
data.exposeHeadRoutes = true;
|
|
71
71
|
}
|
|
72
|
+
if(data.useSemicolonDelimiter === undefined){
|
|
73
|
+
data.useSemicolonDelimiter = true;
|
|
74
|
+
}
|
|
72
75
|
const _errs1 = errors;
|
|
73
76
|
for(const key0 in data){
|
|
74
77
|
if(!(func2.call(schema11.properties, key0))){
|
|
@@ -985,13 +988,38 @@ data["exposeHeadRoutes"] = coerced24;
|
|
|
985
988
|
}
|
|
986
989
|
var valid0 = _errs66 === errors;
|
|
987
990
|
if(valid0){
|
|
988
|
-
|
|
989
|
-
let data23 = data.versioning;
|
|
991
|
+
let data23 = data.useSemicolonDelimiter;
|
|
990
992
|
const _errs68 = errors;
|
|
991
|
-
if(
|
|
992
|
-
|
|
993
|
+
if(typeof data23 !== "boolean"){
|
|
994
|
+
let coerced25 = undefined;
|
|
995
|
+
if(!(coerced25 !== undefined)){
|
|
996
|
+
if(data23 === "false" || data23 === 0 || data23 === null){
|
|
997
|
+
coerced25 = false;
|
|
998
|
+
}
|
|
999
|
+
else if(data23 === "true" || data23 === 1){
|
|
1000
|
+
coerced25 = true;
|
|
1001
|
+
}
|
|
1002
|
+
else {
|
|
1003
|
+
validate10.errors = [{instancePath:instancePath+"/useSemicolonDelimiter",schemaPath:"#/properties/useSemicolonDelimiter/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if(coerced25 !== undefined){
|
|
1008
|
+
data23 = coerced25;
|
|
1009
|
+
if(data !== undefined){
|
|
1010
|
+
data["useSemicolonDelimiter"] = coerced25;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
var valid0 = _errs68 === errors;
|
|
1015
|
+
if(valid0){
|
|
1016
|
+
if(data.versioning !== undefined){
|
|
1017
|
+
let data24 = data.versioning;
|
|
1018
|
+
const _errs70 = errors;
|
|
1019
|
+
if(errors === _errs70){
|
|
1020
|
+
if(data24 && typeof data24 == "object" && !Array.isArray(data24)){
|
|
993
1021
|
let missing1;
|
|
994
|
-
if(((
|
|
1022
|
+
if(((data24.storage === undefined) && (missing1 = "storage")) || ((data24.deriveVersion === undefined) && (missing1 = "deriveVersion"))){
|
|
995
1023
|
validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/properties/versioning/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}];
|
|
996
1024
|
return false;
|
|
997
1025
|
}
|
|
@@ -1001,49 +1029,49 @@ validate10.errors = [{instancePath:instancePath+"/versioning",schemaPath:"#/prop
|
|
|
1001
1029
|
return false;
|
|
1002
1030
|
}
|
|
1003
1031
|
}
|
|
1004
|
-
var valid0 =
|
|
1032
|
+
var valid0 = _errs70 === errors;
|
|
1005
1033
|
}
|
|
1006
1034
|
else {
|
|
1007
1035
|
var valid0 = true;
|
|
1008
1036
|
}
|
|
1009
1037
|
if(valid0){
|
|
1010
1038
|
if(data.constraints !== undefined){
|
|
1011
|
-
let
|
|
1012
|
-
const
|
|
1013
|
-
if(errors ===
|
|
1014
|
-
if(data24 && typeof data24 == "object" && !Array.isArray(data24)){
|
|
1015
|
-
for(const key2 in data24){
|
|
1016
|
-
let data25 = data24[key2];
|
|
1017
|
-
const _errs74 = errors;
|
|
1018
|
-
if(errors === _errs74){
|
|
1039
|
+
let data25 = data.constraints;
|
|
1040
|
+
const _errs73 = errors;
|
|
1041
|
+
if(errors === _errs73){
|
|
1019
1042
|
if(data25 && typeof data25 == "object" && !Array.isArray(data25)){
|
|
1043
|
+
for(const key2 in data25){
|
|
1044
|
+
let data26 = data25[key2];
|
|
1045
|
+
const _errs76 = errors;
|
|
1046
|
+
if(errors === _errs76){
|
|
1047
|
+
if(data26 && typeof data26 == "object" && !Array.isArray(data26)){
|
|
1020
1048
|
let missing2;
|
|
1021
|
-
if(((((
|
|
1049
|
+
if(((((data26.name === undefined) && (missing2 = "name")) || ((data26.storage === undefined) && (missing2 = "storage"))) || ((data26.validate === undefined) && (missing2 = "validate"))) || ((data26.deriveConstraint === undefined) && (missing2 = "deriveConstraint"))){
|
|
1022
1050
|
validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/constraints/additionalProperties/required",keyword:"required",params:{missingProperty: missing2},message:"must have required property '"+missing2+"'"}];
|
|
1023
1051
|
return false;
|
|
1024
1052
|
}
|
|
1025
1053
|
else {
|
|
1026
|
-
if(
|
|
1027
|
-
let
|
|
1028
|
-
if(typeof
|
|
1029
|
-
let
|
|
1030
|
-
let
|
|
1031
|
-
if(!(
|
|
1032
|
-
if(
|
|
1033
|
-
|
|
1054
|
+
if(data26.name !== undefined){
|
|
1055
|
+
let data27 = data26.name;
|
|
1056
|
+
if(typeof data27 !== "string"){
|
|
1057
|
+
let dataType26 = typeof data27;
|
|
1058
|
+
let coerced26 = undefined;
|
|
1059
|
+
if(!(coerced26 !== undefined)){
|
|
1060
|
+
if(dataType26 == "number" || dataType26 == "boolean"){
|
|
1061
|
+
coerced26 = "" + data27;
|
|
1034
1062
|
}
|
|
1035
|
-
else if(
|
|
1036
|
-
|
|
1063
|
+
else if(data27 === null){
|
|
1064
|
+
coerced26 = "";
|
|
1037
1065
|
}
|
|
1038
1066
|
else {
|
|
1039
1067
|
validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/~/g, "~0").replace(/\//g, "~1")+"/name",schemaPath:"#/properties/constraints/additionalProperties/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}];
|
|
1040
1068
|
return false;
|
|
1041
1069
|
}
|
|
1042
1070
|
}
|
|
1043
|
-
if(
|
|
1044
|
-
|
|
1045
|
-
if(
|
|
1046
|
-
|
|
1071
|
+
if(coerced26 !== undefined){
|
|
1072
|
+
data27 = coerced26;
|
|
1073
|
+
if(data26 !== undefined){
|
|
1074
|
+
data26["name"] = coerced26;
|
|
1047
1075
|
}
|
|
1048
1076
|
}
|
|
1049
1077
|
}
|
|
@@ -1055,7 +1083,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints/" + key2.replace(/
|
|
|
1055
1083
|
return false;
|
|
1056
1084
|
}
|
|
1057
1085
|
}
|
|
1058
|
-
var valid7 =
|
|
1086
|
+
var valid7 = _errs76 === errors;
|
|
1059
1087
|
if(!valid7){
|
|
1060
1088
|
break;
|
|
1061
1089
|
}
|
|
@@ -1066,7 +1094,7 @@ validate10.errors = [{instancePath:instancePath+"/constraints",schemaPath:"#/pro
|
|
|
1066
1094
|
return false;
|
|
1067
1095
|
}
|
|
1068
1096
|
}
|
|
1069
|
-
var valid0 =
|
|
1097
|
+
var valid0 = _errs73 === errors;
|
|
1070
1098
|
}
|
|
1071
1099
|
else {
|
|
1072
1100
|
var valid0 = true;
|
|
@@ -1096,6 +1124,7 @@ var valid0 = true;
|
|
|
1096
1124
|
}
|
|
1097
1125
|
}
|
|
1098
1126
|
}
|
|
1127
|
+
}
|
|
1099
1128
|
else {
|
|
1100
1129
|
validate10.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}];
|
|
1101
1130
|
return false;
|
|
@@ -1106,4 +1135,4 @@ return errors === 0;
|
|
|
1106
1135
|
}
|
|
1107
1136
|
|
|
1108
1137
|
|
|
1109
|
-
module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"jsonShorthand":true,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":"request-id","requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true}
|
|
1138
|
+
module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":72000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"allowUnsafeRegex":false,"disableRequestLogging":false,"jsonShorthand":true,"ignoreTrailingSlash":false,"ignoreDuplicateSlashes":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":"request-id","requestIdLogLabel":"reqId","http2SessionTimeout":72000,"exposeHeadRoutes":true,"useSemicolonDelimiter":true}
|
package/lib/contentTypeParser.js
CHANGED
|
@@ -157,7 +157,6 @@ ContentTypeParser.prototype.remove = function (contentType) {
|
|
|
157
157
|
|
|
158
158
|
ContentTypeParser.prototype.run = function (contentType, handler, request, reply) {
|
|
159
159
|
const parser = this.getParser(contentType)
|
|
160
|
-
const resource = new AsyncResource('content-type-parser:run', request)
|
|
161
160
|
|
|
162
161
|
if (parser === undefined) {
|
|
163
162
|
if (request.is404) {
|
|
@@ -165,7 +164,14 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply
|
|
|
165
164
|
} else {
|
|
166
165
|
reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType || undefined))
|
|
167
166
|
}
|
|
168
|
-
|
|
167
|
+
|
|
168
|
+
// Early return to avoid allocating an AsyncResource if it's not needed
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const resource = new AsyncResource('content-type-parser:run', request)
|
|
173
|
+
|
|
174
|
+
if (parser.asString === true || parser.asBuffer === true) {
|
|
169
175
|
rawBody(
|
|
170
176
|
request,
|
|
171
177
|
reply,
|
|
@@ -184,6 +190,7 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply
|
|
|
184
190
|
function done (error, body) {
|
|
185
191
|
// We cannot use resource.bind() because it is broken in node v12 and v14
|
|
186
192
|
resource.runInAsyncScope(() => {
|
|
193
|
+
resource.emitDestroy()
|
|
187
194
|
if (error) {
|
|
188
195
|
reply[kReplyIsError] = true
|
|
189
196
|
reply.send(error)
|
package/lib/error-handler.js
CHANGED
|
@@ -95,6 +95,7 @@ function defaultErrorHandler (error, request, reply) {
|
|
|
95
95
|
function fallbackErrorHandler (error, reply, cb) {
|
|
96
96
|
const res = reply.raw
|
|
97
97
|
const statusCode = reply.statusCode
|
|
98
|
+
reply[kReplyHeaders]['content-type'] = reply[kReplyHeaders]['content-type'] ?? 'application/json; charset=utf-8'
|
|
98
99
|
let payload
|
|
99
100
|
try {
|
|
100
101
|
const serializerFn = getSchemaSerializer(reply[kRouteContext], statusCode, reply[kReplyHeaders]['content-type'])
|
|
@@ -121,7 +122,6 @@ function fallbackErrorHandler (error, reply, cb) {
|
|
|
121
122
|
payload = serializeError(new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload))
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
reply[kReplyHeaders]['content-type'] = 'application/json; charset=utf-8'
|
|
125
125
|
reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
|
|
126
126
|
|
|
127
127
|
cb(reply, payload)
|
package/lib/error-serializer.js
CHANGED
|
@@ -23,35 +23,36 @@
|
|
|
23
23
|
? input.toJSON()
|
|
24
24
|
: input
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
26
|
+
if (obj === null) return '{}'
|
|
27
|
+
|
|
28
|
+
let json = '{'
|
|
29
|
+
let addComma = false
|
|
30
|
+
|
|
31
|
+
if (obj["statusCode"] !== undefined) {
|
|
32
|
+
!addComma && (addComma = true) || (json += ',')
|
|
33
|
+
json += "\"statusCode\":"
|
|
34
|
+
json += serializer.asNumber(obj["statusCode"])
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (obj["code"] !== undefined) {
|
|
38
|
+
!addComma && (addComma = true) || (json += ',')
|
|
39
|
+
json += "\"code\":"
|
|
40
|
+
json += serializer.asString(obj["code"])
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (obj["error"] !== undefined) {
|
|
44
|
+
!addComma && (addComma = true) || (json += ',')
|
|
45
|
+
json += "\"error\":"
|
|
46
|
+
json += serializer.asString(obj["error"])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (obj["message"] !== undefined) {
|
|
50
|
+
!addComma && (addComma = true) || (json += ',')
|
|
51
|
+
json += "\"message\":"
|
|
52
|
+
json += serializer.asString(obj["message"])
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return json + '}'
|
|
55
56
|
|
|
56
57
|
}
|
|
57
58
|
|
package/lib/errors.js
CHANGED
|
@@ -212,6 +212,10 @@ const codes = {
|
|
|
212
212
|
500,
|
|
213
213
|
TypeError
|
|
214
214
|
),
|
|
215
|
+
FST_ERR_REP_RESPONSE_BODY_CONSUMED: createError(
|
|
216
|
+
'FST_ERR_REP_RESPONSE_BODY_CONSUMED',
|
|
217
|
+
'Response.body is already consumed.'
|
|
218
|
+
),
|
|
215
219
|
FST_ERR_REP_ALREADY_SENT: createError(
|
|
216
220
|
'FST_ERR_REP_ALREADY_SENT',
|
|
217
221
|
'Reply was already sent, did you forget to "return reply" in "%s" (%s)?'
|
|
@@ -460,5 +464,6 @@ module.exports.AVVIO_ERRORS_MAP = {
|
|
|
460
464
|
AVV_ERR_PLUGIN_NOT_VALID: codes.FST_ERR_PLUGIN_NOT_VALID,
|
|
461
465
|
AVV_ERR_ROOT_PLG_BOOTED: codes.FST_ERR_ROOT_PLG_BOOTED,
|
|
462
466
|
AVV_ERR_PARENT_PLG_LOADED: codes.FST_ERR_PARENT_PLUGIN_BOOTED,
|
|
463
|
-
AVV_ERR_READY_TIMEOUT: codes.FST_ERR_PLUGIN_TIMEOUT
|
|
467
|
+
AVV_ERR_READY_TIMEOUT: codes.FST_ERR_PLUGIN_TIMEOUT,
|
|
468
|
+
AVV_ERR_PLUGIN_EXEC_TIMEOUT: codes.FST_ERR_PLUGIN_TIMEOUT
|
|
464
469
|
}
|
package/lib/fourOhFour.js
CHANGED
|
@@ -19,6 +19,7 @@ const {
|
|
|
19
19
|
FST_ERR_NOT_FOUND
|
|
20
20
|
} = require('./errors')
|
|
21
21
|
const { createChildLogger } = require('./logger')
|
|
22
|
+
const { getGenReqId } = require('./reqIdGenFactory.js')
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Each fastify instance have a:
|
|
@@ -28,7 +29,7 @@ const { createChildLogger } = require('./logger')
|
|
|
28
29
|
* kFourOhFourContext: the context in the reply object where the handler will be executed
|
|
29
30
|
*/
|
|
30
31
|
function fourOhFour (options) {
|
|
31
|
-
const { logger
|
|
32
|
+
const { logger } = options
|
|
32
33
|
|
|
33
34
|
// 404 router, used for handling encapsulated 404 handlers
|
|
34
35
|
const router = FindMyWay({ onBadUrl: createOnBadUrl(), defaultRoute: fourOhFourFallBack })
|
|
@@ -58,8 +59,8 @@ function fourOhFour (options) {
|
|
|
58
59
|
|
|
59
60
|
function createOnBadUrl () {
|
|
60
61
|
return function onBadUrl (path, req, res) {
|
|
61
|
-
const id = genReqId(req)
|
|
62
62
|
const fourOhFourContext = this[kFourOhFourLevelInstance][kFourOhFourContext]
|
|
63
|
+
const id = getGenReqId(fourOhFourContext.server, req)
|
|
63
64
|
const childLogger = createChildLogger(fourOhFourContext, logger, req, id)
|
|
64
65
|
const request = new Request(id, null, req, null, childLogger, fourOhFourContext)
|
|
65
66
|
const reply = new Reply(res, request, childLogger)
|
|
@@ -166,8 +167,8 @@ function fourOhFour (options) {
|
|
|
166
167
|
// we might want to do some hard debugging
|
|
167
168
|
// here, let's print out as much info as
|
|
168
169
|
// we can
|
|
169
|
-
const id = genReqId(req)
|
|
170
170
|
const fourOhFourContext = this[kFourOhFourLevelInstance][kFourOhFourContext]
|
|
171
|
+
const id = getGenReqId(fourOhFourContext.server, req)
|
|
171
172
|
const childLogger = createChildLogger(fourOhFourContext, logger, req, id)
|
|
172
173
|
|
|
173
174
|
childLogger.info({ req }, 'incoming request')
|
package/lib/hooks.js
CHANGED
|
@@ -181,10 +181,6 @@ function onListenHookRunner (server) {
|
|
|
181
181
|
const hooks = server[kHooks].onListen
|
|
182
182
|
const hooksLen = hooks.length
|
|
183
183
|
|
|
184
|
-
if (hooksLen === 0) {
|
|
185
|
-
return
|
|
186
|
-
}
|
|
187
|
-
|
|
188
184
|
let i = 0
|
|
189
185
|
let c = 0
|
|
190
186
|
|
|
@@ -196,7 +192,7 @@ function onListenHookRunner (server) {
|
|
|
196
192
|
if (
|
|
197
193
|
i === hooksLen
|
|
198
194
|
) {
|
|
199
|
-
|
|
195
|
+
while (c < server[kChildren].length) {
|
|
200
196
|
const child = server[kChildren][c++]
|
|
201
197
|
onListenHookRunner(child)
|
|
202
198
|
}
|
package/lib/reply.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const eos = require('node:stream').finished
|
|
4
|
+
const Readable = require('node:stream').Readable
|
|
4
5
|
|
|
5
6
|
const {
|
|
6
7
|
kFourOhFourContext,
|
|
@@ -44,6 +45,7 @@ const CONTENT_TYPE = {
|
|
|
44
45
|
}
|
|
45
46
|
const {
|
|
46
47
|
FST_ERR_REP_INVALID_PAYLOAD_TYPE,
|
|
48
|
+
FST_ERR_REP_RESPONSE_BODY_CONSUMED,
|
|
47
49
|
FST_ERR_REP_ALREADY_SENT,
|
|
48
50
|
FST_ERR_REP_SENT_VALUE,
|
|
49
51
|
FST_ERR_SEND_INSIDE_ONERR,
|
|
@@ -53,7 +55,9 @@ const {
|
|
|
53
55
|
FST_ERR_MISSING_SERIALIZATION_FN,
|
|
54
56
|
FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN
|
|
55
57
|
} = require('./errors')
|
|
56
|
-
const { FSTDEP010, FSTDEP013, FSTDEP019 } = require('./warnings')
|
|
58
|
+
const { FSTDEP010, FSTDEP013, FSTDEP019, FSTDEP020 } = require('./warnings')
|
|
59
|
+
|
|
60
|
+
const toString = Object.prototype.toString
|
|
57
61
|
|
|
58
62
|
function Reply (res, request, log) {
|
|
59
63
|
this.raw = res
|
|
@@ -84,6 +88,14 @@ Object.defineProperties(Reply.prototype, {
|
|
|
84
88
|
return this.request[kRouteContext]
|
|
85
89
|
}
|
|
86
90
|
},
|
|
91
|
+
elapsedTime: {
|
|
92
|
+
get () {
|
|
93
|
+
if (this[kReplyStartTime] === undefined) {
|
|
94
|
+
return 0
|
|
95
|
+
}
|
|
96
|
+
return (this[kReplyEndTime] || now()) - this[kReplyStartTime]
|
|
97
|
+
}
|
|
98
|
+
},
|
|
87
99
|
server: {
|
|
88
100
|
get () {
|
|
89
101
|
return this.request[kRouteContext].server
|
|
@@ -155,7 +167,14 @@ Reply.prototype.send = function (payload) {
|
|
|
155
167
|
const hasContentType = contentType !== undefined
|
|
156
168
|
|
|
157
169
|
if (payload !== null) {
|
|
158
|
-
if (
|
|
170
|
+
if (
|
|
171
|
+
// node:stream
|
|
172
|
+
typeof payload.pipe === 'function' ||
|
|
173
|
+
// node:stream/web
|
|
174
|
+
typeof payload.getReader === 'function' ||
|
|
175
|
+
// Response
|
|
176
|
+
toString.call(payload) === '[object Response]'
|
|
177
|
+
) {
|
|
159
178
|
onSendHook(this, payload)
|
|
160
179
|
return this
|
|
161
180
|
}
|
|
@@ -452,14 +471,11 @@ Reply.prototype.callNotFound = function () {
|
|
|
452
471
|
return this
|
|
453
472
|
}
|
|
454
473
|
|
|
474
|
+
// TODO: should be removed in fastify@5
|
|
455
475
|
Reply.prototype.getResponseTime = function () {
|
|
456
|
-
|
|
476
|
+
FSTDEP020()
|
|
457
477
|
|
|
458
|
-
|
|
459
|
-
responseTime = (this[kReplyEndTime] || now()) - this[kReplyStartTime]
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
return responseTime
|
|
478
|
+
return this.elapsedTime
|
|
463
479
|
}
|
|
464
480
|
|
|
465
481
|
// Make reply a thenable, so it could be used with async/await.
|
|
@@ -565,7 +581,6 @@ function safeWriteHead (reply, statusCode) {
|
|
|
565
581
|
function onSendEnd (reply, payload) {
|
|
566
582
|
const res = reply.raw
|
|
567
583
|
const req = reply.request
|
|
568
|
-
const statusCode = res.statusCode
|
|
569
584
|
|
|
570
585
|
// we check if we need to update the trailers header and set it
|
|
571
586
|
if (reply[kReplyTrailers] !== null) {
|
|
@@ -581,6 +596,17 @@ function onSendEnd (reply, payload) {
|
|
|
581
596
|
reply.header('Trailer', header.trim())
|
|
582
597
|
}
|
|
583
598
|
|
|
599
|
+
// since Response contain status code, we need to update before
|
|
600
|
+
// any action that used statusCode
|
|
601
|
+
const isResponse = toString.call(payload) === '[object Response]'
|
|
602
|
+
if (isResponse) {
|
|
603
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Response/status
|
|
604
|
+
if (typeof payload.status === 'number') {
|
|
605
|
+
reply.code(payload.status)
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const statusCode = res.statusCode
|
|
609
|
+
|
|
584
610
|
if (payload === undefined || payload === null) {
|
|
585
611
|
// according to https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2
|
|
586
612
|
// we cannot send a content-length for 304 and 204, and all status code
|
|
@@ -612,11 +638,38 @@ function onSendEnd (reply, payload) {
|
|
|
612
638
|
return
|
|
613
639
|
}
|
|
614
640
|
|
|
641
|
+
// node:stream
|
|
615
642
|
if (typeof payload.pipe === 'function') {
|
|
616
643
|
sendStream(payload, res, reply)
|
|
617
644
|
return
|
|
618
645
|
}
|
|
619
646
|
|
|
647
|
+
// node:stream/web
|
|
648
|
+
if (typeof payload.getReader === 'function') {
|
|
649
|
+
sendWebStream(payload, res, reply)
|
|
650
|
+
return
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Response
|
|
654
|
+
if (isResponse) {
|
|
655
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Response/headers
|
|
656
|
+
if (typeof payload.headers === 'object' && typeof payload.headers.forEach === 'function') {
|
|
657
|
+
for (const [headerName, headerValue] of payload.headers) {
|
|
658
|
+
reply.header(headerName, headerValue)
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Response/body
|
|
663
|
+
if (payload.body != null) {
|
|
664
|
+
if (payload.bodyUsed) {
|
|
665
|
+
throw new FST_ERR_REP_RESPONSE_BODY_CONSUMED()
|
|
666
|
+
}
|
|
667
|
+
// Response.body always a ReadableStream
|
|
668
|
+
sendWebStream(payload.body, res, reply)
|
|
669
|
+
}
|
|
670
|
+
return
|
|
671
|
+
}
|
|
672
|
+
|
|
620
673
|
if (typeof payload !== 'string' && !Buffer.isBuffer(payload)) {
|
|
621
674
|
throw new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload)
|
|
622
675
|
}
|
|
@@ -649,6 +702,11 @@ function logStreamError (logger, err, res) {
|
|
|
649
702
|
}
|
|
650
703
|
}
|
|
651
704
|
|
|
705
|
+
function sendWebStream (payload, res, reply) {
|
|
706
|
+
const nodeStream = Readable.fromWeb(payload)
|
|
707
|
+
sendStream(nodeStream, res, reply)
|
|
708
|
+
}
|
|
709
|
+
|
|
652
710
|
function sendStream (payload, res, reply) {
|
|
653
711
|
let sourceOpen = true
|
|
654
712
|
let errorLogged = false
|
|
@@ -813,7 +871,7 @@ function onResponseCallback (err, request, reply) {
|
|
|
813
871
|
return
|
|
814
872
|
}
|
|
815
873
|
|
|
816
|
-
const responseTime = reply.
|
|
874
|
+
const responseTime = reply.elapsedTime
|
|
817
875
|
|
|
818
876
|
if (err != null) {
|
|
819
877
|
reply.log.error({
|
package/lib/reqIdGenFactory.js
CHANGED
|
@@ -21,6 +21,10 @@ function reqIdGenFactory (requestIdHeader, optGenReqId) {
|
|
|
21
21
|
return genReqId
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function getGenReqId (contextServer, req) {
|
|
25
|
+
return contextServer.genReqId(req)
|
|
26
|
+
}
|
|
27
|
+
|
|
24
28
|
function buildDefaultGenReqId () {
|
|
25
29
|
// 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8).
|
|
26
30
|
// With this upper bound, if you'll be generating 1k ids/sec, you're going to hit it in ~25 days.
|
|
@@ -43,5 +47,6 @@ function buildOptionalHeaderReqId (requestIdHeader, genReqId) {
|
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
module.exports = {
|
|
50
|
+
getGenReqId,
|
|
46
51
|
reqIdGenFactory
|
|
47
52
|
}
|
package/lib/route.js
CHANGED
|
@@ -56,6 +56,7 @@ const {
|
|
|
56
56
|
} = require('./symbols.js')
|
|
57
57
|
const { buildErrorHandler } = require('./error-handler')
|
|
58
58
|
const { createChildLogger } = require('./logger')
|
|
59
|
+
const { getGenReqId } = require('./reqIdGenFactory.js')
|
|
59
60
|
|
|
60
61
|
function buildRouting (options) {
|
|
61
62
|
const router = FindMyWay(options.config)
|
|
@@ -66,7 +67,6 @@ function buildRouting (options) {
|
|
|
66
67
|
let hasLogger
|
|
67
68
|
let setupResponseListeners
|
|
68
69
|
let throwIfAlreadyStarted
|
|
69
|
-
let genReqId
|
|
70
70
|
let disableRequestLogging
|
|
71
71
|
let ignoreTrailingSlash
|
|
72
72
|
let ignoreDuplicateSlashes
|
|
@@ -92,7 +92,6 @@ function buildRouting (options) {
|
|
|
92
92
|
validateHTTPVersion = fastifyArgs.validateHTTPVersion
|
|
93
93
|
|
|
94
94
|
globalExposeHeadRoutes = options.exposeHeadRoutes
|
|
95
|
-
genReqId = options.genReqId
|
|
96
95
|
disableRequestLogging = options.disableRequestLogging
|
|
97
96
|
ignoreTrailingSlash = options.ignoreTrailingSlash
|
|
98
97
|
ignoreDuplicateSlashes = options.ignoreDuplicateSlashes
|
|
@@ -120,7 +119,8 @@ function buildRouting (options) {
|
|
|
120
119
|
printRoutes: router.prettyPrint.bind(router),
|
|
121
120
|
addConstraintStrategy,
|
|
122
121
|
hasConstraintStrategy,
|
|
123
|
-
isAsyncConstraint
|
|
122
|
+
isAsyncConstraint,
|
|
123
|
+
findRoute
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
function addConstraintStrategy (strategy) {
|
|
@@ -168,11 +168,27 @@ function buildRouting (options) {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
function hasRoute ({ options }) {
|
|
171
|
-
return
|
|
171
|
+
return findRoute(options) !== null
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function findRoute (options) {
|
|
175
|
+
const route = router.find(
|
|
172
176
|
options.method,
|
|
173
177
|
options.url || '',
|
|
174
178
|
options.constraints
|
|
175
|
-
)
|
|
179
|
+
)
|
|
180
|
+
if (route) {
|
|
181
|
+
// we must reduce the expose surface, otherwise
|
|
182
|
+
// we provide the ability for the user to modify
|
|
183
|
+
// all the route and server information in runtime
|
|
184
|
+
return {
|
|
185
|
+
handler: route.handler,
|
|
186
|
+
params: route.params,
|
|
187
|
+
searchParams: route.searchParams
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
return null
|
|
191
|
+
}
|
|
176
192
|
}
|
|
177
193
|
|
|
178
194
|
/**
|
|
@@ -428,7 +444,7 @@ function buildRouting (options) {
|
|
|
428
444
|
|
|
429
445
|
// HTTP request entry point, the routing has already been executed
|
|
430
446
|
function routeHandler (req, res, params, context, query) {
|
|
431
|
-
const id =
|
|
447
|
+
const id = getGenReqId(context.server, req)
|
|
432
448
|
|
|
433
449
|
const loggerOpts = {
|
|
434
450
|
level: context.logLevel
|