fastify 4.9.2 → 4.10.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/README.md +7 -1
- package/docs/Guides/Ecosystem.md +2 -0
- package/docs/Guides/Plugins-Guide.md +0 -18
- package/docs/Guides/Recommendations.md +51 -0
- package/docs/Guides/Write-Type-Provider.md +32 -0
- package/docs/Reference/Decorators.md +7 -1
- package/docs/Reference/Reply.md +15 -7
- package/docs/Reference/Request.md +20 -1
- package/docs/Reference/Server.md +8 -1
- package/docs/Reference/TypeScript.md +1 -1
- package/fastify.d.ts +1 -0
- package/fastify.js +10 -5
- package/lib/contentTypeParser.js +6 -3
- package/lib/context.js +4 -0
- package/lib/errors.js +6 -0
- package/lib/hooks.js +1 -1
- package/lib/reply.js +50 -7
- package/lib/request.js +19 -0
- package/lib/route.js +9 -3
- package/lib/warnings.js +2 -0
- package/package.json +25 -26
- package/test/bodyLimit.test.js +79 -0
- package/test/close-pipelining.test.js +67 -42
- package/test/close.test.js +42 -1
- package/test/hooks-async.test.js +6 -2
- package/test/hooks.on-ready.test.js +2 -1
- package/test/hooks.test.js +20 -4
- package/test/input-validation.js +0 -1
- package/test/internals/hooks.test.js +1 -1
- package/test/reply-trailers.test.js +207 -18
- package/test/request-error.test.js +96 -0
- package/test/types/fastify.test-d.ts +8 -1
- package/test/types/logger.test-d.ts +0 -32
- package/test/types/request.test-d.ts +2 -1
- package/test/types/type-provider.test-d.ts +317 -2
- package/types/request.d.ts +12 -0
- package/types/type-provider.d.ts +5 -3
package/test/bodyLimit.test.js
CHANGED
|
@@ -44,3 +44,82 @@ test('bodyLimit', t => {
|
|
|
44
44
|
})
|
|
45
45
|
})
|
|
46
46
|
})
|
|
47
|
+
|
|
48
|
+
test('default request.routeOptions.bodyLimit should be 1048576', t => {
|
|
49
|
+
t.plan(4)
|
|
50
|
+
const fastify = Fastify()
|
|
51
|
+
fastify.post('/default-bodylimit', {
|
|
52
|
+
handler (request, reply) {
|
|
53
|
+
t.equal(1048576, request.routeOptions.bodyLimit)
|
|
54
|
+
reply.send({ })
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
fastify.listen({ port: 0 }, function (err) {
|
|
58
|
+
t.error(err)
|
|
59
|
+
t.teardown(() => { fastify.close() })
|
|
60
|
+
|
|
61
|
+
sget({
|
|
62
|
+
method: 'POST',
|
|
63
|
+
url: 'http://localhost:' + fastify.server.address().port + '/default-bodylimit',
|
|
64
|
+
headers: { 'Content-Type': 'application/json' },
|
|
65
|
+
body: [],
|
|
66
|
+
json: true
|
|
67
|
+
}, (err, response, body) => {
|
|
68
|
+
t.error(err)
|
|
69
|
+
t.equal(response.statusCode, 200)
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('request.routeOptions.bodyLimit should be equal to route limit', t => {
|
|
75
|
+
t.plan(4)
|
|
76
|
+
const fastify = Fastify({ bodyLimit: 1 })
|
|
77
|
+
fastify.post('/route-limit', {
|
|
78
|
+
bodyLimit: 1000,
|
|
79
|
+
handler (request, reply) {
|
|
80
|
+
t.equal(1000, request.routeOptions.bodyLimit)
|
|
81
|
+
reply.send({})
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
fastify.listen({ port: 0 }, function (err) {
|
|
85
|
+
t.error(err)
|
|
86
|
+
t.teardown(() => { fastify.close() })
|
|
87
|
+
|
|
88
|
+
sget({
|
|
89
|
+
method: 'POST',
|
|
90
|
+
url: 'http://localhost:' + fastify.server.address().port + '/route-limit',
|
|
91
|
+
headers: { 'Content-Type': 'application/json' },
|
|
92
|
+
body: [],
|
|
93
|
+
json: true
|
|
94
|
+
}, (err, response, body) => {
|
|
95
|
+
t.error(err)
|
|
96
|
+
t.equal(response.statusCode, 200)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('request.routeOptions.bodyLimit should be equal to server limit', t => {
|
|
102
|
+
t.plan(4)
|
|
103
|
+
const fastify = Fastify({ bodyLimit: 100 })
|
|
104
|
+
fastify.post('/server-limit', {
|
|
105
|
+
handler (request, reply) {
|
|
106
|
+
t.equal(100, request.routeOptions.bodyLimit)
|
|
107
|
+
reply.send({})
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
fastify.listen({ port: 0 }, function (err) {
|
|
111
|
+
t.error(err)
|
|
112
|
+
t.teardown(() => { fastify.close() })
|
|
113
|
+
|
|
114
|
+
sget({
|
|
115
|
+
method: 'POST',
|
|
116
|
+
url: 'http://localhost:' + fastify.server.address().port + '/server-limit',
|
|
117
|
+
headers: { 'Content-Type': 'application/json' },
|
|
118
|
+
body: [],
|
|
119
|
+
json: true
|
|
120
|
+
}, (err, response, body) => {
|
|
121
|
+
t.error(err)
|
|
122
|
+
t.equal(response.statusCode, 200)
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
})
|
|
@@ -4,42 +4,72 @@ const t = require('tap')
|
|
|
4
4
|
const test = t.test
|
|
5
5
|
const Fastify = require('..')
|
|
6
6
|
const { Client } = require('undici')
|
|
7
|
+
const semver = require('semver')
|
|
7
8
|
|
|
8
|
-
test('Should return 503 while closing - pipelining', t => {
|
|
9
|
+
test('Should return 503 while closing - pipelining', async t => {
|
|
9
10
|
const fastify = Fastify({
|
|
10
11
|
return503OnClosing: true,
|
|
11
12
|
forceCloseConnections: false
|
|
12
13
|
})
|
|
13
14
|
|
|
15
|
+
fastify.get('/', async (req, reply) => {
|
|
16
|
+
fastify.close()
|
|
17
|
+
return { hello: 'world' }
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
await fastify.listen({ port: 0 })
|
|
21
|
+
|
|
22
|
+
const instance = new Client('http://localhost:' + fastify.server.address().port, {
|
|
23
|
+
pipelining: 2
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const codes = [200, 200, 503]
|
|
27
|
+
const responses = await Promise.all([
|
|
28
|
+
instance.request({ path: '/', method: 'GET' }),
|
|
29
|
+
instance.request({ path: '/', method: 'GET' }),
|
|
30
|
+
instance.request({ path: '/', method: 'GET' })
|
|
31
|
+
])
|
|
32
|
+
const actual = responses.map(r => r.statusCode)
|
|
33
|
+
|
|
34
|
+
t.same(actual, codes)
|
|
35
|
+
|
|
36
|
+
await instance.close()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const isV19plus = semver.satisfies(process.version, '>= v19.0.0')
|
|
40
|
+
test('Should not return 503 while closing - pipelining - return503OnClosing: false, skip Node >= v19.x', { skip: isV19plus }, async t => {
|
|
41
|
+
const fastify = Fastify({
|
|
42
|
+
return503OnClosing: false,
|
|
43
|
+
forceCloseConnections: false
|
|
44
|
+
})
|
|
45
|
+
|
|
14
46
|
fastify.get('/', (req, reply) => {
|
|
15
47
|
fastify.close()
|
|
16
48
|
reply.send({ hello: 'world' })
|
|
17
49
|
})
|
|
18
50
|
|
|
19
|
-
fastify.listen({ port: 0 }
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
pipelining: 1
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
const codes = [200, 503]
|
|
27
|
-
for (const code of codes) {
|
|
28
|
-
instance.request(
|
|
29
|
-
{ path: '/', method: 'GET' }
|
|
30
|
-
).then(data => {
|
|
31
|
-
t.equal(data.statusCode, code)
|
|
32
|
-
}).catch((e) => {
|
|
33
|
-
t.fail(e)
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
instance.close(() => {
|
|
37
|
-
t.end('Done')
|
|
38
|
-
})
|
|
51
|
+
await fastify.listen({ port: 0 })
|
|
52
|
+
|
|
53
|
+
const instance = new Client('http://localhost:' + fastify.server.address().port, {
|
|
54
|
+
pipelining: 2
|
|
39
55
|
})
|
|
56
|
+
|
|
57
|
+
const codes = [200, 200, 200]
|
|
58
|
+
const responses = await Promise.all([
|
|
59
|
+
instance.request({ path: '/', method: 'GET' }),
|
|
60
|
+
instance.request({ path: '/', method: 'GET' }),
|
|
61
|
+
instance.request({ path: '/', method: 'GET' })
|
|
62
|
+
])
|
|
63
|
+
const actual = responses.map(r => r.statusCode)
|
|
64
|
+
|
|
65
|
+
t.same(actual, codes)
|
|
66
|
+
|
|
67
|
+
await instance.close()
|
|
40
68
|
})
|
|
41
69
|
|
|
42
|
-
test('Should
|
|
70
|
+
test('Should close the socket abruptly - pipelining - return503OnClosing: false, skip Node < v19.x', { skip: !isV19plus }, async t => {
|
|
71
|
+
// Since Node v19, we will always invoke server.closeIdleConnections()
|
|
72
|
+
// therefore our socket will be closed
|
|
43
73
|
const fastify = Fastify({
|
|
44
74
|
return503OnClosing: false,
|
|
45
75
|
forceCloseConnections: false
|
|
@@ -50,25 +80,20 @@ test('Should not return 503 while closing - pipelining - return503OnClosing', t
|
|
|
50
80
|
reply.send({ hello: 'world' })
|
|
51
81
|
})
|
|
52
82
|
|
|
53
|
-
fastify.listen({ port: 0 }
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
pipelining: 1
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
const codes = [200, 200]
|
|
61
|
-
for (const code of codes) {
|
|
62
|
-
instance.request(
|
|
63
|
-
{ path: '/', method: 'GET' }
|
|
64
|
-
).then(data => {
|
|
65
|
-
t.equal(data.statusCode, code)
|
|
66
|
-
}).catch((e) => {
|
|
67
|
-
t.fail(e)
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
instance.close(() => {
|
|
71
|
-
t.end('Done')
|
|
72
|
-
})
|
|
83
|
+
await fastify.listen({ port: 0 })
|
|
84
|
+
|
|
85
|
+
const instance = new Client('http://localhost:' + fastify.server.address().port, {
|
|
86
|
+
pipelining: 2
|
|
73
87
|
})
|
|
88
|
+
|
|
89
|
+
const responses = await Promise.allSettled([
|
|
90
|
+
instance.request({ path: '/', method: 'GET' }),
|
|
91
|
+
instance.request({ path: '/', method: 'GET' }),
|
|
92
|
+
instance.request({ path: '/', method: 'GET' })
|
|
93
|
+
])
|
|
94
|
+
t.equal(responses[0].status, 'fulfilled')
|
|
95
|
+
t.equal(responses[1].status, 'rejected')
|
|
96
|
+
t.equal(responses[2].status, 'rejected')
|
|
97
|
+
|
|
98
|
+
await instance.close()
|
|
74
99
|
})
|
package/test/close.test.js
CHANGED
|
@@ -6,6 +6,7 @@ const t = require('tap')
|
|
|
6
6
|
const test = t.test
|
|
7
7
|
const Fastify = require('..')
|
|
8
8
|
const { Client } = require('undici')
|
|
9
|
+
const semver = require('semver')
|
|
9
10
|
|
|
10
11
|
test('close callback', t => {
|
|
11
12
|
t.plan(4)
|
|
@@ -202,7 +203,8 @@ test('Should return error while closing (callback) - injection', t => {
|
|
|
202
203
|
})
|
|
203
204
|
})
|
|
204
205
|
|
|
205
|
-
|
|
206
|
+
const isV19plus = semver.satisfies(process.version, '>= v19.0.0')
|
|
207
|
+
t.test('Current opened connection should continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node >= v19.x', { skip: isV19plus }, t => {
|
|
206
208
|
const fastify = Fastify({
|
|
207
209
|
return503OnClosing: false,
|
|
208
210
|
forceCloseConnections: false
|
|
@@ -240,6 +242,45 @@ t.test('Current opened connection should continue to work after closing and retu
|
|
|
240
242
|
})
|
|
241
243
|
})
|
|
242
244
|
|
|
245
|
+
t.test('Current opened connection should NOT continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node < v19.x', { skip: !isV19plus }, t => {
|
|
246
|
+
t.plan(4)
|
|
247
|
+
const fastify = Fastify({
|
|
248
|
+
return503OnClosing: false,
|
|
249
|
+
forceCloseConnections: false
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
fastify.get('/', (req, reply) => {
|
|
253
|
+
fastify.close()
|
|
254
|
+
reply.send({ hello: 'world' })
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
fastify.listen({ port: 0 }, err => {
|
|
258
|
+
t.error(err)
|
|
259
|
+
|
|
260
|
+
const port = fastify.server.address().port
|
|
261
|
+
const client = net.createConnection({ port }, () => {
|
|
262
|
+
client.write('GET / HTTP/1.1\r\n\r\n')
|
|
263
|
+
|
|
264
|
+
client.on('error', function () {
|
|
265
|
+
// Dependending on the Operating System
|
|
266
|
+
// the socket could error or not.
|
|
267
|
+
// However, it will always be closed.
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
client.on('close', function () {
|
|
271
|
+
t.pass('close')
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
client.once('data', data => {
|
|
275
|
+
t.match(data.toString(), /Connection:\s*keep-alive/i)
|
|
276
|
+
t.match(data.toString(), /200 OK/i)
|
|
277
|
+
|
|
278
|
+
client.write('GET / HTTP/1.1\r\n\r\n')
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
|
|
243
284
|
t.test('Current opened connection should not accept new incoming connections', t => {
|
|
244
285
|
t.plan(3)
|
|
245
286
|
const fastify = Fastify({ forceCloseConnections: false })
|
package/test/hooks-async.test.js
CHANGED
|
@@ -585,33 +585,37 @@ test('preHandler respond with a stream', t => {
|
|
|
585
585
|
|
|
586
586
|
test('Should log a warning if is an async function with `done`', t => {
|
|
587
587
|
t.test('3 arguments', t => {
|
|
588
|
-
t.plan(
|
|
588
|
+
t.plan(2)
|
|
589
589
|
const fastify = Fastify()
|
|
590
590
|
|
|
591
591
|
try {
|
|
592
592
|
fastify.addHook('onRequest', async (req, reply, done) => {})
|
|
593
593
|
} catch (e) {
|
|
594
|
+
t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
|
|
594
595
|
t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
|
|
595
596
|
}
|
|
596
597
|
})
|
|
597
598
|
|
|
598
599
|
t.test('4 arguments', t => {
|
|
599
|
-
t.plan(
|
|
600
|
+
t.plan(6)
|
|
600
601
|
const fastify = Fastify()
|
|
601
602
|
|
|
602
603
|
try {
|
|
603
604
|
fastify.addHook('onSend', async (req, reply, payload, done) => {})
|
|
604
605
|
} catch (e) {
|
|
606
|
+
t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
|
|
605
607
|
t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
|
|
606
608
|
}
|
|
607
609
|
try {
|
|
608
610
|
fastify.addHook('preSerialization', async (req, reply, payload, done) => {})
|
|
609
611
|
} catch (e) {
|
|
612
|
+
t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
|
|
610
613
|
t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
|
|
611
614
|
}
|
|
612
615
|
try {
|
|
613
616
|
fastify.addHook('onError', async (req, reply, payload, done) => {})
|
|
614
617
|
} catch (e) {
|
|
618
|
+
t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
|
|
615
619
|
t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
|
|
616
620
|
}
|
|
617
621
|
})
|
|
@@ -291,12 +291,13 @@ t.test('onReady cannot add lifecycle hooks', t => {
|
|
|
291
291
|
})
|
|
292
292
|
|
|
293
293
|
t.test('onReady throw loading error', t => {
|
|
294
|
-
t.plan(
|
|
294
|
+
t.plan(2)
|
|
295
295
|
const fastify = Fastify()
|
|
296
296
|
|
|
297
297
|
try {
|
|
298
298
|
fastify.addHook('onReady', async function (done) {})
|
|
299
299
|
} catch (e) {
|
|
300
|
+
t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
|
|
300
301
|
t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
|
|
301
302
|
}
|
|
302
303
|
})
|
package/test/hooks.test.js
CHANGED
|
@@ -23,7 +23,7 @@ function getUrl (app) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
test('hooks', t => {
|
|
26
|
-
t.plan(
|
|
26
|
+
t.plan(49)
|
|
27
27
|
const fastify = Fastify({ exposeHeadRoutes: false })
|
|
28
28
|
|
|
29
29
|
try {
|
|
@@ -41,6 +41,22 @@ test('hooks', t => {
|
|
|
41
41
|
t.fail()
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
try {
|
|
45
|
+
fastify.addHook('preHandler', null)
|
|
46
|
+
} catch (e) {
|
|
47
|
+
t.equal(e.code, 'FST_ERR_HOOK_INVALID_HANDLER')
|
|
48
|
+
t.equal(e.message, 'preHandler hook should be a function, instead got null')
|
|
49
|
+
t.pass()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
fastify.addHook('preParsing')
|
|
54
|
+
} catch (e) {
|
|
55
|
+
t.equal(e.code, 'FST_ERR_HOOK_INVALID_HANDLER')
|
|
56
|
+
t.equal(e.message, 'preParsing hook should be a function, instead got undefined')
|
|
57
|
+
t.pass()
|
|
58
|
+
}
|
|
59
|
+
|
|
44
60
|
try {
|
|
45
61
|
fastify.addHook('preParsing', function (request, reply, payload, done) {
|
|
46
62
|
request.preParsing = true
|
|
@@ -3316,7 +3332,7 @@ test('registering invalid hooks should throw an error', async t => {
|
|
|
3316
3332
|
return 'hello world'
|
|
3317
3333
|
}
|
|
3318
3334
|
})
|
|
3319
|
-
}, new Error('onRequest hook should be a function, instead got
|
|
3335
|
+
}, new Error('onRequest hook should be a function, instead got [object Undefined]'))
|
|
3320
3336
|
|
|
3321
3337
|
t.throws(() => {
|
|
3322
3338
|
fastify.route({
|
|
@@ -3327,7 +3343,7 @@ test('registering invalid hooks should throw an error', async t => {
|
|
|
3327
3343
|
return 'hello world'
|
|
3328
3344
|
}
|
|
3329
3345
|
})
|
|
3330
|
-
}, new Error('onRequest hook should be a function, instead got object'))
|
|
3346
|
+
}, new Error('onRequest hook should be a function, instead got [object Null]'))
|
|
3331
3347
|
|
|
3332
3348
|
// undefined is ok
|
|
3333
3349
|
fastify.route({
|
|
@@ -3347,5 +3363,5 @@ test('registering invalid hooks should throw an error', async t => {
|
|
|
3347
3363
|
fastify.get('/', function (request, reply) {
|
|
3348
3364
|
reply.send('hello world')
|
|
3349
3365
|
})
|
|
3350
|
-
}, new Error('onSend hook should be a function, instead got
|
|
3366
|
+
}, new Error('onSend hook should be a function, instead got [object Undefined]'))
|
|
3351
3367
|
})
|
package/test/input-validation.js
CHANGED
|
@@ -78,6 +78,6 @@ test('should throw on wrong parameters', t => {
|
|
|
78
78
|
t.fail()
|
|
79
79
|
} catch (e) {
|
|
80
80
|
t.equal(e.code, 'FST_ERR_HOOK_INVALID_HANDLER')
|
|
81
|
-
t.equal(e.message, 'onSend hook should be a function, instead got object')
|
|
81
|
+
t.equal(e.message, 'onSend hook should be a function, instead got [object Null]')
|
|
82
82
|
}
|
|
83
83
|
})
|