From 43d61a57efaba3eaf1b63db1b28f28673e7cd8fa Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 30 Sep 2021 10:04:42 +0200 Subject: [PATCH 01/46] Bumped v3.22.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bb8ef06d5d..3ce1713d69 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "3.21.6", + "version": "3.22.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From ba364756e4afdd2061e25c21d9cdee9a93436410 Mon Sep 17 00:00:00 2001 From: Chia Wei Ong Date: Thu, 30 Sep 2021 23:13:07 +0200 Subject: [PATCH 02/46] Update NGINX section with comments and fixes (#3352) --- docs/Recommendations.md | 74 ++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/docs/Recommendations.md b/docs/Recommendations.md index 8156db8b05..f85b823af9 100644 --- a/docs/Recommendations.md +++ b/docs/Recommendations.md @@ -166,62 +166,94 @@ backend static-backend ### Nginx ```nginx +# This upstream block groups 3 servers into one named backend fastify_app +# with 2 primary servers distributed via round-robin +# and one backup which is used when the first 2 are not reachable +# This also assumes your fastify servers are listening on port 80. +# more info: http://nginx.org/en/docs/http/ngx_http_upstream_module.html upstream fastify_app { - # more info: http://nginx.org/en/docs/http/ngx_http_upstream_module.html server 10.10.11.1:80; server 10.10.11.2:80; server 10.10.11.3:80 backup; } +# This server block asks NGINX to respond with a redirect when +# an incoming request from port 80 (typically plain HTTP), to +# the same request URL but with HTTPS as protocol. +# This block is optional, and usually used if you are handling +# SSL termination in NGINX, like in the example here. server { - # default server + # default server is a special parameter to ask NGINX + # to set this server block to the default for this address/port + # which in this case is any address and port 80 listen 80 default_server; listen [::]:80 default_server; - # specify host + # With a server_name directive you can also ask NGINX to + # use this server block only with matching server name(s) # listen 80; # listen [::]:80; # server_name example.tld; + # This matches all paths from the request and responds with + # the redirect mentioned above. location / { return 301 https://$host$request_uri; } } +# This server block asks NGINX to respond to requests from +# port 443 with SSL enabled and accept HTTP/2 connections. +# This is where the request is then proxied to the fastify_app +# server group via port 3000. server { - # default server + # This listen directive asks NGINX to accept requests + # coming to any address, port 443, with SSL, and HTTP/2 + # if possible. listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; - - # specify host + + # With a server_name directive you can also ask NGINX to + # use this server block only with matching server name(s) # listen 443 ssl http2; # listen [::]:443 ssl http2; # server_name example.tld; - # public private keys + # Your SSL/TLS certificate (chain) and secret key in the PEM format ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/private.pem; - ssl_trusted_certificate /path/to/chain.pem; - # use https://ssl-config.mozilla.org/ for best practice configuration + # A generic best practice baseline for based + # on https://ssl-config.mozilla.org/ ssl_session_timeout 1d; ssl_session_cache shared:FastifyApp:10m; ssl_session_tickets off; - - # modern configuration + + # This tells NGINX to only accept TLS 1.3, which should be fine + # with most modern browsers including IE 11 with certain updates. + # If you want to support older browsers you might need to add + # additional fallback protocols. ssl_protocols TLSv1.3; ssl_prefer_server_ciphers off; - - # HSTS (ngx_http_headers_module is required) (63072000 seconds) + + # This adds a header that tells browsers to only ever use HTTPS + # with this server. add_header Strict-Transport-Security "max-age=63072000" always; - - # OCSP stapling + + # The following directives are only necessary if you want to + # enable OCSP Stapling. ssl_stapling on; ssl_stapling_verify on; + ssl_trusted_certificate /path/to/chain.pem; - # custom resolver + # Custom nameserver to resolve upstream server names # resolver 127.0.0.1; - + + # This section matches all paths and proxies it to the backend server + # group specified above. Note the additional headers that forward + # information about the original request. You might want to set + # trustProxy to the address of your NGINX server so the X-Forwarded + * fields are used by fastify. location / { # more info: http://nginx.org/en/docs/http/ngx_http_proxy_module.html proxy_http_version 1.1; @@ -232,8 +264,12 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - - proxy_pass http://fastify_app:3000; + + # This is the directive that proxies requests to the specified server. + # If you are using an upstream group, then you do not need to specify a port. + # If you are directly proxying to a server e.g. + # proxy_pass http://127.0.0.1:3000 then specify a port. + proxy_pass http://fastify_app; } } ``` From 22c470d39028143ce0928dc1fbda8d20c40dc6f5 Mon Sep 17 00:00:00 2001 From: Zoron Date: Tue, 5 Oct 2021 00:53:47 +0800 Subject: [PATCH 03/46] Update Validation-and-Serialization.md (#3359) fix typo: cohercion -> coercion --- docs/Validation-and-Serialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Validation-and-Serialization.md b/docs/Validation-and-Serialization.md index 80c21b7d56..8a16cdc8fc 100644 --- a/docs/Validation-and-Serialization.md +++ b/docs/Validation-and-Serialization.md @@ -257,7 +257,7 @@ curl -X GET "http://localhost:3000/?ids=1 You can also specify a custom schema validator for each parameter type (body, querystring, params, headers). -For example, the following code disable type cohercion only for the `body` parameters, changing the ajv default options: +For example, the following code disable type coercion only for the `body` parameters, changing the ajv default options: ```js const schemaCompilers = { From 9d48a3dfc7b9f5886462a068164b24b1077806f9 Mon Sep 17 00:00:00 2001 From: KaKa Date: Tue, 5 Oct 2021 03:16:48 +0800 Subject: [PATCH 04/46] fix(typescript): register missing args for function options (#3355) --- test/types/register.test-d.ts | 14 +++++++++++++- types/register.d.ts | 3 ++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/test/types/register.test-d.ts b/test/types/register.test-d.ts index 5de85c1872..ea265d2b58 100644 --- a/test/types/register.test-d.ts +++ b/test/types/register.test-d.ts @@ -1,4 +1,4 @@ -import { expectAssignable, expectError } from 'tsd' +import { expectAssignable, expectError, expectType } from 'tsd' import fastify, { FastifyInstance, FastifyPluginAsync } from '../../fastify' const testPluginOptsAsync: FastifyPluginAsync = async function (_instance, _opts) { } @@ -14,3 +14,15 @@ expectAssignable( testPluginOptsAsync, { prefix: '/example', logLevel: 'info', logSerializers: { key: (value: any) => `${value}` } } ) ) + +expectAssignable( + fastify().register(testPluginOptsAsync, () => { + return {} + }) +) + +expectAssignable( + fastify().register(testPluginOptsAsync, (instance) => { + expectType(instance) + }) +) diff --git a/types/register.d.ts b/types/register.d.ts index 24be26e83a..4021fd7794 100644 --- a/types/register.d.ts +++ b/types/register.d.ts @@ -1,5 +1,6 @@ import { FastifyPluginOptions, FastifyPluginCallback, FastifyPluginAsync } from './plugin' import { LogLevel } from './logger' +import { FastifyInstance } from './instance' interface RegisterOptions { prefix?: string; @@ -7,7 +8,7 @@ interface RegisterOptions { logSerializers?: Record string>; } -export type FastifyRegisterOptions = (RegisterOptions & Options) | (() => RegisterOptions & Options) +export type FastifyRegisterOptions = (RegisterOptions & Options) | ((instance: FastifyInstance) => RegisterOptions & Options) /** * FastifyRegister From 48ff6990b51edc120ed0cffd12ea0b6aaac1fcb2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Oct 2021 14:22:23 +0000 Subject: [PATCH 05/46] build(deps-dev): bump tsd from 0.17.0 to 0.18.0 (#3368) Bumps [tsd](https://github.com/SamVerschueren/tsd) from 0.17.0 to 0.18.0. - [Release notes](https://github.com/SamVerschueren/tsd/releases) - [Commits](https://github.com/SamVerschueren/tsd/compare/v0.17.0...v0.18.0) --- updated-dependencies: - dependency-name: tsd dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3ce1713d69..93495b2cb1 100644 --- a/package.json +++ b/package.json @@ -165,7 +165,7 @@ "tap": "^15.0.5", "tap-mocha-reporter": "^5.0.1", "then-sleep": "^1.0.1", - "tsd": "^0.17.0", + "tsd": "^0.18.0", "typescript": "^4.0.2", "undici": "^3.3.5", "x-xss-protection": "^2.0.0", From 47185b8ef2bdc9941fc594084daee9f6f1453639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Wed, 13 Oct 2021 18:28:04 +0200 Subject: [PATCH 06/46] test: fix typo in tap test pass message (#3370) --- test/schema-special-usage.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/schema-special-usage.test.js b/test/schema-special-usage.test.js index e3e4bbc4eb..8bc0140cfe 100644 --- a/test/schema-special-usage.test.js +++ b/test/schema-special-usage.test.js @@ -84,7 +84,7 @@ test('Ajv8 usage instead of the bundle one', t => { fastify.ready(err => { t.error(err) - t.pass('startup successfull') + t.pass('startup successful') }) }) }) From f796b75736023ecf8b336fdc14d2be1226332dea Mon Sep 17 00:00:00 2001 From: siemah <28986331+siemah@users.noreply.github.com> Date: Wed, 13 Oct 2021 22:24:31 +0100 Subject: [PATCH 07/46] docs: inform user to set target property (#3291) * doc:inform user about setting target property * docs(TypeScript): add note to es2017 or greater Co-authored-by: KaKa Co-authored-by: Rafael Gonzaga Co-authored-by: KaKa --- docs/TypeScript.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/TypeScript.md b/docs/TypeScript.md index 7aa458004a..279e5db96c 100644 --- a/docs/TypeScript.md +++ b/docs/TypeScript.md @@ -35,6 +35,9 @@ This example will get you up and running with Fastify and TypeScript. It results } } ``` + +*Note: Set `target` property in `tsconfig.json` to `es2017` or greater to avoid [FastifyDeprecation](https://github.com/fastify/fastify/issues/3284) warning.* + 3. Initialize a TypeScript configuration file: ```bash npx tsc --init From 1e186f48efef822c77036b4240b17e7f4511c720 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 14 Oct 2021 16:58:21 +0200 Subject: [PATCH 08/46] Drop readable-stream dependency (#3372) --- lib/reply.js | 2 +- package.json | 1 - test/internals/reply.test.js | 2 +- test/stream.test.js | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/reply.js b/lib/reply.js index e39274824c..2f79205f4d 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -1,6 +1,6 @@ 'use strict' -const eos = require('readable-stream').finished +const eos = require('stream').finished const statusCodes = require('http').STATUS_CODES const flatstr = require('flatstr') const FJS = require('fast-json-stringify') diff --git a/package.json b/package.json index 93495b2cb1..ce6c5d9d6c 100644 --- a/package.json +++ b/package.json @@ -183,7 +183,6 @@ "light-my-request": "^4.2.0", "pino": "^6.13.0", "proxy-addr": "^2.0.7", - "readable-stream": "^3.4.0", "rfdc": "^1.1.4", "secure-json-parse": "^2.0.0", "semver": "^7.3.2", diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index e32fe5656f..117b86ec06 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -7,7 +7,7 @@ const http = require('http') const NotFound = require('http-errors').NotFound const EventEmitter = require('events').EventEmitter const Reply = require('../../lib/reply') -const { Writable } = require('readable-stream') +const { Writable } = require('stream') const { kReplyErrorHandlerCalled, kReplyHeaders, diff --git a/test/stream.test.js b/test/stream.test.js index b732509e75..9e5be5d9ab 100644 --- a/test/stream.test.js +++ b/test/stream.test.js @@ -145,7 +145,7 @@ test('onSend hook stream should work even if payload is not a proper stream', t t.plan(1) const reply = proxyquire('../lib/reply', { - 'readable-stream': { + stream: { finished: (...args) => { if (args.length === 2) { args[1](new Error('test-error')) } } @@ -186,7 +186,7 @@ test('onSend hook stream should work on payload with "close" ending function', t t.plan(1) const reply = proxyquire('../lib/reply', { - 'readable-stream': { + stream: { finished: (...args) => { if (args.length === 2) { args[1](new Error('test-error')) } } From 6727d971c1fdd870187ee2e2c7012e1a0fc198d3 Mon Sep 17 00:00:00 2001 From: KaKa Date: Fri, 15 Oct 2021 14:25:53 +0800 Subject: [PATCH 09/46] fix: pipe stream inside error handler (#3375) --- lib/reply.js | 4 +++- test/reply-error.test.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/reply.js b/lib/reply.js index 2f79205f4d..b0ea2cfc87 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -549,7 +549,9 @@ function handleError (reply, error, cb) { reply[kReplySent] = false reply[kReplyIsError] = false reply[kReplyErrorHandlerCalled] = true - reply[kReplyHeaders]['content-length'] = undefined + // remove header is needed in here, because when we pipe to a stream + // `undefined` value header will directly passed to node response + reply.removeHeader('content-length') const result = errorHandler(error, reply.request, reply) if (result !== undefined) { if (result !== null && typeof result.then === 'function') { diff --git a/test/reply-error.test.js b/test/reply-error.test.js index 7af3224b23..51e1423d35 100644 --- a/test/reply-error.test.js +++ b/test/reply-error.test.js @@ -6,6 +6,8 @@ const net = require('net') const Fastify = require('..') const statusCodes = require('http').STATUS_CODES const split = require('split2') +const fs = require('fs') +const path = require('path') const codes = Object.keys(statusCodes) codes.forEach(code => { @@ -596,3 +598,29 @@ test('setting content-type on reply object should not hang the server case 3', t t.equal(res.statusCode, 200) }) }) + +test('pipe stream inside error handler should not cause error', t => { + t.plan(3) + const location = path.join(__dirname, '..', 'package.json') + const json = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString('utf8')) + + const fastify = Fastify() + + fastify.setErrorHandler((_error, _request, reply) => { + const stream = fs.createReadStream(location) + reply.code(400).type('application/json; charset=utf-8').send(stream) + }) + + fastify.get('/', (request, reply) => { + throw new Error('This is an error.') + }) + + fastify.inject({ + url: '/', + method: 'GET' + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 400) + t.same(JSON.parse(res.payload), json) + }) +}) From 21d36448d83cddb208c7e6e2f8c121f32eff974d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Krysi=C5=84ski?= Date: Sat, 16 Oct 2021 09:43:34 +0200 Subject: [PATCH 10/46] fix(typescript): types for schemaController (#3369) --- fastify.d.ts | 13 +++++++++++++ test/types/fastify.test-d.ts | 15 +++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/fastify.d.ts b/fastify.d.ts index 9052909519..ef3ec5e168 100644 --- a/fastify.d.ts +++ b/fastify.d.ts @@ -15,6 +15,8 @@ import { FastifySchemaValidationError } from './types/schema' import { ConstructorAction, ProtoAction } from "./types/content-type-parser"; import { Socket } from 'net' import { Options as FJSOptions } from 'fast-json-stringify' +import { ValidatorCompiler } from '@fastify/ajv-compiler' +import { FastifySerializerCompiler } from './types/schema'; /** * Fastify factory function for the standard fastify http, https, or http2 server instance. @@ -126,6 +128,17 @@ export type FastifyServerOptions< constraints?: { [name: string]: ConstraintStrategy, unknown>, }, + schemaController?: { + bucket?: (parentSchemas?: unknown) => { + addSchema(schema: unknown): FastifyInstance; + getSchema(schemaId: string): unknown; + getSchemas(): Record; + }; + compilersFactory?: { + buildValidator?: ValidatorCompiler; + buildSerializer?: (externalSchemas: unknown, serializerOptsServerOption: FastifyServerOptions["serializerOpts"]) => FastifySerializerCompiler; + }; + }; return503OnClosing?: boolean, ajv?: { customOptions?: AjvOptions, diff --git a/test/types/fastify.test-d.ts b/test/types/fastify.test-d.ts index 5b02672b09..e5923f0624 100644 --- a/test/types/fastify.test-d.ts +++ b/test/types/fastify.test-d.ts @@ -26,9 +26,24 @@ expectType & PromiseLike>>(fastify({ http2: true, http2SessionTimeout: 1000 })) expectType & PromiseLike>>(fastify({ http2: true, https: {}, http2SessionTimeout: 1000 })) expectType(fastify({ http2: true, https: {} }).inject()) +expectType & PromiseLike>>(fastify({ schemaController: {} })) +expectType & PromiseLike>>( + fastify({ + schemaController: { + compilersFactory: {} + } + }) +) expectError(fastify({ http2: false })) // http2 option must be true expectError(fastify({ http2: false })) // http2 option must be true +expectError( + fastify({ + schemaController: { + bucket: () => ({}) // cannot be empty + } + }) +) // light-my-request expectAssignable({ query: '' }) From f724efcd3d0aac7c91c45b76bd02379dc3e8007a Mon Sep 17 00:00:00 2001 From: laohan <120936337@qq.com> Date: Mon, 18 Oct 2021 16:43:57 +0800 Subject: [PATCH 11/46] fix: spell error (#3379) --- fastify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastify.js b/fastify.js index e80e8ce8c2..222298ee03 100644 --- a/fastify.js +++ b/fastify.js @@ -421,7 +421,7 @@ function fastify (options) { // If the server is not ready yet, this // utility will automatically force it. function inject (opts, cb) { - // lightMyRequest is dynamically laoded as it seems very expensive + // lightMyRequest is dynamically loaded as it seems very expensive // because of Ajv if (lightMyRequest === undefined) { lightMyRequest = require('light-my-request') From 4290f2843c38ec823d1592018f0def783e2a88ad Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 19 Oct 2021 17:29:40 +0200 Subject: [PATCH 12/46] Bumped v3.22.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce6c5d9d6c..b854c5156f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "3.22.0", + "version": "3.22.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 505780bbfb0aa004537af4bf70d6ee5bdb228dc1 Mon Sep 17 00:00:00 2001 From: Amis Shokoohi Date: Wed, 20 Oct 2021 10:33:44 +0330 Subject: [PATCH 13/46] Updated the docs related to AJV plugins (#3189) --- docs/Server.md | 2 +- docs/Validation-and-Serialization.md | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/Server.md b/docs/Server.md index d00312f72c..b2504e5e3b 100644 --- a/docs/Server.md +++ b/docs/Server.md @@ -480,7 +480,7 @@ Configure the Ajv v6 instance used by Fastify without providing a custom one. const fastify = require('fastify')({ ajv: { customOptions: { - nullable: false // Refer to [ajv options](https://ajv.js.org/#options) + nullable: false // Refer to [ajv options](https://github.com/ajv-validator/ajv/tree/v6#options) }, plugins: [ require('ajv-merge-patch'), diff --git a/docs/Validation-and-Serialization.md b/docs/Validation-and-Serialization.md index 8a16cdc8fc..a18b053215 100644 --- a/docs/Validation-and-Serialization.md +++ b/docs/Validation-and-Serialization.md @@ -642,6 +642,7 @@ fastify.setErrorHandler(function (error, request, reply) { ``` If you want custom error response in schema without headaches and quickly, you can take a look at [`ajv-errors`](https://github.com/epoberezkin/ajv-errors). Check out the [example](https://github.com/fastify/example/blob/HEAD/validation-messages/custom-errors-messages.js) usage. +> Make sure to install version 1.0.1 of `ajv-errors`, because later versions of it are not compatible with AJV v6 (the version shipped by Fastify v3). Below is an example showing how to add **custom error messages for each property** of a schema by supplying custom AJV options. Inline comments in the schema below describe how to configure it to show a different error message for each case: @@ -649,7 +650,10 @@ Inline comments in the schema below describe how to configure it to show a diffe ```js const fastify = Fastify({ ajv: { - customOptions: { jsonPointers: true }, + customOptions: { + jsonPointers: true, + allErrors: true // Warning: Enabling this option may lead to this security issue https://www.cvedetails.com/cve/CVE-2020-8192/ + }, plugins: [ require('ajv-errors') ] From 149c03fa0ae62611ec700a6cb514d36d7dff1073 Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Sun, 24 Oct 2021 23:17:06 +0200 Subject: [PATCH 14/46] fix: remove unused types imports (#3392) --- test/types/content-type-parser.test-d.ts | 2 +- test/types/instance.test-d.ts | 2 +- test/types/logger.test-d.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/types/content-type-parser.test-d.ts b/test/types/content-type-parser.test-d.ts index 4e995084e9..a3f388bc2b 100644 --- a/test/types/content-type-parser.test-d.ts +++ b/test/types/content-type-parser.test-d.ts @@ -1,4 +1,4 @@ -import fastify, { FastifyBodyParser, FastifyContentTypeParser } from '../../fastify' +import fastify, { FastifyBodyParser } from '../../fastify' import { expectError, expectType } from 'tsd' import { IncomingMessage } from 'http' import { FastifyRequest } from '../../types/request' diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 0ee0fe0028..6bf1b9f023 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -5,7 +5,7 @@ import fastify, { RawReplyDefaultExpression, RawRequestDefaultExpression } from '../../fastify' -import { expectAssignable, expectError, expectNotAssignable, expectType } from 'tsd' +import { expectAssignable, expectError, expectType } from 'tsd' import { FastifyRequest } from '../../types/request' import { FastifyReply } from '../../types/reply' import { HookHandlerDoneFunction } from '../../types/hooks' diff --git a/test/types/logger.test-d.ts b/test/types/logger.test-d.ts index c2dd5df85c..70a3042943 100644 --- a/test/types/logger.test-d.ts +++ b/test/types/logger.test-d.ts @@ -1,4 +1,4 @@ -import { expectType, expectError } from 'tsd' +import { expectType } from 'tsd' import fastify, { FastifyLogFn, LogLevel, FastifyLoggerInstance, FastifyError, FastifyRequest, FastifyReply } from '../../fastify' import { Server, IncomingMessage, ServerResponse } from 'http' import pino from 'pino' From 705a505ffbbc6d30f937a1fce854e3f9991e8732 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Mon, 25 Oct 2021 12:37:47 +0000 Subject: [PATCH 15/46] docs(test/bundler): grammar and spelling fixes (#3393) * docs(test/bundler): grammar and spelling fixes * Update test/bundler/README.md Co-authored-by: James Sumners * Update test/bundler/README.md Co-authored-by: James Sumners * Update test/bundler/README.md Co-authored-by: James Sumners Co-authored-by: James Sumners --- test/bundler/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/bundler/README.md b/test/bundler/README.md index 1602469874..e4040bfb8b 100644 --- a/test/bundler/README.md +++ b/test/bundler/README.md @@ -1,16 +1,16 @@ # Bundlers test stack -In some cases developers bundle their apps for several targets, eg: serveless applications. +In some cases, developers bundle their apps for several targets such as serverless applications. Even if it's not recommended by Fastify team; we need to ensure we do not break the build process. -Please note this might result in feature behaving differently like the version handling check for plugins. +Please note this might result in features behaving differently, like the version handling check for plugins. ## Test bundlers -The bundler test stack has been set appart than the rest of the Unit testing stack because it's not a +The bundler test stack has been defined separately from the rest of the Unit testing stack because it's not a part of the fastify lib itself. Note that the tests run in CI only on NodeJs LTS version. -Developers does not need to install every bundler to run unit tests. +Developers do not need to install every bundler to run unit tests. -To run the bundler tests you'll need to first install the repository dependencies and after the bundler +To run the bundler tests you will need to install the repository dependencies followed by the bundler stack dependencies. See: ```bash @@ -24,6 +24,6 @@ stack dependencies. See: ## Bundler test development To not break the fastify unit testing stack please name test files like this `*-test.js` and not `*.test.js`, -otherwise it can be catched by unit-test regex of fastify. -Test need to ensure the build process works and the fastify application can be run, +otherwise it will be targeted by the regular expression used for unit tests for fastify. +Tests need to ensure the build process works and the fastify application can be run, no need to go in deep testing unless an issue is raised. From c9a788858ecf2d4a8e786cc1a50e241bb084f308 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Oct 2021 15:17:45 +0200 Subject: [PATCH 16/46] build(deps-dev): bump split2 from 3.2.2 to 4.1.0 (#3395) Bumps [split2](https://github.com/mcollina/split2) from 3.2.2 to 4.1.0. - [Release notes](https://github.com/mcollina/split2/releases) - [Commits](https://github.com/mcollina/split2/compare/v3.2.2...v4.1.0) --- updated-dependencies: - dependency-name: split2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b854c5156f..1ffd4e34ac 100644 --- a/package.json +++ b/package.json @@ -160,7 +160,7 @@ "serve-static": "^1.14.1", "simple-get": "^4.0.0", "snazzy": "^9.0.0", - "split2": "^3.1.1", + "split2": "^4.1.0", "standard": "^16.0.1", "tap": "^15.0.5", "tap-mocha-reporter": "^5.0.1", From eed0121f35eafc99deccded945cec1c10f8954c0 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Thu, 28 Oct 2021 22:28:21 +0200 Subject: [PATCH 17/46] Update Validation-and-Serialization.md (#3400) --- docs/Validation-and-Serialization.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Validation-and-Serialization.md b/docs/Validation-and-Serialization.md index a18b053215..b630993730 100644 --- a/docs/Validation-and-Serialization.md +++ b/docs/Validation-and-Serialization.md @@ -10,6 +10,10 @@ Fastify uses a schema-based approach, and even if it is not mandatory we recomme > user-provided schemas. See [Ajv](https://npm.im/ajv) and > [fast-json-stringify](https://npm.im/fast-json-stringify) for more > details. +> +> Moreover, the [`$async` Ajv feature](https://ajv.js.org/guide/async-validation.html) should not be used as part of the first validation strategy. +> This option is used to access Databases and reading them during the validation process may lead to Denial of Service Attacks to your +> application. If you need to run `async` tasks, use [Fastify's hooks](./Hooks.md) instead after validation completes, such as `preHandler`. ### Core concepts From 9e4b7cc12c4beff53d08d6512d6347920a1f9842 Mon Sep 17 00:00:00 2001 From: Artur K Date: Sun, 31 Oct 2021 20:52:12 +0200 Subject: [PATCH 18/46] Add requestTimeout option (#3407) --- 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 + test/requestTimeout.test.js | 50 ++ 8 files changed, 525 insertions(+), 423 deletions(-) create mode 100644 test/requestTimeout.test.js 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, 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) +}) From d72dfa07de3b2308f3ee3ea176b954ce6f49f031 Mon Sep 17 00:00:00 2001 From: Davide Fiorello Date: Thu, 4 Nov 2021 17:29:16 +0100 Subject: [PATCH 19/46] fix: verify request socket before access attributes (#3491) (#3420) --- lib/request.js | 12 ++++++--- test/internals/request.test.js | 47 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/lib/request.js b/lib/request.js index f6c94e2db8..9709a23cf9 100644 --- a/lib/request.js +++ b/lib/request.js @@ -102,7 +102,9 @@ function buildRequestWithTrustProxy (R, trustProxy) { if (this.headers['x-forwarded-proto']) { return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-proto']) } - return this.socket.encrypted ? 'https' : 'http' + if (this.socket) { + return this.socket.encrypted ? 'https' : 'http' + } } } }) @@ -158,7 +160,9 @@ Object.defineProperties(Request.prototype, { }, ip: { get () { - return this.socket.remoteAddress + if (this.socket) { + return this.socket.remoteAddress + } } }, hostname: { @@ -168,7 +172,9 @@ Object.defineProperties(Request.prototype, { }, protocol: { get () { - return this.socket.encrypted ? 'https' : 'http' + if (this.socket) { + return this.socket.encrypted ? 'https' : 'http' + } } }, headers: { diff --git a/test/internals/request.test.js b/test/internals/request.test.js index 644c044248..e7f3d01dce 100644 --- a/test/internals/request.test.js +++ b/test/internals/request.test.js @@ -213,3 +213,50 @@ test('Request with trust proxy - plain', t => { const request = new TpRequest('id', 'params', req, 'query', 'log') t.same(request.protocol, 'http') }) + +test('Request with undefined socket', t => { + t.plan(15) + const headers = { + host: 'hostname' + } + const req = { + method: 'GET', + url: '/', + socket: undefined, + headers + } + const request = new Request('id', 'params', req, 'query', 'log') + t.type(request, Request) + t.equal(request.id, 'id') + t.equal(request.params, 'params') + t.same(request.raw, req) + t.equal(request.query, 'query') + t.equal(request.headers, headers) + t.equal(request.log, 'log') + t.equal(request.ip, undefined) + t.equal(request.ips, undefined) + t.equal(request.hostname, 'hostname') + t.equal(request.body, null) + t.equal(request.method, 'GET') + t.equal(request.url, '/') + t.equal(request.protocol, undefined) + t.same(request.socket, req.socket) +}) + +test('Request with trust proxy and undefined socket', t => { + t.plan(1) + const headers = { + 'x-forwarded-for': '2.2.2.2, 1.1.1.1', + 'x-forwarded-host': 'example.com' + } + const req = { + method: 'GET', + url: '/', + socket: undefined, + headers + } + + const TpRequest = Request.buildRequest(Request, true) + const request = new TpRequest('id', 'params', req, 'query', 'log') + t.same(request.protocol, undefined) +}) From 6a0403319148163fe6f826aba16e33e46fdb88d4 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 4 Nov 2021 17:40:36 +0100 Subject: [PATCH 20/46] Bumped v3.23.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1ffd4e34ac..4df5a50aa0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "3.22.1", + "version": "3.23.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 53fe09f6664776330054a968237f04701ecdc6b7 Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Sat, 6 Nov 2021 17:50:55 +0100 Subject: [PATCH 21/46] fix: setSchemaController not inheriting Schemas from root parent (#3401) * fix: setSchema not inheriting Schemas from root * refactor: add comment to test Co-authored-by: Manuel Spigolon * refactor: use `or` instead of ternary operator Co-authored-by: Manuel Spigolon * feat: allow recursive search of compilersFactory * test: adjust tests * refactor: make granular recursive calls * test: add testing for recursive calls * fix: typo Co-authored-by: Vincent LE GOFF * refactor: test/schema-feature.test.js Co-authored-by: Manuel Spigolon * refactor: test/schema-feature.test.js Co-authored-by: Manuel Spigolon * test: remove unnecessary comments Co-authored-by: Manuel Spigolon Co-authored-by: Vincent LE GOFF --- fastify.js | 3 +- lib/schema-controller.js | 13 +- test/schema-feature.test.js | 315 ++++++++++++++++++++++++++++++++++++ 3 files changed, 328 insertions(+), 3 deletions(-) diff --git a/fastify.js b/fastify.js index 679bb65516..9e67f36a72 100644 --- a/fastify.js +++ b/fastify.js @@ -636,7 +636,8 @@ function fastify (options) { function setSchemaController (schemaControllerOpts) { throwIfAlreadyStarted('Cannot call "setSchemaController" when fastify instance is already started!') const old = this[kSchemaController] - const schemaController = SchemaController.buildSchemaController(old.parent, Object.assign({}, old.opts, schemaControllerOpts)) + const parent = old.parent || old + const schemaController = SchemaController.buildSchemaController(parent, Object.assign({}, old.opts, schemaControllerOpts)) this[kSchemaController] = schemaController this.getSchema = schemaController.getSchema.bind(schemaController) this.getSchemas = schemaController.getSchemas.bind(schemaController) diff --git a/lib/schema-controller.js b/lib/schema-controller.js index 7f682ce2e3..34821486b1 100644 --- a/lib/schema-controller.js +++ b/lib/schema-controller.js @@ -79,6 +79,14 @@ class SchemaController { return this.serializerCompiler || (this.parent && this.parent.getSerializerCompiler()) } + getSerializerBuilder () { + return this.compilersFactory.buildSerializer || (this.parent && this.parent.getSerializerBuilder()) + } + + getValidatorBuilder () { + return this.compilersFactory.buildValidator || (this.parent && this.parent.getValidatorBuilder()) + } + /** * This method will be called when a validator must be setup. * Do not setup the compiler more than once @@ -89,7 +97,7 @@ class SchemaController { if (isReady) { return } - this.validatorCompiler = this.compilersFactory.buildValidator(this.schemaBucket.getSchemas(), serverOption.ajv) + this.validatorCompiler = this.getValidatorBuilder()(this.schemaBucket.getSchemas(), serverOption.ajv) } /** @@ -102,7 +110,8 @@ class SchemaController { if (isReady) { return } - this.serializerCompiler = this.compilersFactory.buildSerializer(this.schemaBucket.getSchemas(), serverOption.serializerOpts) + + this.serializerCompiler = this.getSerializerBuilder()(this.schemaBucket.getSchemas(), serverOption.serializerOpts) } } diff --git a/test/schema-feature.test.js b/test/schema-feature.test.js index b36b25b1e6..46ce98ac83 100644 --- a/test/schema-feature.test.js +++ b/test/schema-feature.test.js @@ -3,6 +3,7 @@ const { test } = require('tap') const Fastify = require('..') const fp = require('fastify-plugin') +const Ajv = require('ajv') const { kSchemaController } = require('../lib/symbols.js') const echoParams = (req, reply) => { reply.send(req.params) } @@ -1289,3 +1290,317 @@ test('setSchemaController per instance', t => { fastify.ready(err => { t.error(err) }) }) + +test('setSchemaController: Inherits correctly parent schemas with a customized validator instance', async t => { + t.plan(5) + const customAjv = new Ajv({ coerceTypes: false }) + const server = Fastify() + const someSchema = { + $id: 'some', + type: 'array', + items: { + type: 'string' + } + } + const errorResponseSchema = { + $id: 'error_response', + type: 'object', + properties: { + statusCode: { + type: 'integer' + }, + message: { + type: 'string' + } + } + } + + server.addSchema(someSchema) + server.addSchema(errorResponseSchema) + + server.register((instance, _, done) => { + instance.setSchemaController({ + compilersFactory: { + buildValidator: function (externalSchemas) { + const schemaKeys = Object.keys(externalSchemas) + t.equal(schemaKeys.length, 2, 'Contains same number of schemas') + t.hasStrict([someSchema, errorResponseSchema], Object.values(externalSchemas), 'Contains expected schemas') + for (const key of schemaKeys) { + if (customAjv.getSchema(key) == null) { + customAjv.addSchema(externalSchemas[key], key) + } + } + return function validatorCompiler ({ schema }) { + return customAjv.compile(schema) + } + } + } + }) + + instance.get( + '/', + { + schema: { + querystring: { + msg: { + $ref: 'some#' + } + }, + response: { + '4xx': { + $ref: 'error_response#' + } + } + } + }, + (req, reply) => { + reply.send({ noop: 'noop' }) + } + ) + + done() + }) + + const res = await server.inject({ + method: 'GET', + url: '/', + query: { + msg: 'string' + } + }) + const json = res.json() + + t.equal(json.message, 'querystring.msg should be array') + t.equal(json.statusCode, 400) + t.equal(res.statusCode, 400, 'Should not coearce the string into array') +}) + +test('setSchemaController: Inherits buildSerializer from parent if not present within the instance', async t => { + t.plan(6) + const customAjv = new Ajv({ coerceTypes: false }) + const someSchema = { + $id: 'some', + type: 'array', + items: { + type: 'string' + } + } + const errorResponseSchema = { + $id: 'error_response', + type: 'object', + properties: { + statusCode: { + type: 'integer' + }, + message: { + type: 'string' + } + } + } + let rootSerializerCalled = 0 + let rootValidatorCalled = 0 + let childValidatorCalled = 0 + const rootBuildSerializer = function (externalSchemas) { + rootSerializerCalled++ + return function serializer () { + return data => { + return JSON.stringify({ + statusCode: data.statusCode, + message: data.message + }) + } + } + } + const rootBuildValidator = function (externalSchemas) { + rootValidatorCalled++ + return function validatorCompiler ({ schema }) { + return customAjv.compile(schema) + } + } + const server = Fastify({ + schemaController: { + compilersFactory: { + buildValidator: rootBuildValidator, + buildSerializer: rootBuildSerializer + } + } + }) + + server.addSchema(someSchema) + server.addSchema(errorResponseSchema) + + server.register((instance, _, done) => { + instance.setSchemaController({ + compilersFactory: { + buildValidator: function (externalSchemas) { + childValidatorCalled++ + const schemaKeys = Object.keys(externalSchemas) + for (const key of schemaKeys) { + if (customAjv.getSchema(key) == null) { + customAjv.addSchema(externalSchemas[key], key) + } + } + return function validatorCompiler ({ schema }) { + return customAjv.compile(schema) + } + } + } + }) + + instance.get( + '/', + { + schema: { + querystring: { + msg: { + $ref: 'some#' + } + }, + response: { + '4xx': { + $ref: 'error_response#' + } + } + } + }, + (req, reply) => { + reply.send({ noop: 'noop' }) + } + ) + + done() + }) + + const res = await server.inject({ + method: 'GET', + url: '/', + query: { + msg: 'string' + } + }) + const json = res.json() + + t.equal(json.statusCode, 400) + t.equal(json.message, 'querystring.msg should be array') + t.equal(rootSerializerCalled, 1, 'Should be called from the child') + t.equal(rootValidatorCalled, 0, 'Should not be called from the child') + t.equal(childValidatorCalled, 1, 'Should be called from the child') + t.equal(res.statusCode, 400, 'Should not coerce the string into array') +}) + +test('setSchemaController: Inherits buildValidator from parent if not present within the instance', async t => { + t.plan(6) + const customAjv = new Ajv({ coerceTypes: false }) + const someSchema = { + $id: 'some', + type: 'array', + items: { + type: 'string' + } + } + const errorResponseSchema = { + $id: 'error_response', + type: 'object', + properties: { + statusCode: { + type: 'integer' + }, + message: { + type: 'string' + } + } + } + let rootSerializerCalled = 0 + let rootValidatorCalled = 0 + let childSerializerCalled = 0 + const rootBuildSerializer = function (externalSchemas) { + rootSerializerCalled++ + return function serializer () { + return data => JSON.stringify(data) + } + } + const rootBuildValidator = function (externalSchemas) { + rootValidatorCalled++ + const schemaKeys = Object.keys(externalSchemas) + for (const key of schemaKeys) { + if (customAjv.getSchema(key) == null) { + customAjv.addSchema(externalSchemas[key], key) + } + } + return function validatorCompiler ({ schema }) { + return customAjv.compile(schema) + } + } + const server = Fastify({ + schemaController: { + compilersFactory: { + buildValidator: rootBuildValidator, + buildSerializer: rootBuildSerializer + } + } + }) + + server.register((instance, _, done) => { + instance.register((subInstance, _, subDone) => { + subInstance.setSchemaController({ + compilersFactory: { + buildSerializer: function (externalSchemas) { + childSerializerCalled++ + return function serializerCompiler () { + return data => { + return JSON.stringify({ + statusCode: data.statusCode, + message: data.message + }) + } + } + } + } + }) + + subInstance.get( + '/', + { + schema: { + querystring: { + msg: { + $ref: 'some#' + } + }, + response: { + '4xx': { + $ref: 'error_response#' + } + } + } + }, + (req, reply) => { + reply.send({ noop: 'noop' }) + } + ) + + subDone() + }) + + done() + }) + + server.addSchema(someSchema) + server.addSchema(errorResponseSchema) + + const res = await server.inject({ + method: 'GET', + url: '/', + query: { + msg: ['string'] + } + }) + const json = res.json() + + t.equal(json.statusCode, 400) + t.equal(json.message, 'querystring.msg should be array') + t.equal(rootSerializerCalled, 0, 'Should be called from the child') + t.equal(rootValidatorCalled, 1, 'Should not be called from the child') + t.equal(childSerializerCalled, 1, 'Should be called from the child') + t.equal(res.statusCode, 400, 'Should not coearce the string into array') +}) From 692fe4f8e083c2509de67c1813a52b18f62df4d9 Mon Sep 17 00:00:00 2001 From: Davide Fiorello Date: Sun, 7 Nov 2021 18:24:22 +0100 Subject: [PATCH 22/46] fix: socket null in logger (#3422) --- lib/logger.js | 2 +- test/internals/logger.test.js | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/logger.js b/lib/logger.js index 592bb0f433..59df861bc7 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -53,7 +53,7 @@ const serializers = { version: req.headers['accept-version'], hostname: req.hostname, remoteAddress: req.ip, - remotePort: req.socket.remotePort + remotePort: req.socket ? req.socket.remotePort : undefined } }, err: pino.stdSerializers.err, diff --git a/test/internals/logger.test.js b/test/internals/logger.test.js index 3b3da210e9..5688b97796 100644 --- a/test/internals/logger.test.js +++ b/test/internals/logger.test.js @@ -112,3 +112,23 @@ test('The logger should error if both stream and file destination are given', t t.equal(err.message, 'Cannot specify both logger.stream and logger.file options') } }) + +test('The serializer prevent fails if the request socket is undefined', t => { + t.plan(1) + + const serialized = loggerUtils.serializers.req({ + method: 'GET', + url: '/', + socket: undefined, + headers: {} + }) + + t.same(serialized, { + method: 'GET', + url: '/', + version: undefined, + hostname: undefined, + remoteAddress: undefined, + remotePort: undefined + }) +}) From bc19a34ab9e71fec731ecb41db15ef5faee1c88c Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 7 Nov 2021 19:25:55 +0100 Subject: [PATCH 23/46] Bumped v3.23.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4df5a50aa0..57c9681654 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "3.23.0", + "version": "3.23.1", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From 166b9fdab6183d80b7579baf410091b53a19c5d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Nov 2021 10:49:48 +0100 Subject: [PATCH 24/46] build(deps-dev): bump @sinonjs/fake-timers from 7.1.2 to 8.1.0 (#3418) Bumps [@sinonjs/fake-timers](https://github.com/sinonjs/fake-timers) from 7.1.2 to 8.1.0. - [Release notes](https://github.com/sinonjs/fake-timers/releases) - [Changelog](https://github.com/sinonjs/fake-timers/blob/master/CHANGELOG.md) - [Commits](https://github.com/sinonjs/fake-timers/compare/v7.1.2...v8.1.0) --- updated-dependencies: - dependency-name: "@sinonjs/fake-timers" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 57c9681654..5e418c76d6 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "@fastify/ajv-compiler-8": "npm:@fastify/ajv-compiler@^2.0.0", "@fastify/pre-commit": "^2.0.1", "@hapi/joi": "^17.1.1", - "@sinonjs/fake-timers": "^7.0.0", + "@sinonjs/fake-timers": "^8.1.0", "@types/node": "^16.0.0", "@types/pino": "^6.0.1", "@typescript-eslint/eslint-plugin": "^4.5.0", From 1e94070992d911a81a26597c25f2d35ae65f3d91 Mon Sep 17 00:00:00 2001 From: Jesse Chan Date: Wed, 10 Nov 2021 08:28:45 -0800 Subject: [PATCH 25/46] feat: make version field deterministic and reliable (#3427) * feat: make version field deterministic and reliable This change makes the "version" a reliable field, instead of one that might be "undefined". Some issues with bundlers such as "a wild package.json appears" are resolved as well. Maintainers would have to ensure that versions in fastify.js and package.json are in sync. That would be relatively trivial, compared to the benefits, when it comes to such an essential package. To avoid errors and reduce maintenance burden, a test has been added to ensure that the versions are in sync. It is configured to run before publish. Refs: #3110, #3113, #3155, #3185, #3191 Signed-off-by: Jesse Chan * test: bundler: drop no longer relevant version fallback test Signed-off-by: Jesse Chan * build: add a script to sync versions Signed-off-by: Jesse Chan * test: bundler: restore version test but invert assertion Signed-off-by: Jesse Chan --- build/sync-version.js | 11 ++++++++ fastify.js | 25 +++-------------- lib/pluginUtils.js | 4 +-- package.json | 1 + test/bundler/webpack/bundler-test.js | 8 ++---- test/internals/version.test.js | 40 +++++----------------------- types/instance.d.ts | 2 +- 7 files changed, 25 insertions(+), 66 deletions(-) create mode 100644 build/sync-version.js diff --git a/build/sync-version.js b/build/sync-version.js new file mode 100644 index 0000000000..5d00f216ed --- /dev/null +++ b/build/sync-version.js @@ -0,0 +1,11 @@ +'use strict' + +const fs = require('fs') +const path = require('path') + +// package.json:version -> fastify.js:VERSION +const { version } = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString('utf8')) + +const fastifyJs = path.join(__dirname, '..', 'fastify.js') + +fs.writeFileSync(fastifyJs, fs.readFileSync(fastifyJs).toString('utf8').replace(/const\s*VERSION\s*=.*/, `const VERSION = '${version}'`)) diff --git a/fastify.js b/fastify.js index 9e67f36a72..1c8cdaf956 100644 --- a/fastify.js +++ b/fastify.js @@ -1,11 +1,11 @@ 'use strict' +const VERSION = '3.23.1' + const Avvio = require('avvio') const http = require('http') const querystring = require('querystring') let lightMyRequest -let version -let versionLoaded = false const { kAvvioBoot, @@ -326,12 +326,7 @@ function fastify (options) { get () { return this[kSchemaController].getSerializerCompiler() } }, version: { - get () { - if (versionLoaded === false) { - version = loadVersion() - } - return version - } + get () { return VERSION } }, errorHandler: { get () { @@ -693,20 +688,6 @@ function wrapRouting (httpHandler, { rewriteUrl, logger }) { } } -function loadVersion () { - versionLoaded = true - const fs = require('fs') - const path = require('path') - try { - const pkgPath = path.join(__dirname, 'package.json') - fs.accessSync(pkgPath, fs.constants.R_OK) - const pkg = JSON.parse(fs.readFileSync(pkgPath)) - return pkg.name === 'fastify' ? pkg.version : undefined - } catch (e) { - return undefined - } -} - /** * These export configurations enable JS and TS developers * to consumer fastify in whatever way best suits their needs. diff --git a/lib/pluginUtils.js b/lib/pluginUtils.js index b566b5bb1d..b613ac0725 100644 --- a/lib/pluginUtils.js +++ b/lib/pluginUtils.js @@ -113,9 +113,7 @@ function registerPluginName (fn) { function registerPlugin (fn) { registerPluginName.call(this, fn) - if (this.version !== undefined) { - checkVersion.call(this, fn) - } + checkVersion.call(this, fn) checkDecorators.call(this, fn) checkDependencies.call(this, fn) return shouldSkipOverride(fn) diff --git a/package.json b/package.json index 5e418c76d6..1fa48329f4 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "lint:fix": "standard --fix", "lint:standard": "standard --verbose | snazzy", "lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts", + "prepublishOnly": "tap --no-check-coverage test/internals/version.test.js", "test": "npm run lint && npm run unit && npm run test:typescript", "test:ci": "npm run lint && npm run unit -- --cov --coverage-report=lcovonly && npm run test:typescript", "test:report": "npm run lint && npm run unit:report && npm run test:typescript", diff --git a/test/bundler/webpack/bundler-test.js b/test/bundler/webpack/bundler-test.js index a945917e93..b3145a6163 100644 --- a/test/bundler/webpack/bundler-test.js +++ b/test/bundler/webpack/bundler-test.js @@ -12,13 +12,9 @@ test('Bundled package should work', t => { }) }) -// In the webpack bundle context the fastify package.json is not read -// Because of this the version is set to `undefined`, this makes the plugin -// version check not able to work properly. By then this test shouldn't work -// in non-bundled environment but works in bundled environment -test('Bundled package should work with bad plugin version, undefined version fallback', t => { +test('Bundled package should not work with bad plugin version', t => { t.plan(1) fastifyFailPlugin.ready((err) => { - t.error(err) + t.ok(err) }) }) diff --git a/test/internals/version.test.js b/test/internals/version.test.js index dfc73a2753..46730e7541 100644 --- a/test/internals/version.test.js +++ b/test/internals/version.test.js @@ -1,43 +1,15 @@ 'use strict' +const fs = require('fs') +const path = require('path') const t = require('tap') const test = t.test -const proxyquire = require('proxyquire') +const fastify = require('../..')() -test('should output an undefined version in case of package.json not available', t => { - const Fastify = proxyquire('../..', { fs: { accessSync: () => { throw Error('error') } } }) +test('should be the same as package.json', t => { t.plan(1) - const srv = Fastify() - t.equal(srv.version, undefined) -}) - -test('should output an undefined version in case of package.json is not the fastify one', t => { - const Fastify = proxyquire('../..', { fs: { accessSync: () => { }, readFileSync: () => JSON.stringify({ name: 'foo', version: '6.6.6' }) } }) - t.plan(1) - const srv = Fastify() - t.equal(srv.version, undefined) -}) - -test('should skip the version check if the version is undefined', t => { - const Fastify = proxyquire('../..', { fs: { accessSync: () => { }, readFileSync: () => JSON.stringify({ name: 'foo', version: '6.6.6' }) } }) - t.plan(3) - const srv = Fastify() - t.equal(srv.version, undefined) - - plugin[Symbol.for('skip-override')] = false - plugin[Symbol.for('plugin-meta')] = { - name: 'plugin', - fastify: '>=99.0.0' - } - - srv.register(plugin) - srv.ready((err) => { - t.error(err) - t.pass('everything right') - }) + const json = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'package.json')).toString('utf8')) - function plugin (instance, opts, done) { - done() - } + t.equal(fastify.version, json.version) }) diff --git a/types/instance.d.ts b/types/instance.d.ts index 1dcba005c5..4f184d820a 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -27,7 +27,7 @@ export interface FastifyInstance< > { server: RawServer; prefix: string; - version: string | undefined; + version: string; log: Logger; addSchema(schema: unknown): FastifyInstance; From 9a6dd4de685d1a55e63e731408ad1087da252250 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Fri, 12 Nov 2021 13:05:37 +0000 Subject: [PATCH 26/46] chore(.npmignore): remove default ignored files/paths (#3434) --- .npmignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.npmignore b/.npmignore index 6ca338ae96..33caf9bf83 100644 --- a/.npmignore +++ b/.npmignore @@ -1,7 +1,5 @@ .editorconfig .gitattributes -.git -.DS_Store .gitignore .github .nyc_output @@ -9,7 +7,6 @@ coverage/ tools/ CODE_OF_CONDUCT.md CONTRIBUTING.md -.dependabot .clinic # test certification From acc8c22d0099b11315df71c22e4bea08bda9177c Mon Sep 17 00:00:00 2001 From: Thomas Schaaf Date: Fri, 12 Nov 2021 22:08:28 +0100 Subject: [PATCH 27/46] make getResponseTime deterministic (#3431) --- lib/reply.js | 5 ++++- lib/symbols.js | 1 + test/internals/reply.test.js | 42 ++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/reply.js b/lib/reply.js index b0ea2cfc87..50f3e81435 100644 --- a/lib/reply.js +++ b/lib/reply.js @@ -11,6 +11,7 @@ const { kReplySent, kReplySentOverwritten, kReplyStartTime, + kReplyEndTime, kReplySerializer, kReplySerializerDefault, kReplyIsError, @@ -312,7 +313,7 @@ Reply.prototype.getResponseTime = function () { let responseTime = 0 if (this[kReplyStartTime] !== undefined) { - responseTime = now() - this[kReplyStartTime] + responseTime = (this[kReplyEndTime] || now()) - this[kReplyStartTime] } return responseTime @@ -612,6 +613,7 @@ function setupResponseListeners (reply) { reply[kReplyStartTime] = now() const onResFinished = err => { + reply[kReplyEndTime] = now() reply.raw.removeListener('finish', onResFinished) reply.raw.removeListener('error', onResFinished) @@ -673,6 +675,7 @@ function buildReply (R) { this.request = request this[kReplyHeaders] = {} this[kReplyStartTime] = undefined + this[kReplyEndTime] = undefined this.log = log // eslint-disable-next-line no-var diff --git a/lib/symbols.js b/lib/symbols.js index bdd861be55..f5dd03ff3f 100644 --- a/lib/symbols.js +++ b/lib/symbols.js @@ -33,6 +33,7 @@ const keys = { kReplySent: Symbol('fastify.reply.sent'), kReplySentOverwritten: Symbol('fastify.reply.sentOverwritten'), kReplyStartTime: Symbol('fastify.reply.startTime'), + kReplyEndTime: Symbol('fastify.reply.endTime'), kReplyErrorHandlerCalled: Symbol('fastify.reply.errorHandlerCalled'), kReplyIsRunningOnErrorHook: Symbol('fastify.reply.isRunningOnErrorHook'), kSchemaVisited: Symbol('fastify.schemas.visited'), diff --git a/test/internals/reply.test.js b/test/internals/reply.test.js index 117b86ec06..4545eda1db 100644 --- a/test/internals/reply.test.js +++ b/test/internals/reply.test.js @@ -1368,6 +1368,48 @@ test('reply.getResponseTime() should return a number greater than 0 after the ti fastify.inject({ method: 'GET', url: '/' }) }) +test('reply.getResponseTime() should return the time since a request started while inflight', t => { + t.plan(1) + const fastify = require('../..')() + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send('hello world') + } + }) + + fastify.addHook('preValidation', (req, reply, done) => { + t.not(reply.getResponseTime(), reply.getResponseTime()) + done() + }) + + fastify.addHook('onResponse', (req, reply) => { + t.end() + }) + + fastify.inject({ method: 'GET', url: '/' }) +}) + +test('reply.getResponseTime() should return the same value after a request is finished', t => { + t.plan(1) + const fastify = require('../..')() + fastify.route({ + method: 'GET', + url: '/', + handler: (req, reply) => { + reply.send('hello world') + } + }) + + fastify.addHook('onResponse', (req, reply) => { + t.equal(reply.getResponseTime(), reply.getResponseTime()) + t.end() + }) + + fastify.inject({ method: 'GET', url: '/' }) +}) + test('reply should use the custom serializer', t => { t.plan(4) const fastify = require('../..')() From cdc3c690ab7bbecdf5b18bae68f9684deb982641 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Sat, 13 Nov 2021 07:23:42 +0000 Subject: [PATCH 28/46] docs: clarify on `request.body` content and usage (#3436) --- docs/Getting-Started.md | 14 ++++++++++++++ docs/Request.md | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index 5cf43ec090..84d2a5e9a5 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -363,6 +363,20 @@ fastify.get('/', opts, async (request, reply) => { By specifying a schema as shown, you can speed up serialization by a factor of 2-3. This also helps to protect against leakage of potentially sensitive data, since Fastify will serialize only the data present in the response schema. Read [Validation and Serialization](Validation-and-Serialization.md) to learn more. + +### Parsing request payloads +Fastify parses `'application/json'` and `'text/plain'` request payloads natively, with the result accessible from the [Fastify request](Request.md) object at `request.body`.
+The following example returns the parsed body of a request back to the client: + +```js +const opts = {} +fastify.post('/', opts, async (request, reply) => { + return request.body +}) +``` + +Read [Content Type Parser](ContentTypeParser.md) to learn more about Fastify's default parsing functionality and how to support other content types. + ### Extend your server Fastify is built to be extremely extensible and minimal, we believe that a bare-bones framework is all that is necessary to make great applications possible.
diff --git a/docs/Request.md b/docs/Request.md index bb03aeca30..50fc2dd750 100644 --- a/docs/Request.md +++ b/docs/Request.md @@ -4,7 +4,7 @@ The first parameter of the handler function is `Request`.
Request is a core Fastify object containing the following fields: - `query` - the parsed querystring, its format is specified by [`querystringParser`](Server.md#querystringparser) -- `body` - the body +- `body` - the request payload, see [Content Type Parser](ContentTypeParser.md) for details on what request payloads Fastify natively parses and how to support other content types - `params` - the params matching the URL - [`headers`](#headers) - the headers getter and setter - `raw` - the incoming HTTP request from Node core From 326c6b48d9e1ccebebeff5a0a42a748100af73e5 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Sat, 13 Nov 2021 13:35:38 +0100 Subject: [PATCH 29/46] test: add hook tests (#3439) * add ontimeout test * add onerror test --- docs/Hooks.md | 2 +- docs/Routes.md | 2 ++ test/route-hooks.test.js | 55 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/docs/Hooks.md b/docs/Hooks.md index eda4d1f74a..b95fb8f909 100644 --- a/docs/Hooks.md +++ b/docs/Hooks.md @@ -222,7 +222,7 @@ fastify.addHook('onTimeout', async (request, reply) => { await asyncMethod() }) ``` -`onTimeout` is useful if you need to monitor the request timed out in your service (if the `connectionTimeout` property is set on the Fastify instance). The `onTimeout` hook is executed when a request is timed out and the HTTP socket has been hanged up. Therefore ,you will not be able to send data to the client. +`onTimeout` is useful if you need to monitor the request timed out in your service (if the `connectionTimeout` property is set on the Fastify instance). The `onTimeout` hook is executed when a request is timed out and the HTTP socket has been hanged up. Therefore, you will not be able to send data to the client. ### Manage Errors from a hook diff --git a/docs/Routes.md b/docs/Routes.md index 2596d9b77d..08a08f5674 100644 --- a/docs/Routes.md +++ b/docs/Routes.md @@ -50,6 +50,8 @@ They need to be in * `preSerialization(request, reply, payload, done)`: a [function](Hooks.md#preserialization) called just before the serialization, it could also be an array of functions. * `onSend(request, reply, payload, done)`: a [function](Hooks.md#route-hooks) called right before a response is sent, it could also be an array of functions. * `onResponse(request, reply, done)`: a [function](Hooks.md#onresponse) called when a response has been sent, so you will not be able to send more data to the client. It could also be an array of functions. +* `onTimeout(request, reply, done)`: a [function](Hooks.md#ontimeout) called when a request is timed out and the HTTP socket has been hanged up. +* `onError(request, reply, error, done)`: a [function](Hooks.md#onerror) called when an Error is thrown or send to the client by the route handler. * `handler(request, reply)`: the function that will handle this request. The [Fastify server](Server.md) will be bound to `this` when the handler is called. Note: using an arrow function will break the binding of `this`. * `errorHandler(error, request, reply)`: a custom error handler for the scope of the request. Overrides the default error global handler, and anything set by [`setErrorHandler`](Server.md#setErrorHandler), for requests to the route. To access the default handler, you can access `instance.errorHandler`. Note that this will point to fastify's default `errorHandler` only if a plugin hasn't overridden it already. * `validatorCompiler({ schema, method, url, httpPart })`: function that builds schemas for request validations. See the [Validation and Serialization](Validation-and-Serialization.md#schema-validator) documentation. diff --git a/test/route-hooks.test.js b/test/route-hooks.test.js index 64233ab76d..294d7cef91 100644 --- a/test/route-hooks.test.js +++ b/test/route-hooks.test.js @@ -2,6 +2,7 @@ const { Readable } = require('stream') const test = require('tap').test +const sget = require('simple-get').concat const Fastify = require('../') process.removeAllListeners('warning') @@ -496,3 +497,57 @@ test('onRequest option should be called before preParsing', t => { t.same(payload, { hello: 'world' }) }) }) + +test('onTimeout on route', t => { + t.plan(4) + const fastify = Fastify({ connectionTimeout: 500 }) + + fastify.get('/timeout', { + async handler (request, reply) { }, + onTimeout (request, reply, done) { + t.pass('onTimeout called') + done() + } + }) + + fastify.listen(0, (err, address) => { + t.error(err) + t.teardown(() => fastify.close()) + + sget({ + method: 'GET', + url: `${address}/timeout` + }, (err, response, body) => { + t.type(err, Error) + t.equal(err.message, 'socket hang up') + }) + }) +}) + +test('onError on route', t => { + t.plan(3) + + const fastify = Fastify() + + const err = new Error('kaboom') + + fastify.get('/', + { + onError (request, reply, error, done) { + t.match(error, err) + done() + } + }, + (req, reply) => { + reply.send(err) + }) + + fastify.inject('/', (err, res) => { + t.error(err) + t.same(JSON.parse(res.payload), { + error: 'Internal Server Error', + message: 'kaboom', + statusCode: 500 + }) + }) +}) From 37add428a50a450ccd5585c2d382bfab9ee91eaa Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 15 Nov 2021 11:26:10 +0100 Subject: [PATCH 30/46] Bumped v3.24.0 --- fastify.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index 1c8cdaf956..cc0ebbeda7 100644 --- a/fastify.js +++ b/fastify.js @@ -1,6 +1,6 @@ 'use strict' -const VERSION = '3.23.1' +const VERSION = '3.24.0' const Avvio = require('avvio') const http = require('http') diff --git a/package.json b/package.json index 1fa48329f4..3eed0dd8c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastify", - "version": "3.23.1", + "version": "3.24.0", "description": "Fast and low overhead web framework, for Node.js", "main": "fastify.js", "type": "commonjs", From ea0e90137406db6e601c13c312ede4aeefb03235 Mon Sep 17 00:00:00 2001 From: gzq Date: Tue, 16 Nov 2021 16:56:53 +0800 Subject: [PATCH 31/46] fix: server.requestTimeout miss when https (#3447) * fix: server.requestTimeout miss when https * add tests --- lib/server.js | 1 + test/requestTimeout.test.js | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index d34fabf99a..b108372bca 100644 --- a/lib/server.js +++ b/lib/server.js @@ -21,6 +21,7 @@ function createServer (options, httpHandler) { } else { server = https.createServer(options.https, httpHandler) server.keepAliveTimeout = options.keepAliveTimeout + server.requestTimeout = options.requestTimeout } } else if (options.http2) { server = http2().createServer(httpHandler) diff --git a/test/requestTimeout.test.js b/test/requestTimeout.test.js index 7978ea3b48..6f62a63bac 100644 --- a/test/requestTimeout.test.js +++ b/test/requestTimeout.test.js @@ -5,7 +5,7 @@ const { test } = require('tap') const Fastify = require('../fastify') test('requestTimeout passed to server', t => { - t.plan(4) + t.plan(5) try { Fastify({ requestTimeout: 500.1 }) @@ -24,6 +24,9 @@ test('requestTimeout passed to server', t => { const httpServer = Fastify({ requestTimeout: 1000 }).server t.equal(httpServer.requestTimeout, 1000) + const httpsServer = Fastify({ requestTimeout: 1000, https: true }).server + t.equal(httpsServer.requestTimeout, 1000) + const serverFactory = (handler, _) => { const server = http.createServer((req, res) => { handler(req, res) From 6cfd1d0828470eba1d033a768e2540643c9eb0fd Mon Sep 17 00:00:00 2001 From: Stefano Giraldi Date: Tue, 16 Nov 2021 15:05:26 +0100 Subject: [PATCH 32/46] docs: add Google Cloud Functions example to Serverless Documentation (#3445) * Update Serverless Docs with Google Cloud Functions example. * Update. Removed the use of `serverFactory` option. * Update docs/Serverless.md Co-authored-by: Frazer Smith * Update docs/Serverless.md Co-authored-by: Frazer Smith * Update docs/Serverless.md Co-authored-by: Matthias Keckl <53833818+matthyk@users.noreply.github.com> * Update docs/Serverless.md Fix typo Co-authored-by: Matthias Keckl <53833818+matthyk@users.noreply.github.com> * Changed order of list contents * Typo Fix Co-authored-by: James Sumners * Update docs/Serverless.md Co-authored-by: James Sumners * Fix typo Co-authored-by: James Sumners * Update docs/Serverless.md Co-authored-by: James Sumners * Update docs/Serverless.md Co-authored-by: James Sumners * Update docs/Serverless.md Co-authored-by: James Sumners * Update docs/Serverless.md Co-authored-by: James Sumners * Update docs/Serverless.md Co-authored-by: James Sumners * Update docs/Serverless.md Co-authored-by: James Sumners * Update docs/Serverless.md Co-authored-by: James Sumners Co-authored-by: Frazer Smith Co-authored-by: Matthias Keckl <53833818+matthyk@users.noreply.github.com> Co-authored-by: James Sumners --- docs/Serverless.md | 123 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/docs/Serverless.md b/docs/Serverless.md index dd82ff2985..251cb5bbc0 100644 --- a/docs/Serverless.md +++ b/docs/Serverless.md @@ -22,6 +22,7 @@ choice with an additional snippet of code. ### Contents - [AWS Lambda](#aws-lambda) +- [Google Cloud Functions](#google-cloud-functions) - [Google Cloud Run](#google-cloud-run) - [Netlify Lambda](#netlify-lambda) - [Vercel](#vercel) @@ -100,6 +101,128 @@ An example deployable with [claudia.js](https://claudiajs.com/tutorials/serverle - API Gateway does not support streams yet, so you are not able to handle [streams](https://www.fastify.io/docs/latest/Reply/#streams). - API Gateway has a timeout of 29 seconds, so it is important to provide a reply during this time. +## Google Cloud Functions + +### Creation of Fastify instance +```js +const fastify = require("fastify")({ + logger: true // you can also define the level passing an object configuration to logger: {level: 'debug'} +}); +``` + +### Add Custom `contentTypeParser` to Fastify instance + +As explained [in issue #946](https://github.com/fastify/fastify/issues/946#issuecomment-766319521), since the Google Cloud Functions platform parses the body of the request before it arrives into Fastify instance, troubling the body request in case of `POST` and `PATCH` methods, you need to add a custom [`ContentTypeParser`](https://www.fastify.io/docs/latest/ContentTypeParser/) to mitigate this behavior. + +```js +fastify.addContentTypeParser('application/json', {}, (req, body, done) => { + done(null, body.body); +}); +``` + +### Define your endpoint (examples) + +A simple `GET` endpoint: +```js +fastify.get('/', async (request, reply) => { + reply.send({message: 'Hello World!'}) +}) +``` + +Or a more complete `POST` endpoint with schema validation: +```js +fastify.route({ + method: 'POST', + url: '/hello', + schema: { + body: { + type: 'object', + properties: { + name: { type: 'string'} + }, + required: ['name'] + }, + response: { + 200: { + type: 'object', + properties: { + message: {type: 'string'} + } + } + }, + }, + handler: async (request, reply) => { + const { name } = request.body; + reply.code(200).send({ + message: `Hello ${name}!` + }) + } +}) +``` + +### Implement and export the function + +Final step, implement the function to handle the request and pass it to Fastify by emitting `request` event to `fastify.server`: + +```js +const fastifyFunction = async (request, reply) => { + await fastify.ready(); + fastify.server.emit('request', request, reply) +} + +export.fastifyFunction = fastifyFunction; +``` + +### Local test + +Install [Google Functions Framework for Node.js](https://github.com/GoogleCloudPlatform/functions-framework-nodejs). + +You can install it globally: +```bash +npm i -g @google-cloud/functions-framework +``` + +Or as a development library: +```bash +npm i --save-dev @google-cloud/functions-framework +``` + +Than you can run your function locally with Functions Framework: +``` bash +npx @google-cloud/functions-framework --target=fastifyFunction +``` + +Or add this command to your `package.json` scripts: +```json +"scripts": { +... +"dev": "npx @google-cloud/functions-framework --target=fastifyFunction" +... +} +``` +and run it with `npm run dev`. + + +### Deploy +```bash +gcloud functions deploy fastifyFunction \ +--runtime nodejs14 --trigger-http --region $GOOGLE_REGION --allow-unauthenticated +``` + +#### Read logs +```bash +gcloud functions logs read +``` + +#### Example request to `/hello` endpoint +```bash +curl -X POST https://$GOOGLE_REGION-$GOOGLE_PROJECT.cloudfunctions.net/me -H "Content-Type: application/json" -d '{ "name": "Fastify" }' +{"message":"Hello Fastify!"} +``` + +### References +- [Google Cloud Functions - Node.js Quickstart ](https://cloud.google.com/functions/docs/quickstart-nodejs) + ## Google Cloud Run Unlike AWS Lambda or Google Cloud Functions, Google Cloud Run is a serverless **container** environment. Its primary purpose is to provide an infrastructure-abstracted environment to run arbitrary containers. As a result, Fastify can be deployed to Google Cloud Run with little-to-no code changes from the way you would write your Fastify app normally. From 5b6b583cd4db573a6134c280963b1f7d6e22128d Mon Sep 17 00:00:00 2001 From: heplyadm <66788740+heplyadm@users.noreply.github.com> Date: Tue, 16 Nov 2021 19:08:11 +0100 Subject: [PATCH 33/46] docs: add fastify-polyglot plugin to the ecosystem (#3452) --- docs/Ecosystem.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Ecosystem.md b/docs/Ecosystem.md index aa55b45de0..0bcc0490f5 100644 --- a/docs/Ecosystem.md +++ b/docs/Ecosystem.md @@ -158,6 +158,7 @@ Plugins maintained by the Fastify team are listed under [Core](#core) while plug - [`fastify-orientdb`](https://github.com/mahmed8003/fastify-orientdb) Fastify OrientDB connection plugin, with which you can share the OrientDB connection across every part of your server. - [`fastify-piscina`](https://github.com/piscinajs/fastify-piscina) A worker thread pool plugin using [Piscina](https://github.com/piscinajs/piscina). - [`fastify-peekaboo`](https://github.com/simone-sanfratello/fastify-peekaboo) Fastify plugin for memoize responses by expressive settings. +- [`fastify-polyglot`](https://github.com/heply/fastify-polyglot) A plugin to handle i18n using [node-polyglot](https://www.npmjs.com/package/node-polyglot). - [`fastify-postgraphile`](https://github.com/alemagio/fastify-postgraphile) Plugin to integrate [PostGraphile](https://www.graphile.org/postgraphile/) in a Fastify project. - [`fastify-prettier`](https://github.com/hsynlms/fastify-prettier) A Fastify plugin that uses [prettier](https://github.com/prettier/prettier) under the hood to beautify outgoing responses and/or other things in the Fastify server. - [`fastify-print-routes`](https://github.com/ShogunPanda/fastify-print-routes) A Fastify plugin that prints all available routes. From f15c557ae1b44e8bad40d2789a8e2e9c67e7c013 Mon Sep 17 00:00:00 2001 From: Valerio Pizzichini Date: Tue, 16 Nov 2021 19:46:11 +0100 Subject: [PATCH 34/46] fix types: define instance listen callback error nullable (#3449) --- test/types/instance.test-d.ts | 6 ++++++ types/instance.d.ts | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/test/types/instance.test-d.ts b/test/types/instance.test-d.ts index 6bf1b9f023..4285c006cb 100644 --- a/test/types/instance.test-d.ts +++ b/test/types/instance.test-d.ts @@ -90,6 +90,12 @@ expectAssignable(server.listen('3000', '', (err, address) => {})) expectAssignable(server.listen(3000, (err, address) => {})) expectAssignable(server.listen('3000', (err, address) => {})) +// test listen method callback types +expectAssignable(server.listen('3000', (err, address) => { + expectAssignable(err) + expectAssignable(address) +})) + // test listen method promise expectAssignable>(server.listen(3000)) expectAssignable>(server.listen('3000')) diff --git a/types/instance.d.ts b/types/instance.d.ts index 4f184d820a..ce7e351c3c 100644 --- a/types/instance.d.ts +++ b/types/instance.d.ts @@ -70,11 +70,11 @@ export interface FastifyInstance< inject(opts: InjectOptions | string): Promise; inject(): LightMyRequestChain; - listen(port: number | string, address: string, backlog: number, callback: (err: Error, address: string) => void): void; - listen(port: number | string, address: string, callback: (err: Error, address: string) => void): void; - listen(port: number | string, callback: (err: Error, address: string) => void): void; + listen(port: number | string, address: string, backlog: number, callback: (err: Error|null, address: string) => void): void; + listen(port: number | string, address: string, callback: (err: Error|null, address: string) => void): void; + listen(port: number | string, callback: (err: Error|null, address: string) => void): void; listen(port: number | string, address?: string, backlog?: number): Promise; - listen(opts: { port: number; host?: string; backlog?: number }, callback: (err: Error, address: string) => void): void; + listen(opts: { port: number; host?: string; backlog?: number }, callback: (err: Error|null, address: string) => void): void; listen(opts: { port: number; host?: string; backlog?: number }): Promise; ready(): FastifyInstance & PromiseLike; From 1dc4717fda444a790a1eedc9ffe18dfe9ab08aa8 Mon Sep 17 00:00:00 2001 From: Morgan Roderick <20321+mroderick@users.noreply.github.com> Date: Tue, 16 Nov 2021 20:46:10 +0100 Subject: [PATCH 35/46] docs: replace "sons" with "descendants" (#3453) --- docs/Plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Plugins.md b/docs/Plugins.md index 2ba3e08f3b..e5fce2624e 100644 --- a/docs/Plugins.md +++ b/docs/Plugins.md @@ -4,7 +4,7 @@ Fastify allows the user to extend its functionalities with plugins. A plugin can be a set of routes, a server [decorator](Decorators.md), or whatever. The API that you will need to use one or more plugins, is `register`.
-By default, `register` creates a *new scope*, this means that if you make some changes to the Fastify instance (via `decorate`), this change will not be reflected by the current context ancestors, but only to its sons. This feature allows us to achieve plugin *encapsulation* and *inheritance*, in this way we create a *direct acyclic graph* (DAG) and we will not have issues caused by cross dependencies. +By default, `register` creates a *new scope*, this means that if you make some changes to the Fastify instance (via `decorate`), this change will not be reflected by the current context ancestors, but only to its descendants. This feature allows us to achieve plugin *encapsulation* and *inheritance*, in this way we create a *direct acyclic graph* (DAG) and we will not have issues caused by cross dependencies. You already see in the [getting started](Getting-Started.md#register) section how using this API is pretty straightforward. ``` From f2644835fe7e7f8547b8b0f3f53c3e4a44ee76e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Nov 2021 14:11:12 +0000 Subject: [PATCH 36/46] build(deps): bump fastify/github-action-merge-dependabot (#3459) Bumps [fastify/github-action-merge-dependabot](https://github.com/fastify/github-action-merge-dependabot) from 2.5.0 to 2.6.0. - [Release notes](https://github.com/fastify/github-action-merge-dependabot/releases) - [Commits](https://github.com/fastify/github-action-merge-dependabot/compare/v2.5.0...v2.6.0) --- updated-dependencies: - dependency-name: fastify/github-action-merge-dependabot dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4dcf948ed9..47ea7d0b1b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,7 +74,7 @@ jobs: needs: test runs-on: ubuntu-latest steps: - - uses: fastify/github-action-merge-dependabot@v2.5.0 + - uses: fastify/github-action-merge-dependabot@v2.6.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} target: minor From 4996bacc463dd1b37c9e2611db43fa1a5d1ce358 Mon Sep 17 00:00:00 2001 From: qin20 <10720918+qin20@users.noreply.github.com> Date: Thu, 18 Nov 2021 23:59:43 +0800 Subject: [PATCH 37/46] docs(examples): fix ts-server exit code of error (#3458) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 秦缘斌 --- examples/typescript-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/typescript-server.ts b/examples/typescript-server.ts index 549213ac12..31eb3bc317 100644 --- a/examples/typescript-server.ts +++ b/examples/typescript-server.ts @@ -73,6 +73,6 @@ server.get<{ server.listen(8080, (err, address) => { if (err) { console.error(err); - process.exit(0); + process.exit(1); } }); From 48a99a470f82461c711a2c914e8221e49c731b07 Mon Sep 17 00:00:00 2001 From: Emanuele Bertoldi Date: Thu, 18 Nov 2021 19:20:53 +0100 Subject: [PATCH 38/46] docs: add fastify-crud-generator plugin (#3460) --- docs/Ecosystem.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Ecosystem.md b/docs/Ecosystem.md index 0bcc0490f5..4cafa616be 100644 --- a/docs/Ecosystem.md +++ b/docs/Ecosystem.md @@ -91,6 +91,7 @@ Plugins maintained by the Fastify team are listed under [Core](#core) while plug - [`fastify-cloudevents`](https://github.com/smartiniOnGitHub/fastify-cloudevents) Fastify plugin to generate and forward Fastify events in the Cloudevents format. - [`fastify-cockroachdb`](https://github.com/alex-ppg/fastify-cockroachdb) Fastify plugin to connect to a CockroachDB PostgreSQL instance via the Sequelize ORM. - [`fastify-couchdb`](https://github.com/nigelhanlon/fastify-couchdb) Fastify plugin to add CouchDB support via [nano](https://github.com/apache/nano). +- [`fastify-crud-generator`](https://github.com/heply/fastify-crud-generator) A plugin to rapidly generate CRUD routes for any entity. - [`fastify-custom-healthcheck`](https://github.com/gkampitakis/fastify-custom-healthcheck) Fastify plugin to add health route in your server that asserts custom functions. - [`fastify-decorators`](https://github.com/L2jLiga/fastify-decorators) Fastify plugin that provides the set of TypeScript decorators. - [`fastify-disablecache`](https://github.com/Fdawgs/fastify-disablecache) Fastify plugin to disable client-side caching, inspired by [nocache](https://github.com/helmetjs/nocache). From 9814c3828ff020a90bcb364cea06d2cee5f4b1bc Mon Sep 17 00:00:00 2001 From: gzq Date: Fri, 19 Nov 2021 18:56:21 +0800 Subject: [PATCH 39/46] fix: server.maxRequestsPerSocket when https (#3463) --- lib/server.js | 18 +++++++++--------- test/maxRequestsPerSocket.test.js | 10 ++++++++++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/server.js b/lib/server.js index b108372bca..adcfd06dd3 100644 --- a/lib/server.js +++ b/lib/server.js @@ -14,20 +14,20 @@ function createServer (options, httpHandler) { let server = null if (options.serverFactory) { server = options.serverFactory(httpHandler, options) - } else if (options.https) { - if (options.http2) { + } else if (options.http2) { + if (options.https) { server = http2().createSecureServer(options.https, httpHandler) - server.on('session', sessionTimeout(options.http2SessionTimeout)) } else { - server = https.createServer(options.https, httpHandler) - server.keepAliveTimeout = options.keepAliveTimeout - server.requestTimeout = options.requestTimeout + server = http2().createServer(httpHandler) } - } else if (options.http2) { - server = http2().createServer(httpHandler) server.on('session', sessionTimeout(options.http2SessionTimeout)) } else { - server = http.createServer(httpHandler) + // this is http1 + if (options.https) { + server = https.createServer(options.https, httpHandler) + } else { + server = http.createServer(httpHandler) + } server.keepAliveTimeout = options.keepAliveTimeout server.requestTimeout = options.requestTimeout // we treat zero as null diff --git a/test/maxRequestsPerSocket.test.js b/test/maxRequestsPerSocket.test.js index 3104b12745..edd33e4d46 100644 --- a/test/maxRequestsPerSocket.test.js +++ b/test/maxRequestsPerSocket.test.js @@ -102,3 +102,13 @@ test('maxRequestsPerSocket should 0', async (t) => { const initialConfig = Fastify().initialConfig t.same(initialConfig.maxRequestsPerSocket, 0) }) + +test('requestTimeout passed to server', t => { + t.plan(2) + + const httpServer = Fastify({ maxRequestsPerSocket: 5 }).server + t.equal(httpServer.maxRequestsPerSocket, 5) + + const httpsServer = Fastify({ maxRequestsPerSocket: 5, https: true }).server + t.equal(httpsServer.maxRequestsPerSocket, 5) +}) From f97cdfb79e4067b6a75784667c0e4b7b9fceb8bb Mon Sep 17 00:00:00 2001 From: KaKa Date: Fri, 19 Nov 2021 20:58:43 +0800 Subject: [PATCH 40/46] fix: logger req serializers (#3465) * fix: logger req serializers * chore: linting * test: name typo --- lib/logger.js | 2 +- test/logger.test.js | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/logger.js b/lib/logger.js index 59df861bc7..0a44150506 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -50,7 +50,7 @@ const serializers = { return { method: req.method, url: req.url, - version: req.headers['accept-version'], + version: req.headers && req.headers['accept-version'], hostname: req.hostname, remoteAddress: req.ip, remotePort: req.socket ? req.socket.remotePort : undefined diff --git a/test/logger.test.js b/test/logger.test.js index 8782488e9b..81e675b111 100644 --- a/test/logger.test.js +++ b/test/logger.test.js @@ -1516,3 +1516,19 @@ test('should create a default logger if provided one is invalid', t => { t.pass() }) + +test('should not throw error when serializing custom req', t => { + t.plan(1) + + const lines = [] + const dest = new stream.Writable({ + write: function (chunk, enc, cb) { + lines.push(JSON.parse(chunk)) + cb() + } + }) + const fastify = Fastify({ logger: { level: 'info', stream: dest } }) + fastify.log.info({ req: {} }) + + t.same(lines[0].req, {}) +}) From 7972a1b3e76d94251db9918ff92cb1466d5f862f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Nov 2021 14:38:56 +0000 Subject: [PATCH 41/46] build(deps-dev): bump tsd from 0.18.0 to 0.19.0 (#3466) Bumps [tsd](https://github.com/SamVerschueren/tsd) from 0.18.0 to 0.19.0. - [Release notes](https://github.com/SamVerschueren/tsd/releases) - [Commits](https://github.com/SamVerschueren/tsd/compare/v0.18.0...v0.19.0) --- updated-dependencies: - dependency-name: tsd dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3eed0dd8c0..af354185d1 100644 --- a/package.json +++ b/package.json @@ -166,7 +166,7 @@ "tap": "^15.0.5", "tap-mocha-reporter": "^5.0.1", "then-sleep": "^1.0.1", - "tsd": "^0.18.0", + "tsd": "^0.19.0", "typescript": "^4.0.2", "undici": "^3.3.5", "x-xss-protection": "^2.0.0", From 11c77b595309f5a5010f123147a6c9237b55a79d Mon Sep 17 00:00:00 2001 From: Jiri Spac Date: Fri, 19 Nov 2021 18:06:26 +0100 Subject: [PATCH 42/46] docs: add code sample for development logger config (#3464) * docs: add code sample for development logger config add section of code to show how to setup logger for development * docs: tidy up logging * docs: fix js block formatting * Update docs/Logging.md Co-authored-by: James Sumners * docs: do not include the process.env.NODE_ENV in the code sample someone might prefer other env variable * Update docs/Logging.md Co-authored-by: Frazer Smith * Update docs/Logging.md Co-authored-by: Frazer Smith * docs replace sensibe with appropriate Co-authored-by: James Sumners Co-authored-by: Frazer Smith --- docs/Logging.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/Logging.md b/docs/Logging.md index baf44ed251..0cc496f217 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -2,6 +2,7 @@ ## Logging +### Enable logging Logging is disabled by default, and you can enable it by passing `{ logger: true }` or `{ logger: { level: 'info' } }` when you create a fastify instance. Note that if the logger is disabled, it is impossible to @@ -11,13 +12,34 @@ this purpose. As Fastify is focused on performance, it uses [pino](https://github.com/pinojs/pino) as its logger, with the default log level, when enabled, set to `'info'`. -Enabling the logger is extremely easy: +Enabling the production JSON logger: ```js const fastify = require('fastify')({ logger: true }) +``` + +Enabling the logger with appropriate configuration for both local development and production environment requires bit more configuration: +```js +const fastify = require('fastify')({ + logger: { + prettyPrint: + environment === 'development' + ? { + translateTime: 'HH:MM:ss Z', + ignore: 'pid,hostname' + } + : false + } +}) +``` +⚠️ `pino-pretty` needs to be installed as a dev dependency, it is not included by default for performance reasons. +### Usage +You can use the logger like this in your route handlers: + +```js fastify.get('/', options, function (request, reply) { request.log.info('Some info about the current request') reply.send({ hello: 'world' }) From e1ad1f309eabe2d78f70ac2e4276d5ac36ec8ccf Mon Sep 17 00:00:00 2001 From: Saihajpreet Singh Date: Sat, 20 Nov 2021 02:53:30 -0500 Subject: [PATCH 43/46] docs: add post example to intro (#3438) * docs: add post example to intro * Update docs/Getting-Started.md Co-authored-by: Manuel Spigolon * Revert "Update docs/Getting-Started.md" This reverts commit 0018cb58e412f0b4a569c073e26bc1fd93032e0e. * add validation Co-authored-by: Manuel Spigolon --- docs/Getting-Started.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index 84d2a5e9a5..fb53566038 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -262,6 +262,24 @@ async function routes (fastify, options) { } return result }) + + const animalBodyJsonSchema = { + type: 'object', + required: ['animal'], + properties: { + animal: { type: 'string' }, + }, + } + + const schema = { + body: animalBodyJsonSchema, + } + + fastify.post('/animals', { schema }, async (request, reply) => { + // we can use the `request.body` object to get the data sent by the client + const result = await collection.insertOne({ animal: request.body.animal }) + return result + }) } module.exports = routes From 1c8e4eda83cb0910cb03372a96d479b8226b1fb6 Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Sat, 20 Nov 2021 09:08:46 +0100 Subject: [PATCH 44/46] fix: use current instance schema controller (#3454) --- fastify.js | 3 +- test/schema-feature.test.js | 146 ++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 2 deletions(-) diff --git a/fastify.js b/fastify.js index cc0ebbeda7..34a3d47586 100644 --- a/fastify.js +++ b/fastify.js @@ -631,8 +631,7 @@ function fastify (options) { function setSchemaController (schemaControllerOpts) { throwIfAlreadyStarted('Cannot call "setSchemaController" when fastify instance is already started!') const old = this[kSchemaController] - const parent = old.parent || old - const schemaController = SchemaController.buildSchemaController(parent, Object.assign({}, old.opts, schemaControllerOpts)) + const schemaController = SchemaController.buildSchemaController(old, Object.assign({}, old.opts, schemaControllerOpts)) this[kSchemaController] = schemaController this.getSchema = schemaController.getSchema.bind(schemaController) this.getSchemas = schemaController.getSchemas.bind(schemaController) diff --git a/test/schema-feature.test.js b/test/schema-feature.test.js index 46ce98ac83..fdf19d0c49 100644 --- a/test/schema-feature.test.js +++ b/test/schema-feature.test.js @@ -1604,3 +1604,149 @@ test('setSchemaController: Inherits buildValidator from parent if not present wi t.equal(childSerializerCalled, 1, 'Should be called from the child') t.equal(res.statusCode, 400, 'Should not coearce the string into array') }) + +test('Should throw if not default validator passed', async t => { + t.plan(4) + const customAjv = new Ajv({ coerceTypes: false }) + const someSchema = { + $id: 'some', + type: 'array', + items: { + type: 'string' + } + } + const anotherSchema = { + $id: 'another', + type: 'integer' + } + const plugin = fp(function (pluginInstance, _, pluginDone) { + pluginInstance.setSchemaController({ + compilersFactory: { + buildValidator: function (externalSchemas) { + const schemaKeys = Object.keys(externalSchemas) + t.equal(schemaKeys.length, 2) + t.same(schemaKeys, ['some', 'another']) + + for (const key of schemaKeys) { + if (customAjv.getSchema(key) == null) { + customAjv.addSchema(externalSchemas[key], key) + } + } + return function validatorCompiler ({ schema }) { + return customAjv.compile(schema) + } + } + } + }) + + pluginDone() + }) + const server = Fastify() + + server.addSchema(someSchema) + + server.register((instance, opts, done) => { + instance.addSchema(anotherSchema) + + instance.register(plugin, {}) + + instance.post( + '/', + { + schema: { + query: { + msg: { + $ref: 'some#' + } + }, + headers: { + 'x-another': { + $ref: 'another#' + } + } + } + }, + (req, reply) => { + reply.send({ noop: 'noop' }) + } + ) + + done() + }) + + try { + const res = await server.inject({ + method: 'POST', + url: '/', + query: { + msg: ['string'] + } + }) + + t.equal(res.json().message, 'querystring.msg should be array') + t.equal(res.statusCode, 400, 'Should not coearce the string into array') + } catch (err) { + t.error(err) + } +}) + +test('Should throw if not default validator passed', async t => { + t.plan(2) + const someSchema = { + $id: 'some', + type: 'array', + items: { + type: 'string' + } + } + const anotherSchema = { + $id: 'another', + type: 'integer' + } + + const server = Fastify() + + server.addSchema(someSchema) + + server.register((instance, opts, done) => { + instance.addSchema(anotherSchema) + + instance.post( + '/', + { + schema: { + query: { + msg: { + $ref: 'some#' + } + }, + headers: { + 'x-another': { + $ref: 'another#' + } + } + } + }, + (req, reply) => { + reply.send({ noop: 'noop' }) + } + ) + + done() + }) + + try { + const res = await server.inject({ + method: 'POST', + url: '/', + query: { + msg: ['string'] + } + }) + + t.equal(res.json().message, 'querystring.msg should be array') + t.equal(res.statusCode, 400, 'Should not coearce the string into array') + } catch (err) { + t.error(err) + } +}) From b55ec60047171c2c94753a08f01521464bff998e Mon Sep 17 00:00:00 2001 From: Chris Karcher Date: Sun, 21 Nov 2021 02:14:13 -0600 Subject: [PATCH 45/46] fix(typescript): add type declaration for FastifyRequest.context (#3472) --- test/types/request.test-d.ts | 8 +++++++- test/types/route.test-d.ts | 21 +++++++++++++++++++++ types/request.d.ts | 5 ++++- types/route.d.ts | 2 +- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index aa50150c1e..c5f1a2ffdc 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -1,5 +1,5 @@ import { expectType } from 'tsd' -import fastify, { RouteHandler, RawRequestDefaultExpression, RequestBodyDefault, RequestGenericInterface } from '../../fastify' +import fastify, { RouteHandler, RawRequestDefaultExpression, RequestBodyDefault, RequestGenericInterface, FastifyContext, ContextConfigDefault, FastifyContextConfig } from '../../fastify' import { RequestParamsDefault, RequestHeadersDefault, RequestQuerystringDefault } from '../../types/utils' import { FastifyLoggerInstance } from '../../types/logger' import { FastifyRequest } from '../../types/request' @@ -50,6 +50,8 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.raw) expectType(request.body) expectType(request.params) + expectType>(request.context) + expectType(request.context.config) expectType(request.headers) request.headers = {} @@ -72,6 +74,8 @@ const postHandler: Handler = function (request) { expectType(request.params.id) expectType(request.headers['x-foobar']) expectType(request.server) + expectType>(request.context) + expectType(request.context.config) } function putHandler (request: CustomRequest, reply: FastifyReply) { @@ -84,6 +88,8 @@ function putHandler (request: CustomRequest, reply: FastifyReply) { expectType(request.params.id) expectType(request.headers['x-foobar']) expectType(request.server) + expectType>(request.context) + expectType(request.context.config) } const server = fastify() diff --git a/test/types/route.test-d.ts b/test/types/route.test-d.ts index 56b328b103..c6de92a2cb 100644 --- a/test/types/route.test-d.ts +++ b/test/types/route.test-d.ts @@ -65,6 +65,9 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.query) expectType(req.params) expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) + expectType(req.context.config.extra) expectType(res.context.config.foo) expectType(res.context.config.bar) expectType(res.context.config.extra) @@ -80,6 +83,8 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.query) expectType(req.params) expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) expectType(res.context.config.foo) expectType(res.context.config.bar) }, @@ -88,6 +93,8 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.query) expectType(req.params) expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) expectType(res.context.config.foo) expectType(res.context.config.bar) expectType(payload) @@ -99,6 +106,8 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.query) expectType(req.params) expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) expectType(res.context.config.foo) expectType(res.context.config.bar) }, @@ -107,6 +116,8 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.query) expectType(req.params) expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) expectType(res.context.config.foo) expectType(res.context.config.bar) }, @@ -115,6 +126,8 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.query) expectType(req.params) expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) expectType(res.context.config.foo) expectType(res.context.config.bar) expectType(res.statusCode) @@ -124,6 +137,8 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.query) expectType(req.params) expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) expectType(res.context.config.foo) expectType(res.context.config.bar) }, @@ -132,6 +147,8 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.query) expectType(req.params) expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) expectType(res.context.config.foo) expectType(res.context.config.bar) }, @@ -140,6 +157,8 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.query) expectType(req.params) expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) expectType(res.context.config.foo) expectType(res.context.config.bar) }, @@ -148,6 +167,8 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' expectType(req.query) expectType(req.params) expectType(req.headers) + expectType(req.context.config.foo) + expectType(req.context.config.bar) expectType(res.context.config.foo) expectType(res.context.config.bar) } diff --git a/types/request.d.ts b/types/request.d.ts index 2857fba3b5..93dab01316 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -1,7 +1,8 @@ import { FastifyLoggerInstance } from './logger' -import { RawServerBase, RawServerDefault, RawRequestDefaultExpression, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './utils' +import { ContextConfigDefault, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './utils' import { RouteGenericInterface } from './route' import { FastifyInstance } from './instance' +import { FastifyContext } from './context' export interface RequestGenericInterface { Body?: RequestBodyDefault; @@ -18,6 +19,7 @@ export interface FastifyRequest< RouteGeneric extends RouteGenericInterface = RouteGenericInterface, RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, + ContextConfig = ContextConfigDefault, > { id: any; params: RouteGeneric['Params']; @@ -27,6 +29,7 @@ export interface FastifyRequest< log: FastifyLoggerInstance; server: FastifyInstance; body: RouteGeneric['Body']; + context: FastifyContext; /** in order for this to be used the user should ensure they have set the attachValidation option. */ validationError?: Error & { validation: any; validationContext: string }; diff --git a/types/route.d.ts b/types/route.d.ts index d12652c132..5cd01033a3 100644 --- a/types/route.d.ts +++ b/types/route.d.ts @@ -59,7 +59,7 @@ export type RouteHandlerMethod< ContextConfig = ContextConfigDefault > = ( this: FastifyInstance, - request: FastifyRequest, + request: FastifyRequest, reply: FastifyReply ) => void | Promise From 1552f7df18231670ae8572a99e1151064b80ae8f Mon Sep 17 00:00:00 2001 From: Rishit Pandey Date: Sun, 21 Nov 2021 16:14:55 +0530 Subject: [PATCH 46/46] docs: add set-cookie special case (#3470) * docs: add set-cookie special case * add sub section about set-cookie * fix typos and add point about resetting set-cookie --- docs/Reply.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/Reply.md b/docs/Reply.md index 43ab8c75b7..f292c772a4 100644 --- a/docs/Reply.md +++ b/docs/Reply.md @@ -7,6 +7,7 @@ - [.statusCode](#statusCode) - [.server](#server) - [.header(key, value)](#headerkey-value) + - [set-cookie](#set-cookie) - [.headers(object)](#headersobject) - [.getHeader(key)](#getheaderkey) - [.getHeaders()](#getheaders) @@ -109,6 +110,18 @@ fastify.get('/', async function (req, rep) { Sets a response header. If the value is omitted or undefined, it is coerced to `''`. + +- ### set-cookie + - While sending different values as cookie with `set-cookie` as the key, every value will be sent as cookie instead of replacing the previous value. + + ```js + reply.header('set-cookie', 'foo'); + reply.header('set-cookie', 'bar'); + ``` + - The browser will only consider the latest reference of a key for `set-cookie` header. The fact that this is done this way is to avoid parsing the set-cookie header when you add it in the reply and speeds up the serialization of the reply. + + - To reset the `set-cookie`, you need to make an explicit call to `reply.removeHeader('set-cookie')`, read more about `.removeHeader(key)` [here](#removeheaderkey). + For more information, see [`http.ServerResponse#setHeader`](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_response_setheader_name_value).