fastify 3.26.0 → 3.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/build-validation.js +2 -0
- package/docs/Reference/Server.md +14 -0
- package/fastify.js +18 -3
- package/lib/noop-set.js +10 -0
- package/lib/route.js +21 -0
- package/lib/symbols.js +2 -1
- package/package.json +4 -4
- package/test/close.test.js +38 -0
- package/test/noop-set.test.js +19 -0
|
@@ -15,6 +15,7 @@ const ajv = new Ajv({
|
|
|
15
15
|
const defaultInitOptions = {
|
|
16
16
|
connectionTimeout: 0, // 0 sec
|
|
17
17
|
keepAliveTimeout: 5000, // 5 sec
|
|
18
|
+
forceCloseConnections: false, // keep-alive connections
|
|
18
19
|
maxRequestsPerSocket: 0, // no limit
|
|
19
20
|
requestTimeout: 0, // no limit
|
|
20
21
|
bodyLimit: 1024 * 1024, // 1 MiB
|
|
@@ -49,6 +50,7 @@ const schema = {
|
|
|
49
50
|
properties: {
|
|
50
51
|
connectionTimeout: { type: 'integer', default: defaultInitOptions.connectionTimeout },
|
|
51
52
|
keepAliveTimeout: { type: 'integer', default: defaultInitOptions.keepAliveTimeout },
|
|
53
|
+
forceCloseConnections: { type: 'boolean', default: defaultInitOptions.forceCloseConnections },
|
|
52
54
|
maxRequestsPerSocket: { type: 'integer', default: defaultInitOptions.maxRequestsPerSocket, nullable: true },
|
|
53
55
|
requestTimeout: { type: 'integer', default: defaultInitOptions.requestTimeout },
|
|
54
56
|
bodyLimit: { type: 'integer', default: defaultInitOptions.bodyLimit },
|
package/docs/Reference/Server.md
CHANGED
|
@@ -13,6 +13,7 @@ describes the properties available in that options object.
|
|
|
13
13
|
- [`https`](#https)
|
|
14
14
|
- [`connectionTimeout`](#connectiontimeout)
|
|
15
15
|
- [`keepAliveTimeout`](#keepalivetimeout)
|
|
16
|
+
- [`forceCloseConnections](#forcecloseconnections)
|
|
16
17
|
- [`maxRequestsPerSocket`](#maxrequestspersocket)
|
|
17
18
|
- [`requestTimeout`](#requesttimeout)
|
|
18
19
|
- [`ignoreTrailingSlash`](#ignoretrailingslash)
|
|
@@ -124,6 +125,19 @@ use. Also, when `serverFactory` option is specified, this option is ignored.
|
|
|
124
125
|
|
|
125
126
|
+ Default: `5000` (5 seconds)
|
|
126
127
|
|
|
128
|
+
### `forceCloseConnections`
|
|
129
|
+
<a id="forcecloseconnections"></a>
|
|
130
|
+
|
|
131
|
+
When set to `true` requests with the header `connection: keep-alive` will be
|
|
132
|
+
tracked by the server. Upon [`close`](#close), the server will iterate the
|
|
133
|
+
current persistent connections and [destroy their
|
|
134
|
+
sockets](https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketdestroyerror).
|
|
135
|
+
This means the server will shutdown immediately instead of waiting for existing
|
|
136
|
+
persistent connections to timeout first. Important: connections are not
|
|
137
|
+
inspected to determine if requests have been completed.
|
|
138
|
+
|
|
139
|
+
+ Default: `false`
|
|
140
|
+
|
|
127
141
|
### `maxRequestsPerSocket`
|
|
128
142
|
<a id="factory-max-requests-per-socket"></a>
|
|
129
143
|
|
package/fastify.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const VERSION = '3.
|
|
3
|
+
const VERSION = '3.27.0'
|
|
4
4
|
|
|
5
5
|
const Avvio = require('avvio')
|
|
6
6
|
const http = require('http')
|
|
@@ -26,7 +26,8 @@ const {
|
|
|
26
26
|
kOptions,
|
|
27
27
|
kPluginNameChain,
|
|
28
28
|
kSchemaErrorFormatter,
|
|
29
|
-
kErrorHandler
|
|
29
|
+
kErrorHandler,
|
|
30
|
+
kKeepAliveConnections
|
|
30
31
|
} = require('./lib/symbols.js')
|
|
31
32
|
|
|
32
33
|
const { createServer } = require('./lib/server')
|
|
@@ -45,6 +46,7 @@ const build404 = require('./lib/fourOhFour')
|
|
|
45
46
|
const getSecuredInitialConfig = require('./lib/initialConfigValidation')
|
|
46
47
|
const override = require('./lib/pluginOverride')
|
|
47
48
|
const warning = require('./lib/warnings')
|
|
49
|
+
const noopSet = require('./lib/noop-set')
|
|
48
50
|
const { defaultInitOptions } = getSecuredInitialConfig
|
|
49
51
|
|
|
50
52
|
const {
|
|
@@ -133,6 +135,7 @@ function fastify (options) {
|
|
|
133
135
|
// Update the options with the fixed values
|
|
134
136
|
options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout
|
|
135
137
|
options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout
|
|
138
|
+
options.forceCloseConnections = typeof options.forceCloseConnections === 'boolean' ? options.forceCloseConnections : defaultInitOptions.forceCloseConnections
|
|
136
139
|
options.maxRequestsPerSocket = options.maxRequestsPerSocket || defaultInitOptions.maxRequestsPerSocket
|
|
137
140
|
options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout
|
|
138
141
|
options.logger = logger
|
|
@@ -146,6 +149,7 @@ function fastify (options) {
|
|
|
146
149
|
options.exposeHeadRoutes = exposeHeadRoutes
|
|
147
150
|
|
|
148
151
|
const initialConfig = getSecuredInitialConfig(options)
|
|
152
|
+
const keepAliveConnections = options.forceCloseConnections === true ? new Set() : noopSet()
|
|
149
153
|
|
|
150
154
|
let constraints = options.constraints
|
|
151
155
|
if (options.versioning) {
|
|
@@ -176,7 +180,8 @@ function fastify (options) {
|
|
|
176
180
|
maxParamLength: options.maxParamLength || defaultInitOptions.maxParamLength,
|
|
177
181
|
caseSensitive: options.caseSensitive,
|
|
178
182
|
buildPrettyMeta: defaultBuildPrettyMeta
|
|
179
|
-
}
|
|
183
|
+
},
|
|
184
|
+
keepAliveConnections
|
|
180
185
|
})
|
|
181
186
|
|
|
182
187
|
// 404 router, used for handling encapsulated 404 handlers
|
|
@@ -200,6 +205,7 @@ function fastify (options) {
|
|
|
200
205
|
closing: false,
|
|
201
206
|
started: false
|
|
202
207
|
},
|
|
208
|
+
[kKeepAliveConnections]: keepAliveConnections,
|
|
203
209
|
[kOptions]: options,
|
|
204
210
|
[kChildren]: [],
|
|
205
211
|
[kBodyLimit]: bodyLimit,
|
|
@@ -375,6 +381,15 @@ function fastify (options) {
|
|
|
375
381
|
if (fastify[kState].listening) {
|
|
376
382
|
// No new TCP connections are accepted
|
|
377
383
|
instance.server.close(done)
|
|
384
|
+
|
|
385
|
+
for (const conn of fastify[kKeepAliveConnections]) {
|
|
386
|
+
// We must invoke the destroy method instead of merely unreffing
|
|
387
|
+
// the sockets. If we only unref, then the callback passed to
|
|
388
|
+
// `fastify.close` will never be invoked; nor will any of the
|
|
389
|
+
// registered `onClose` hooks.
|
|
390
|
+
conn.destroy()
|
|
391
|
+
fastify[kKeepAliveConnections].delete(conn)
|
|
392
|
+
}
|
|
378
393
|
} else {
|
|
379
394
|
done(null)
|
|
380
395
|
}
|
package/lib/noop-set.js
ADDED
package/lib/route.js
CHANGED
|
@@ -41,6 +41,7 @@ const {
|
|
|
41
41
|
} = require('./symbols.js')
|
|
42
42
|
|
|
43
43
|
function buildRouting (options) {
|
|
44
|
+
const { keepAliveConnections } = options
|
|
44
45
|
const router = FindMyWay(options.config)
|
|
45
46
|
|
|
46
47
|
let avvio
|
|
@@ -345,6 +346,17 @@ function buildRouting (options) {
|
|
|
345
346
|
}
|
|
346
347
|
}
|
|
347
348
|
|
|
349
|
+
// When server.forceCloseConnections is true, we will collect any requests
|
|
350
|
+
// that have indicated they want persistence so that they can be reaped
|
|
351
|
+
// on server close. Otherwise, the container is a noop container.
|
|
352
|
+
const connHeader = String.prototype.toLowerCase.call(req.headers.connection || '')
|
|
353
|
+
if (connHeader === 'keep-alive') {
|
|
354
|
+
if (keepAliveConnections.has(req.socket) === false) {
|
|
355
|
+
keepAliveConnections.add(req.socket)
|
|
356
|
+
req.socket.on('close', removeTrackedSocket.bind({ keepAliveConnections, socket: req.socket }))
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
348
360
|
// we revert the changes in defaultRoute
|
|
349
361
|
if (req.headers[kRequestAcceptVersion] !== undefined) {
|
|
350
362
|
req.headers['accept-version'] = req.headers[kRequestAcceptVersion]
|
|
@@ -485,6 +497,15 @@ function preParsingHookRunner (functions, request, reply, cb) {
|
|
|
485
497
|
next(null, request[kRequestPayloadStream])
|
|
486
498
|
}
|
|
487
499
|
|
|
500
|
+
/**
|
|
501
|
+
* Used within the route handler as a `net.Socket.close` event handler.
|
|
502
|
+
* The purpose is to remove a socket from the tracked sockets collection when
|
|
503
|
+
* the socket has naturally timed out.
|
|
504
|
+
*/
|
|
505
|
+
function removeTrackedSocket () {
|
|
506
|
+
this.keepAliveConnections.delete(this.socket)
|
|
507
|
+
}
|
|
508
|
+
|
|
488
509
|
function noop () { }
|
|
489
510
|
|
|
490
511
|
module.exports = { buildRouting, validateBodyLimitOption }
|
package/lib/symbols.js
CHANGED
|
@@ -44,7 +44,8 @@ const keys = {
|
|
|
44
44
|
kPluginNameChain: Symbol('fastify.pluginNameChain'),
|
|
45
45
|
// This symbol is only meant to be used for fastify tests and should not be used for any other purpose
|
|
46
46
|
kTestInternals: Symbol('fastify.testInternals'),
|
|
47
|
-
kErrorHandler: Symbol('fastify.errorHandler')
|
|
47
|
+
kErrorHandler: Symbol('fastify.errorHandler'),
|
|
48
|
+
kKeepAliveConnections: Symbol('fastify.keepAliveConnections')
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
module.exports = keys
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.27.0",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts",
|
|
17
17
|
"prepublishOnly": "tap --no-check-coverage test/internals/version.test.js",
|
|
18
18
|
"test": "npm run lint && npm run unit && npm run test:typescript",
|
|
19
|
-
"test:ci": "npm run unit -- --cov --coverage-report=lcovonly && npm run test:typescript",
|
|
19
|
+
"test:ci": "npm run unit -- -R terse --cov --coverage-report=lcovonly && npm run test:typescript",
|
|
20
20
|
"test:report": "npm run lint && npm run unit:report && npm run test:typescript",
|
|
21
21
|
"test:typescript": "tsd",
|
|
22
22
|
"unit": "tap -J test/*.test.js test/*/*.test.js",
|
|
@@ -129,6 +129,7 @@
|
|
|
129
129
|
"@types/pino": "^6.0.1",
|
|
130
130
|
"@typescript-eslint/eslint-plugin": "^4.5.0",
|
|
131
131
|
"@typescript-eslint/parser": "^4.5.0",
|
|
132
|
+
"JSONStream": "^1.3.5",
|
|
132
133
|
"ajv": "^6.0.0",
|
|
133
134
|
"ajv-errors": "^1.0.1",
|
|
134
135
|
"ajv-formats": "^2.1.1",
|
|
@@ -157,7 +158,6 @@
|
|
|
157
158
|
"hsts": "^2.2.0",
|
|
158
159
|
"http-errors": "^2.0.0",
|
|
159
160
|
"ienoopen": "^1.1.0",
|
|
160
|
-
"JSONStream": "^1.3.5",
|
|
161
161
|
"license-checker": "^25.0.1",
|
|
162
162
|
"pem": "^1.14.4",
|
|
163
163
|
"proxyquire": "^2.1.3",
|
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
"then-sleep": "^1.0.1",
|
|
174
174
|
"tsd": "^0.19.0",
|
|
175
175
|
"typescript": "^4.0.2",
|
|
176
|
-
"undici": "^3.3.
|
|
176
|
+
"undici": "^3.3.6",
|
|
177
177
|
"x-xss-protection": "^2.0.0",
|
|
178
178
|
"yup": "^0.32.0"
|
|
179
179
|
},
|
package/test/close.test.js
CHANGED
|
@@ -291,3 +291,41 @@ test('Cannot be reopened the closed server has listen callback', async t => {
|
|
|
291
291
|
t.ok(err)
|
|
292
292
|
})
|
|
293
293
|
})
|
|
294
|
+
|
|
295
|
+
test('shutsdown while keep-alive connections are active (non-async)', t => {
|
|
296
|
+
t.plan(5)
|
|
297
|
+
|
|
298
|
+
const timeoutTime = 2 * 60 * 1000
|
|
299
|
+
const fastify = Fastify({ forceCloseConnections: true })
|
|
300
|
+
|
|
301
|
+
fastify.server.setTimeout(timeoutTime)
|
|
302
|
+
fastify.server.keepAliveTimeout = timeoutTime
|
|
303
|
+
|
|
304
|
+
fastify.get('/', (req, reply) => {
|
|
305
|
+
reply.send({ hello: 'world' })
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
fastify.listen(0, (err, address) => {
|
|
309
|
+
t.error(err)
|
|
310
|
+
|
|
311
|
+
const client = new Client(
|
|
312
|
+
'http://localhost:' + fastify.server.address().port,
|
|
313
|
+
{ keepAliveTimeout: 1 * 60 * 1000 }
|
|
314
|
+
)
|
|
315
|
+
client.request({ path: '/', method: 'GET' }, (err, response) => {
|
|
316
|
+
t.error(err)
|
|
317
|
+
t.equal(client.closed, false)
|
|
318
|
+
|
|
319
|
+
fastify.close((err) => {
|
|
320
|
+
t.error(err)
|
|
321
|
+
|
|
322
|
+
// Due to the nature of the way we reap these keep-alive connections,
|
|
323
|
+
// there hasn't been enough time before the server fully closed in order
|
|
324
|
+
// for the client to have seen the socket get destroyed. The mere fact
|
|
325
|
+
// that we have reached this callback is enough indication that the
|
|
326
|
+
// feature being tested works as designed.
|
|
327
|
+
t.equal(client.closed, false)
|
|
328
|
+
})
|
|
329
|
+
})
|
|
330
|
+
})
|
|
331
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const tap = require('tap')
|
|
4
|
+
const noopSet = require('../lib/noop-set')
|
|
5
|
+
|
|
6
|
+
tap.test('does a lot of nothing', async t => {
|
|
7
|
+
const aSet = noopSet()
|
|
8
|
+
t.type(aSet, 'object')
|
|
9
|
+
|
|
10
|
+
const item = {}
|
|
11
|
+
aSet.add(item)
|
|
12
|
+
aSet.add({ another: 'item' })
|
|
13
|
+
aSet.delete(item)
|
|
14
|
+
t.equal(aSet.has(item), true)
|
|
15
|
+
|
|
16
|
+
for (const i of aSet) {
|
|
17
|
+
t.fail('should not have any items', i)
|
|
18
|
+
}
|
|
19
|
+
})
|