From 1f06beaaeb127abef37561c77782d7c5dbabe826 Mon Sep 17 00:00:00 2001 From: Artur K Date: Sun, 31 Oct 2021 19:46:43 +0200 Subject: [PATCH 1/2] Add requestTimeout option --- build/build-validation.js | 2 + docs/Server.md | 12 + fastify.d.ts | 2 + fastify.js | 1 + lib/configValidator.js | 878 ++++++++++++++------------- lib/server.js | 1 + test/internals/initialConfig.test.js | 2 + 7 files changed, 475 insertions(+), 423 deletions(-) diff --git a/build/build-validation.js b/build/build-validation.js index 65b8053d0b..d3429d36db 100644 --- a/build/build-validation.js +++ b/build/build-validation.js @@ -16,6 +16,7 @@ const defaultInitOptions = { connectionTimeout: 0, // 0 sec keepAliveTimeout: 5000, // 5 sec maxRequestsPerSocket: 0, // no limit + requestTimeout: 0, // no limit bodyLimit: 1024 * 1024, // 1 MiB caseSensitive: true, disableRequestLogging: false, @@ -49,6 +50,7 @@ const schema = { connectionTimeout: { type: 'integer', default: defaultInitOptions.connectionTimeout }, keepAliveTimeout: { type: 'integer', default: defaultInitOptions.keepAliveTimeout }, maxRequestsPerSocket: { type: 'integer', default: defaultInitOptions.maxRequestsPerSocket, nullable: true }, + requestTimeout: { type: 'integer', default: defaultInitOptions.requestTimeout }, bodyLimit: { type: 'integer', default: defaultInitOptions.bodyLimit }, caseSensitive: { type: 'boolean', default: defaultInitOptions.caseSensitive }, http2: { type: 'boolean' }, diff --git a/docs/Server.md b/docs/Server.md index b2504e5e3b..a637b0503c 100644 --- a/docs/Server.md +++ b/docs/Server.md @@ -13,6 +13,7 @@ document describes the properties available in that options object. - [connectionTimeout](./Server.md#connectiontimeout) - [keepAliveTimeout](./Server.md#keepalivetimeout) - [maxRequestsPerSocket](./Server.md#maxRequestsPerSocket) +- [requestTimeout](./Server.md#requestTimeout) - [ignoreTrailingSlash](./Server.md#ignoretrailingslash) - [maxParamLength](./Server.md#maxparamlength) - [onProtoPoisoning](./Server.md#onprotopoisoning) @@ -94,6 +95,17 @@ is in use. Also, when `serverFactory` option is specified, this option is ignore + Default: `0` (no limit) + +### `requestTimeout` + +Defines the maximum number of milliseconds for receiving the entire request from the client. +[`server.requestTimeout` property](https://nodejs.org/dist/latest/docs/api/http.html#http_server_requesttimeout) +to understand the effect of this option. Also, when `serverFactory` option is specified, this option is ignored. +It must be set to a non-zero value (e.g. 120 seconds) to protect against potential Denial-of-Service attacks in case the server is deployed without a reverse proxy in front. +> At the time of this writing, only node version greater or equal to 14.11.0 support this option. Check the Node.js documentation for availability in the version you are running. + ++ Default: `0` (no limit) + ### `ignoreTrailingSlash` diff --git a/fastify.d.ts b/fastify.d.ts index ef3ec5e168..0aca4418e6 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -97,6 +97,8 @@ export type FastifyServerOptions< ignoreTrailingSlash?: boolean, connectionTimeout?: number, keepAliveTimeout?: number, + maxRequestsPerSocket?: number, + requestTimeout?: number, pluginTimeout?: number, bodyLimit?: number, maxParamLength?: number, diff --git a/fastify.js b/fastify.js index 222298ee03..679bb65516 100644 --- a/fastify.js +++ b/fastify.js @@ -133,6 +133,7 @@ function fastify (options) { options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout options.maxRequestsPerSocket = options.maxRequestsPerSocket || defaultInitOptions.maxRequestsPerSocket + options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout options.logger = logger options.genReqId = genReqId options.requestIdHeader = requestIdHeader diff --git a/lib/configValidator.js b/lib/configValidator.js index 03edb80855..7e6deb5070 100644 --- a/lib/configValidator.js +++ b/lib/configValidator.js @@ -15,6 +15,7 @@ var validate = (function() { if (data.connectionTimeout === undefined) data.connectionTimeout = 0; if (data.keepAliveTimeout === undefined) data.keepAliveTimeout = 5000; if (data.maxRequestsPerSocket === undefined) data.maxRequestsPerSocket = 0; + if (data.requestTimeout === undefined) data.requestTimeout = 0; if (data.bodyLimit === undefined) data.bodyLimit = 1048576; if (data.caseSensitive === undefined) data.caseSensitive = true; if (data.ignoreTrailingSlash === undefined) data.ignoreTrailingSlash = false; @@ -114,7 +115,7 @@ var validate = (function() { } var valid1 = errors === errs_1; if (valid1) { - var data1 = data.bodyLimit; + var data1 = data.requestTimeout; var errs_1 = errors; if ((typeof data1 !== "number" || (data1 % 1) || data1 !== data1)) { var dataType1 = typeof data1; @@ -124,8 +125,8 @@ var validate = (function() { else { validate.errors = [{ keyword: 'type', - dataPath: (dataPath || '') + '.bodyLimit', - schemaPath: '#/properties/bodyLimit/type', + dataPath: (dataPath || '') + '.requestTimeout', + schemaPath: '#/properties/requestTimeout/type', params: { type: 'integer' }, @@ -135,298 +136,297 @@ var validate = (function() { } if (coerced1 !== undefined) { data1 = coerced1; - data['bodyLimit'] = coerced1; + data['requestTimeout'] = coerced1; } } var valid1 = errors === errs_1; if (valid1) { - var data1 = data.caseSensitive; + var data1 = data.bodyLimit; var errs_1 = errors; - if (typeof data1 !== "boolean") { + if ((typeof data1 !== "number" || (data1 % 1) || data1 !== data1)) { var dataType1 = typeof data1; var coerced1 = undefined; if (coerced1 !== undefined); - else if (data1 === 'false' || data1 === 0 || data1 === null) coerced1 = false; - else if (data1 === 'true' || data1 === 1) coerced1 = true; + else if (dataType1 == 'boolean' || data1 === null || (dataType1 == 'string' && data1 && data1 == +data1 && !(data1 % 1))) coerced1 = +data1; else { validate.errors = [{ keyword: 'type', - dataPath: (dataPath || '') + '.caseSensitive', - schemaPath: '#/properties/caseSensitive/type', + dataPath: (dataPath || '') + '.bodyLimit', + schemaPath: '#/properties/bodyLimit/type', params: { - type: 'boolean' + type: 'integer' }, - message: 'should be boolean' + message: 'should be integer' }]; return false; } if (coerced1 !== undefined) { data1 = coerced1; - data['caseSensitive'] = coerced1; + data['bodyLimit'] = coerced1; } } var valid1 = errors === errs_1; if (valid1) { - var data1 = data.http2; - if (data1 === undefined) { - valid1 = true; - } else { - var errs_1 = errors; - if (typeof data1 !== "boolean") { - var dataType1 = typeof data1; - var coerced1 = undefined; - if (coerced1 !== undefined); - else if (data1 === 'false' || data1 === 0 || data1 === null) coerced1 = false; - else if (data1 === 'true' || data1 === 1) coerced1 = true; - else { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.http2', - schemaPath: '#/properties/http2/type', - params: { - type: 'boolean' - }, - message: 'should be boolean' - }]; - return false; - } - if (coerced1 !== undefined) { - data1 = coerced1; - data['http2'] = coerced1; - } + var data1 = data.caseSensitive; + var errs_1 = errors; + if (typeof data1 !== "boolean") { + var dataType1 = typeof data1; + var coerced1 = undefined; + if (coerced1 !== undefined); + else if (data1 === 'false' || data1 === 0 || data1 === null) coerced1 = false; + else if (data1 === 'true' || data1 === 1) coerced1 = true; + else { + validate.errors = [{ + keyword: 'type', + dataPath: (dataPath || '') + '.caseSensitive', + schemaPath: '#/properties/caseSensitive/type', + params: { + type: 'boolean' + }, + message: 'should be boolean' + }]; + return false; + } + if (coerced1 !== undefined) { + data1 = coerced1; + data['caseSensitive'] = coerced1; } - var valid1 = errors === errs_1; } + var valid1 = errors === errs_1; if (valid1) { - var data1 = data.https; + var data1 = data.http2; if (data1 === undefined) { valid1 = true; } else { var errs_1 = errors; - var errs__1 = errors; - var valid1 = true; - var errs_2 = errors; - var errs__2 = errors; - var errs_3 = errors; - var errs__3 = errors, - prevValid3 = false, - valid3 = false, - passingSchemas3 = null; - var errs_4 = errors; if (typeof data1 !== "boolean") { - var dataType4 = typeof data1; - var coerced4 = undefined; - if (coerced4 !== undefined); - else if (data1 === 'false' || data1 === 0 || data1 === null) coerced4 = false; - else if (data1 === 'true' || data1 === 1) coerced4 = true; - else { - var err = {}; - if (vErrors === null) vErrors = [err]; - else vErrors.push(err); - errors++; - } - if (coerced4 !== undefined) { - data1 = coerced4; - data['https'] = coerced4; - } - } - var valid4 = errors === errs_4; - if (valid4) { - valid3 = prevValid3 = true; - passingSchemas3 = 0; - } - var errs_4 = errors; - if (data1 !== null) { - var dataType4 = typeof data1; - var coerced4 = undefined; - if (coerced4 !== undefined); - else if (data1 === '' || data1 === 0 || data1 === false) coerced4 = null; + var dataType1 = typeof data1; + var coerced1 = undefined; + if (coerced1 !== undefined); + else if (data1 === 'false' || data1 === 0 || data1 === null) coerced1 = false; + else if (data1 === 'true' || data1 === 1) coerced1 = true; else { - var err = {}; - if (vErrors === null) vErrors = [err]; - else vErrors.push(err); - errors++; + validate.errors = [{ + keyword: 'type', + dataPath: (dataPath || '') + '.http2', + schemaPath: '#/properties/http2/type', + params: { + type: 'boolean' + }, + message: 'should be boolean' + }]; + return false; } - if (coerced4 !== undefined) { - data1 = coerced4; - data['https'] = coerced4; + if (coerced1 !== undefined) { + data1 = coerced1; + data['http2'] = coerced1; } } - var valid4 = errors === errs_4; - if (valid4 && prevValid3) { - valid3 = false; - passingSchemas3 = [passingSchemas3, 1]; + var valid1 = errors === errs_1; + } + if (valid1) { + var data1 = data.https; + if (data1 === undefined) { + valid1 = true; } else { + var errs_1 = errors; + var errs__1 = errors; + var valid1 = true; + var errs_2 = errors; + var errs__2 = errors; + var errs_3 = errors; + var errs__3 = errors, + prevValid3 = false, + valid3 = false, + passingSchemas3 = null; + var errs_4 = errors; + if (typeof data1 !== "boolean") { + var dataType4 = typeof data1; + var coerced4 = undefined; + if (coerced4 !== undefined); + else if (data1 === 'false' || data1 === 0 || data1 === null) coerced4 = false; + else if (data1 === 'true' || data1 === 1) coerced4 = true; + else { + var err = {}; + if (vErrors === null) vErrors = [err]; + else vErrors.push(err); + errors++; + } + if (coerced4 !== undefined) { + data1 = coerced4; + data['https'] = coerced4; + } + } + var valid4 = errors === errs_4; if (valid4) { valid3 = prevValid3 = true; - passingSchemas3 = 1; + passingSchemas3 = 0; } var errs_4 = errors; - if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) { - if (true) { - var errs__4 = errors; - var valid5 = true; - for (var key4 in data1) { - var isAdditional4 = !(false || key4 == 'allowHTTP1'); - if (isAdditional4) { - delete data1[key4]; + if (data1 !== null) { + var dataType4 = typeof data1; + var coerced4 = undefined; + if (coerced4 !== undefined); + else if (data1 === '' || data1 === 0 || data1 === false) coerced4 = null; + else { + var err = {}; + if (vErrors === null) vErrors = [err]; + else vErrors.push(err); + errors++; + } + if (coerced4 !== undefined) { + data1 = coerced4; + data['https'] = coerced4; + } + } + var valid4 = errors === errs_4; + if (valid4 && prevValid3) { + valid3 = false; + passingSchemas3 = [passingSchemas3, 1]; + } else { + if (valid4) { + valid3 = prevValid3 = true; + passingSchemas3 = 1; + } + var errs_4 = errors; + if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) { + if (true) { + var errs__4 = errors; + var valid5 = true; + for (var key4 in data1) { + var isAdditional4 = !(false || key4 == 'allowHTTP1'); + if (isAdditional4) { + delete data1[key4]; + } } - } - if (valid5) { - var data2 = data1.allowHTTP1; - if (data2 === undefined) { - valid5 = false; - var err = {}; - if (vErrors === null) vErrors = [err]; - else vErrors.push(err); - errors++; - } else { - var errs_5 = errors; - if (typeof data2 !== "boolean") { - var dataType5 = typeof data2; - var coerced5 = undefined; - if (coerced5 !== undefined); - else if (data2 === 'false' || data2 === 0 || data2 === null) coerced5 = false; - else if (data2 === 'true' || data2 === 1) coerced5 = true; - else { - var err = {}; - if (vErrors === null) vErrors = [err]; - else vErrors.push(err); - errors++; - } - if (coerced5 !== undefined) { - data2 = coerced5; - data1['allowHTTP1'] = coerced5; + if (valid5) { + var data2 = data1.allowHTTP1; + if (data2 === undefined) { + valid5 = false; + var err = {}; + if (vErrors === null) vErrors = [err]; + else vErrors.push(err); + errors++; + } else { + var errs_5 = errors; + if (typeof data2 !== "boolean") { + var dataType5 = typeof data2; + var coerced5 = undefined; + if (coerced5 !== undefined); + else if (data2 === 'false' || data2 === 0 || data2 === null) coerced5 = false; + else if (data2 === 'true' || data2 === 1) coerced5 = true; + else { + var err = {}; + if (vErrors === null) vErrors = [err]; + else vErrors.push(err); + errors++; + } + if (coerced5 !== undefined) { + data2 = coerced5; + data1['allowHTTP1'] = coerced5; + } } + var valid5 = errors === errs_5; } - var valid5 = errors === errs_5; + if (valid5) {} } - if (valid5) {} + if (errs__4 == errors) {} } - if (errs__4 == errors) {} + } else { + var err = {}; + if (vErrors === null) vErrors = [err]; + else vErrors.push(err); + errors++; } - } else { + if (errors === errs_4) {} + var valid4 = errors === errs_4; + if (valid4 && prevValid3) { + valid3 = false; + passingSchemas3 = [passingSchemas3, 2]; + } else { + if (valid4) { + valid3 = prevValid3 = true; + passingSchemas3 = 2; + } + } + } + if (!valid3) { var err = {}; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; + } else { + errors = errs__3; + if (vErrors !== null) { + if (errs__3) vErrors.length = errs__3; + else vErrors = null; + } } - if (errors === errs_4) {} - var valid4 = errors === errs_4; - if (valid4 && prevValid3) { - valid3 = false; - passingSchemas3 = [passingSchemas3, 2]; + if (errors === errs_3) {} + var valid3 = errors === errs_3; + if (valid3) { + var err = {}; + if (vErrors === null) vErrors = [err]; + else vErrors.push(err); + errors++; } else { - if (valid4) { - valid3 = prevValid3 = true; - passingSchemas3 = 2; + errors = errs__2; + if (vErrors !== null) { + if (errs__2) vErrors.length = errs__2; + else vErrors = null; } } - } - if (!valid3) { - var err = {}; - if (vErrors === null) vErrors = [err]; - else vErrors.push(err); - errors++; - } else { - errors = errs__3; + if (errors === errs_2) {} + var valid2 = errors === errs_2; + errors = errs__1; if (vErrors !== null) { - if (errs__3) vErrors.length = errs__3; + if (errs__1) vErrors.length = errs__1; else vErrors = null; } - } - if (errors === errs_3) {} - var valid3 = errors === errs_3; - if (valid3) { - var err = {}; - if (vErrors === null) vErrors = [err]; - else vErrors.push(err); - errors++; - } else { - errors = errs__2; - if (vErrors !== null) { - if (errs__2) vErrors.length = errs__2; - else vErrors = null; + if (valid2) { + var errs_2 = errors; + customRule0.errors = null; + var errs__2 = errors; + var valid2; + valid2 = customRule0.call(self, validate.schema.properties.https.then.setDefaultValue, data1, validate.schema.properties.https.then, (dataPath || '') + '.https', data, 'https', rootData); + if (data) data1 = data['https']; + if (!valid2) { + validate.errors = [{ + keyword: 'setDefaultValue', + dataPath: (dataPath || '') + '.https', + schemaPath: '#/properties/https/then/setDefaultValue', + params: { + keyword: 'setDefaultValue' + }, + message: 'should pass "setDefaultValue" keyword validation' + }]; + return false; + } else {} + if (errors === errs_2) {} + var valid2 = errors === errs_2; + valid1 = valid2; } - } - if (errors === errs_2) {} - var valid2 = errors === errs_2; - errors = errs__1; - if (vErrors !== null) { - if (errs__1) vErrors.length = errs__1; - else vErrors = null; - } - if (valid2) { - var errs_2 = errors; - customRule0.errors = null; - var errs__2 = errors; - var valid2; - valid2 = customRule0.call(self, validate.schema.properties.https.then.setDefaultValue, data1, validate.schema.properties.https.then, (dataPath || '') + '.https', data, 'https', rootData); - if (data) data1 = data['https']; - if (!valid2) { - validate.errors = [{ - keyword: 'setDefaultValue', + if (!valid1) { + var err = { + keyword: 'if', dataPath: (dataPath || '') + '.https', - schemaPath: '#/properties/https/then/setDefaultValue', + schemaPath: '#/properties/https/if', params: { - keyword: 'setDefaultValue' + failingKeyword: 'then' }, - message: 'should pass "setDefaultValue" keyword validation' - }]; + message: 'should match "' + 'then' + '" schema' + }; + if (vErrors === null) vErrors = [err]; + else vErrors.push(err); + errors++; + validate.errors = vErrors; return false; } else {} - if (errors === errs_2) {} - var valid2 = errors === errs_2; - valid1 = valid2; - } - if (!valid1) { - var err = { - keyword: 'if', - dataPath: (dataPath || '') + '.https', - schemaPath: '#/properties/https/if', - params: { - failingKeyword: 'then' - }, - message: 'should match "' + 'then' + '" schema' - }; - if (vErrors === null) vErrors = [err]; - else vErrors.push(err); - errors++; - validate.errors = vErrors; - return false; - } else {} - if (errors === errs_1) {} - var valid1 = errors === errs_1; - } - if (valid1) { - var data1 = data.ignoreTrailingSlash; - var errs_1 = errors; - if (typeof data1 !== "boolean") { - var dataType1 = typeof data1; - var coerced1 = undefined; - if (coerced1 !== undefined); - else if (data1 === 'false' || data1 === 0 || data1 === null) coerced1 = false; - else if (data1 === 'true' || data1 === 1) coerced1 = true; - else { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.ignoreTrailingSlash', - schemaPath: '#/properties/ignoreTrailingSlash/type', - params: { - type: 'boolean' - }, - message: 'should be boolean' - }]; - return false; - } - if (coerced1 !== undefined) { - data1 = coerced1; - data['ignoreTrailingSlash'] = coerced1; - } + if (errors === errs_1) {} + var valid1 = errors === errs_1; } - var valid1 = errors === errs_1; if (valid1) { - var data1 = data.disableRequestLogging; + var data1 = data.ignoreTrailingSlash; var errs_1 = errors; if (typeof data1 !== "boolean") { var dataType1 = typeof data1; @@ -437,8 +437,8 @@ var validate = (function() { else { validate.errors = [{ keyword: 'type', - dataPath: (dataPath || '') + '.disableRequestLogging', - schemaPath: '#/properties/disableRequestLogging/type', + dataPath: (dataPath || '') + '.ignoreTrailingSlash', + schemaPath: '#/properties/ignoreTrailingSlash/type', params: { type: 'boolean' }, @@ -448,12 +448,12 @@ var validate = (function() { } if (coerced1 !== undefined) { data1 = coerced1; - data['disableRequestLogging'] = coerced1; + data['ignoreTrailingSlash'] = coerced1; } } var valid1 = errors === errs_1; if (valid1) { - var data1 = data.jsonShorthand; + var data1 = data.disableRequestLogging; var errs_1 = errors; if (typeof data1 !== "boolean") { var dataType1 = typeof data1; @@ -464,8 +464,8 @@ var validate = (function() { else { validate.errors = [{ keyword: 'type', - dataPath: (dataPath || '') + '.jsonShorthand', - schemaPath: '#/properties/jsonShorthand/type', + dataPath: (dataPath || '') + '.disableRequestLogging', + schemaPath: '#/properties/disableRequestLogging/type', params: { type: 'boolean' }, @@ -475,65 +475,65 @@ var validate = (function() { } if (coerced1 !== undefined) { data1 = coerced1; - data['jsonShorthand'] = coerced1; + data['disableRequestLogging'] = coerced1; } } var valid1 = errors === errs_1; if (valid1) { - var data1 = data.maxParamLength; + var data1 = data.jsonShorthand; var errs_1 = errors; - if ((typeof data1 !== "number" || (data1 % 1) || data1 !== data1)) { + if (typeof data1 !== "boolean") { var dataType1 = typeof data1; var coerced1 = undefined; if (coerced1 !== undefined); - else if (dataType1 == 'boolean' || data1 === null || (dataType1 == 'string' && data1 && data1 == +data1 && !(data1 % 1))) coerced1 = +data1; + else if (data1 === 'false' || data1 === 0 || data1 === null) coerced1 = false; + else if (data1 === 'true' || data1 === 1) coerced1 = true; else { validate.errors = [{ keyword: 'type', - dataPath: (dataPath || '') + '.maxParamLength', - schemaPath: '#/properties/maxParamLength/type', + dataPath: (dataPath || '') + '.jsonShorthand', + schemaPath: '#/properties/jsonShorthand/type', params: { - type: 'integer' + type: 'boolean' }, - message: 'should be integer' + message: 'should be boolean' }]; return false; } if (coerced1 !== undefined) { data1 = coerced1; - data['maxParamLength'] = coerced1; + data['jsonShorthand'] = coerced1; } } var valid1 = errors === errs_1; if (valid1) { - var data1 = data.onProtoPoisoning; + var data1 = data.maxParamLength; var errs_1 = errors; - if (typeof data1 !== "string") { + if ((typeof data1 !== "number" || (data1 % 1) || data1 !== data1)) { var dataType1 = typeof data1; var coerced1 = undefined; if (coerced1 !== undefined); - else if (dataType1 == 'number' || dataType1 == 'boolean') coerced1 = '' + data1; - else if (data1 === null) coerced1 = ''; + else if (dataType1 == 'boolean' || data1 === null || (dataType1 == 'string' && data1 && data1 == +data1 && !(data1 % 1))) coerced1 = +data1; else { validate.errors = [{ keyword: 'type', - dataPath: (dataPath || '') + '.onProtoPoisoning', - schemaPath: '#/properties/onProtoPoisoning/type', + dataPath: (dataPath || '') + '.maxParamLength', + schemaPath: '#/properties/maxParamLength/type', params: { - type: 'string' + type: 'integer' }, - message: 'should be string' + message: 'should be integer' }]; return false; } if (coerced1 !== undefined) { data1 = coerced1; - data['onProtoPoisoning'] = coerced1; + data['maxParamLength'] = coerced1; } } var valid1 = errors === errs_1; if (valid1) { - var data1 = data.onConstructorPoisoning; + var data1 = data.onProtoPoisoning; var errs_1 = errors; if (typeof data1 !== "string") { var dataType1 = typeof data1; @@ -544,8 +544,8 @@ var validate = (function() { else { validate.errors = [{ keyword: 'type', - dataPath: (dataPath || '') + '.onConstructorPoisoning', - schemaPath: '#/properties/onConstructorPoisoning/type', + dataPath: (dataPath || '') + '.onProtoPoisoning', + schemaPath: '#/properties/onProtoPoisoning/type', params: { type: 'string' }, @@ -555,65 +555,65 @@ var validate = (function() { } if (coerced1 !== undefined) { data1 = coerced1; - data['onConstructorPoisoning'] = coerced1; + data['onProtoPoisoning'] = coerced1; } } var valid1 = errors === errs_1; if (valid1) { - var data1 = data.pluginTimeout; + var data1 = data.onConstructorPoisoning; var errs_1 = errors; - if ((typeof data1 !== "number" || (data1 % 1) || data1 !== data1)) { + if (typeof data1 !== "string") { var dataType1 = typeof data1; var coerced1 = undefined; if (coerced1 !== undefined); - else if (dataType1 == 'boolean' || data1 === null || (dataType1 == 'string' && data1 && data1 == +data1 && !(data1 % 1))) coerced1 = +data1; + else if (dataType1 == 'number' || dataType1 == 'boolean') coerced1 = '' + data1; + else if (data1 === null) coerced1 = ''; else { validate.errors = [{ keyword: 'type', - dataPath: (dataPath || '') + '.pluginTimeout', - schemaPath: '#/properties/pluginTimeout/type', + dataPath: (dataPath || '') + '.onConstructorPoisoning', + schemaPath: '#/properties/onConstructorPoisoning/type', params: { - type: 'integer' + type: 'string' }, - message: 'should be integer' + message: 'should be string' }]; return false; } if (coerced1 !== undefined) { data1 = coerced1; - data['pluginTimeout'] = coerced1; + data['onConstructorPoisoning'] = coerced1; } } var valid1 = errors === errs_1; if (valid1) { - var data1 = data.requestIdHeader; + var data1 = data.pluginTimeout; var errs_1 = errors; - if (typeof data1 !== "string") { + if ((typeof data1 !== "number" || (data1 % 1) || data1 !== data1)) { var dataType1 = typeof data1; var coerced1 = undefined; if (coerced1 !== undefined); - else if (dataType1 == 'number' || dataType1 == 'boolean') coerced1 = '' + data1; - else if (data1 === null) coerced1 = ''; + else if (dataType1 == 'boolean' || data1 === null || (dataType1 == 'string' && data1 && data1 == +data1 && !(data1 % 1))) coerced1 = +data1; else { validate.errors = [{ keyword: 'type', - dataPath: (dataPath || '') + '.requestIdHeader', - schemaPath: '#/properties/requestIdHeader/type', + dataPath: (dataPath || '') + '.pluginTimeout', + schemaPath: '#/properties/pluginTimeout/type', params: { - type: 'string' + type: 'integer' }, - message: 'should be string' + message: 'should be integer' }]; return false; } if (coerced1 !== undefined) { data1 = coerced1; - data['requestIdHeader'] = coerced1; + data['pluginTimeout'] = coerced1; } } var valid1 = errors === errs_1; if (valid1) { - var data1 = data.requestIdLogLabel; + var data1 = data.requestIdHeader; var errs_1 = errors; if (typeof data1 !== "string") { var dataType1 = typeof data1; @@ -624,8 +624,8 @@ var validate = (function() { else { validate.errors = [{ keyword: 'type', - dataPath: (dataPath || '') + '.requestIdLogLabel', - schemaPath: '#/properties/requestIdLogLabel/type', + dataPath: (dataPath || '') + '.requestIdHeader', + schemaPath: '#/properties/requestIdHeader/type', params: { type: 'string' }, @@ -635,190 +635,101 @@ var validate = (function() { } if (coerced1 !== undefined) { data1 = coerced1; - data['requestIdLogLabel'] = coerced1; + data['requestIdHeader'] = coerced1; } } var valid1 = errors === errs_1; if (valid1) { - var data1 = data.http2SessionTimeout; + var data1 = data.requestIdLogLabel; var errs_1 = errors; - if ((typeof data1 !== "number" || (data1 % 1) || data1 !== data1)) { + if (typeof data1 !== "string") { var dataType1 = typeof data1; var coerced1 = undefined; if (coerced1 !== undefined); - else if (dataType1 == 'boolean' || data1 === null || (dataType1 == 'string' && data1 && data1 == +data1 && !(data1 % 1))) coerced1 = +data1; + else if (dataType1 == 'number' || dataType1 == 'boolean') coerced1 = '' + data1; + else if (data1 === null) coerced1 = ''; else { validate.errors = [{ keyword: 'type', - dataPath: (dataPath || '') + '.http2SessionTimeout', - schemaPath: '#/properties/http2SessionTimeout/type', + dataPath: (dataPath || '') + '.requestIdLogLabel', + schemaPath: '#/properties/requestIdLogLabel/type', params: { - type: 'integer' + type: 'string' }, - message: 'should be integer' + message: 'should be string' }]; return false; } if (coerced1 !== undefined) { data1 = coerced1; - data['http2SessionTimeout'] = coerced1; + data['requestIdLogLabel'] = coerced1; } } var valid1 = errors === errs_1; if (valid1) { - var data1 = data.versioning; - if (data1 === undefined) { - valid1 = true; - } else { - var errs_1 = errors; - if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) { - var missing1; - if (((data1.storage === undefined) && (missing1 = '.storage')) || ((data1.deriveVersion === undefined) && (missing1 = '.deriveVersion'))) { - validate.errors = [{ - keyword: 'required', - dataPath: (dataPath || '') + '.versioning', - schemaPath: '#/properties/versioning/required', - params: { - missingProperty: '' + missing1 + '' - }, - message: 'should have required property \'' + missing1 + '\'' - }]; - return false; - } else { - var errs__1 = errors; - var valid2 = true; - for (var key1 in data1) { - var isAdditional1 = !(false || key1 == 'storage' || key1 == 'deriveVersion'); - if (isAdditional1) {} - } - if (valid2) { - if (valid2) { - if (valid2) {} - } - } - if (errs__1 == errors) {} - } - } else { + var data1 = data.http2SessionTimeout; + var errs_1 = errors; + if ((typeof data1 !== "number" || (data1 % 1) || data1 !== data1)) { + var dataType1 = typeof data1; + var coerced1 = undefined; + if (coerced1 !== undefined); + else if (dataType1 == 'boolean' || data1 === null || (dataType1 == 'string' && data1 && data1 == +data1 && !(data1 % 1))) coerced1 = +data1; + else { validate.errors = [{ keyword: 'type', - dataPath: (dataPath || '') + '.versioning', - schemaPath: '#/properties/versioning/type', + dataPath: (dataPath || '') + '.http2SessionTimeout', + schemaPath: '#/properties/http2SessionTimeout/type', params: { - type: 'object' + type: 'integer' }, - message: 'should be object' + message: 'should be integer' }]; return false; } - if (errors === errs_1) {} - var valid1 = errors === errs_1; + if (coerced1 !== undefined) { + data1 = coerced1; + data['http2SessionTimeout'] = coerced1; + } } + var valid1 = errors === errs_1; if (valid1) { - var data1 = data.constraints; + var data1 = data.versioning; if (data1 === undefined) { valid1 = true; } else { var errs_1 = errors; if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) { - var errs__1 = errors; - var valid2 = true; - for (var key1 in data1) { - var data2 = data1[key1]; - var errs_2 = errors; - if ((data2 && typeof data2 === "object" && !Array.isArray(data2))) { - var missing2; - if (((data2.storage === undefined) && (missing2 = '.storage')) || ((data2.validate === undefined) && (missing2 = '.validate')) || ((data2.deriveConstraint === undefined) && (missing2 = '.deriveConstraint'))) { - validate.errors = [{ - keyword: 'required', - dataPath: (dataPath || '') + '.constraints[\'' + key1 + '\']', - schemaPath: '#/properties/constraints/additionalProperties/required', - params: { - missingProperty: '' + missing2 + '' - }, - message: 'should have required property \'' + missing2 + '\'' - }]; - return false; - } else { - var errs__2 = errors; - var valid3 = true; - for (var key2 in data2) { - var isAdditional2 = !(false || key2 == 'name' || key2 == 'storage' || key2 == 'validate' || key2 == 'deriveConstraint'); - if (isAdditional2) {} - } - if (valid3) { - var data3 = data2.name; - if (data3 === undefined) { - valid3 = false; - validate.errors = [{ - keyword: 'required', - dataPath: (dataPath || '') + '.constraints[\'' + key1 + '\']', - schemaPath: '#/properties/constraints/additionalProperties/required', - params: { - missingProperty: 'name' - }, - message: 'should have required property \'name\'' - }]; - return false; - } else { - var errs_3 = errors; - if (typeof data3 !== "string") { - var dataType3 = typeof data3; - var coerced3 = undefined; - if (coerced3 !== undefined); - else if (dataType3 == 'number' || dataType3 == 'boolean') coerced3 = '' + data3; - else if (data3 === null) coerced3 = ''; - else { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.constraints[\'' + key1 + '\'].name', - schemaPath: '#/properties/constraints/additionalProperties/properties/name/type', - params: { - type: 'string' - }, - message: 'should be string' - }]; - return false; - } - if (coerced3 !== undefined) { - data3 = coerced3; - data2['name'] = coerced3; - } - } - var valid3 = errors === errs_3; - } - if (valid3) { - if (valid3) { - if (valid3) { - if (valid3) {} - } - } - } - } - if (errs__2 == errors) {} + var missing1; + if (((data1.storage === undefined) && (missing1 = '.storage')) || ((data1.deriveVersion === undefined) && (missing1 = '.deriveVersion'))) { + validate.errors = [{ + keyword: 'required', + dataPath: (dataPath || '') + '.versioning', + schemaPath: '#/properties/versioning/required', + params: { + missingProperty: '' + missing1 + '' + }, + message: 'should have required property \'' + missing1 + '\'' + }]; + return false; + } else { + var errs__1 = errors; + var valid2 = true; + for (var key1 in data1) { + var isAdditional1 = !(false || key1 == 'storage' || key1 == 'deriveVersion'); + if (isAdditional1) {} + } + if (valid2) { + if (valid2) { + if (valid2) {} } - } else { - validate.errors = [{ - keyword: 'type', - dataPath: (dataPath || '') + '.constraints[\'' + key1 + '\']', - schemaPath: '#/properties/constraints/additionalProperties/type', - params: { - type: 'object' - }, - message: 'should be object' - }]; - return false; } - if (errors === errs_2) {} - var valid2 = errors === errs_2; - if (!valid2) break; + if (errs__1 == errors) {} } - if (valid2) {} - if (errs__1 == errors) {} } else { validate.errors = [{ keyword: 'type', - dataPath: (dataPath || '') + '.constraints', - schemaPath: '#/properties/constraints/type', + dataPath: (dataPath || '') + '.versioning', + schemaPath: '#/properties/versioning/type', params: { type: 'object' }, @@ -829,7 +740,124 @@ var validate = (function() { if (errors === errs_1) {} var valid1 = errors === errs_1; } - if (valid1) {} + if (valid1) { + var data1 = data.constraints; + if (data1 === undefined) { + valid1 = true; + } else { + var errs_1 = errors; + if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) { + var errs__1 = errors; + var valid2 = true; + for (var key1 in data1) { + var data2 = data1[key1]; + var errs_2 = errors; + if ((data2 && typeof data2 === "object" && !Array.isArray(data2))) { + var missing2; + if (((data2.storage === undefined) && (missing2 = '.storage')) || ((data2.validate === undefined) && (missing2 = '.validate')) || ((data2.deriveConstraint === undefined) && (missing2 = '.deriveConstraint'))) { + validate.errors = [{ + keyword: 'required', + dataPath: (dataPath || '') + '.constraints[\'' + key1 + '\']', + schemaPath: '#/properties/constraints/additionalProperties/required', + params: { + missingProperty: '' + missing2 + '' + }, + message: 'should have required property \'' + missing2 + '\'' + }]; + return false; + } else { + var errs__2 = errors; + var valid3 = true; + for (var key2 in data2) { + var isAdditional2 = !(false || key2 == 'name' || key2 == 'storage' || key2 == 'validate' || key2 == 'deriveConstraint'); + if (isAdditional2) {} + } + if (valid3) { + var data3 = data2.name; + if (data3 === undefined) { + valid3 = false; + validate.errors = [{ + keyword: 'required', + dataPath: (dataPath || '') + '.constraints[\'' + key1 + '\']', + schemaPath: '#/properties/constraints/additionalProperties/required', + params: { + missingProperty: 'name' + }, + message: 'should have required property \'name\'' + }]; + return false; + } else { + var errs_3 = errors; + if (typeof data3 !== "string") { + var dataType3 = typeof data3; + var coerced3 = undefined; + if (coerced3 !== undefined); + else if (dataType3 == 'number' || dataType3 == 'boolean') coerced3 = '' + data3; + else if (data3 === null) coerced3 = ''; + else { + validate.errors = [{ + keyword: 'type', + dataPath: (dataPath || '') + '.constraints[\'' + key1 + '\'].name', + schemaPath: '#/properties/constraints/additionalProperties/properties/name/type', + params: { + type: 'string' + }, + message: 'should be string' + }]; + return false; + } + if (coerced3 !== undefined) { + data3 = coerced3; + data2['name'] = coerced3; + } + } + var valid3 = errors === errs_3; + } + if (valid3) { + if (valid3) { + if (valid3) { + if (valid3) {} + } + } + } + } + if (errs__2 == errors) {} + } + } else { + validate.errors = [{ + keyword: 'type', + dataPath: (dataPath || '') + '.constraints[\'' + key1 + '\']', + schemaPath: '#/properties/constraints/additionalProperties/type', + params: { + type: 'object' + }, + message: 'should be object' + }]; + return false; + } + if (errors === errs_2) {} + var valid2 = errors === errs_2; + if (!valid2) break; + } + if (valid2) {} + if (errs__1 == errors) {} + } else { + validate.errors = [{ + keyword: 'type', + dataPath: (dataPath || '') + '.constraints', + schemaPath: '#/properties/constraints/type', + params: { + type: 'object' + }, + message: 'should be object' + }]; + return false; + } + if (errors === errs_1) {} + var valid1 = errors === errs_1; + } + if (valid1) {} + } } } } @@ -884,6 +912,10 @@ validate.schema = { "default": 0, "nullable": true }, + "requestTimeout": { + "type": "integer", + "default": 0 + }, "bodyLimit": { "type": "integer", "default": 1048576 @@ -993,4 +1025,4 @@ function customRule0 (schemaParamValue, validatedParamValue, validationSchemaObj return true } -module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":5000,"maxRequestsPerSocket":0,"bodyLimit":1048576,"caseSensitive":true,"disableRequestLogging":false,"jsonShorthand":true,"ignoreTrailingSlash":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":"request-id","requestIdLogLabel":"reqId","http2SessionTimeout":5000} +module.exports.defaultInitOptions = {"connectionTimeout":0,"keepAliveTimeout":5000,"maxRequestsPerSocket":0,"requestTimeout":0,"bodyLimit":1048576,"caseSensitive":true,"disableRequestLogging":false,"jsonShorthand":true,"ignoreTrailingSlash":false,"maxParamLength":100,"onProtoPoisoning":"error","onConstructorPoisoning":"error","pluginTimeout":10000,"requestIdHeader":"request-id","requestIdLogLabel":"reqId","http2SessionTimeout":5000} diff --git a/lib/server.js b/lib/server.js index d8510af995..d34fabf99a 100644 --- a/lib/server.js +++ b/lib/server.js @@ -28,6 +28,7 @@ function createServer (options, httpHandler) { } else { server = http.createServer(httpHandler) server.keepAliveTimeout = options.keepAliveTimeout + server.requestTimeout = options.requestTimeout // we treat zero as null // and null is the default setting from nodejs // so we do not pass the option to server diff --git a/test/internals/initialConfig.test.js b/test/internals/initialConfig.test.js index 719d8f00ca..a2ddaf6b6c 100644 --- a/test/internals/initialConfig.test.js +++ b/test/internals/initialConfig.test.js @@ -25,6 +25,7 @@ test('without options passed to Fastify, initialConfig should expose default val connectionTimeout: 0, keepAliveTimeout: 5000, maxRequestsPerSocket: 0, + requestTimeout: 0, bodyLimit: 1024 * 1024, caseSensitive: true, disableRequestLogging: false, @@ -242,6 +243,7 @@ test('Should not have issues when passing stream options to Pino.js', t => { connectionTimeout: 0, keepAliveTimeout: 5000, maxRequestsPerSocket: 0, + requestTimeout: 0, bodyLimit: 1024 * 1024, caseSensitive: true, disableRequestLogging: false, From a18f523f1e70f045e0993d92c22ba080e775d485 Mon Sep 17 00:00:00 2001 From: Artur K Date: Sun, 31 Oct 2021 20:01:14 +0200 Subject: [PATCH 2/2] Add tests --- test/requestTimeout.test.js | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 test/requestTimeout.test.js diff --git a/test/requestTimeout.test.js b/test/requestTimeout.test.js new file mode 100644 index 0000000000..7978ea3b48 --- /dev/null +++ b/test/requestTimeout.test.js @@ -0,0 +1,50 @@ +'use strict' + +const http = require('http') +const { test } = require('tap') +const Fastify = require('../fastify') + +test('requestTimeout passed to server', t => { + t.plan(4) + + try { + Fastify({ requestTimeout: 500.1 }) + t.fail('option must be an integer') + } catch (err) { + t.ok(err) + } + + try { + Fastify({ requestTimeout: [] }) + t.fail('option must be an integer') + } catch (err) { + t.ok(err) + } + + const httpServer = Fastify({ requestTimeout: 1000 }).server + t.equal(httpServer.requestTimeout, 1000) + + const serverFactory = (handler, _) => { + const server = http.createServer((req, res) => { + handler(req, res) + }) + server.requestTimeout = 5000 + return server + } + const customServer = Fastify({ requestTimeout: 4000, serverFactory }).server + t.equal(customServer.requestTimeout, 5000) +}) + +test('requestTimeout should be set', async (t) => { + t.plan(1) + + const initialConfig = Fastify({ requestTimeout: 5000 }).initialConfig + t.same(initialConfig.requestTimeout, 5000) +}) + +test('requestTimeout should 0', async (t) => { + t.plan(1) + + const initialConfig = Fastify().initialConfig + t.same(initialConfig.requestTimeout, 0) +})