fastify 5.7.3 → 5.8.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/LICENSE +1 -1
- package/README.md +1 -1
- package/SECURITY.md +20 -20
- package/build/build-validation.js +2 -0
- package/docs/Guides/Ecosystem.md +7 -59
- package/docs/Guides/Fluent-Schema.md +2 -1
- package/docs/Guides/Migration-Guide-V4.md +9 -8
- package/docs/Guides/Migration-Guide-V5.md +1 -1
- package/docs/Guides/Serverless.md +1 -1
- package/docs/Reference/ContentTypeParser.md +2 -1
- package/docs/Reference/Decorators.md +4 -2
- package/docs/Reference/Errors.md +5 -0
- package/docs/Reference/Hooks.md +85 -23
- package/docs/Reference/LTS.md +2 -2
- package/docs/Reference/Lifecycle.md +16 -1
- package/docs/Reference/Logging.md +11 -7
- package/docs/Reference/Middleware.md +3 -2
- package/docs/Reference/Reply.md +15 -8
- package/docs/Reference/Request.md +17 -1
- package/docs/Reference/Routes.md +15 -6
- package/docs/Reference/Server.md +135 -14
- package/docs/Reference/Type-Providers.md +5 -5
- package/docs/Reference/TypeScript.md +14 -10
- package/docs/Reference/Validation-and-Serialization.md +28 -1
- package/fastify.d.ts +1 -0
- package/fastify.js +4 -2
- package/lib/config-validator.js +324 -296
- package/lib/content-type-parser.js +1 -1
- package/lib/context.js +5 -2
- package/lib/errors.js +12 -1
- package/lib/reply.js +23 -1
- package/lib/request.js +18 -1
- package/lib/route.js +45 -4
- package/lib/symbols.js +4 -0
- package/package.json +5 -5
- package/scripts/validate-ecosystem-links.js +179 -0
- package/test/content-parser.test.js +25 -1
- package/test/handler-timeout.test.js +367 -0
- package/test/internals/errors.test.js +2 -2
- package/test/internals/initial-config.test.js +2 -0
- package/test/request-error.test.js +41 -0
- package/test/router-options.test.js +42 -0
- package/test/scripts/validate-ecosystem-links.test.js +339 -0
- package/test/types/dummy-plugin.ts +2 -2
- package/test/types/fastify.test-d.ts +1 -0
- package/test/types/logger.test-d.ts +17 -18
- package/test/types/register.test-d.ts +2 -2
- package/test/types/reply.test-d.ts +2 -2
- package/test/types/request.test-d.ts +4 -3
- package/test/types/route.test-d.ts +6 -0
- package/test/types/type-provider.test-d.ts +1 -1
- package/test/web-api.test.js +75 -0
- package/types/errors.d.ts +2 -0
- package/types/logger.d.ts +1 -1
- package/types/request.d.ts +2 -0
- package/types/route.d.ts +35 -21
- package/types/tsconfig.eslint.json +0 -13
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test')
|
|
4
|
+
const net = require('node:net')
|
|
5
|
+
const Fastify = require('..')
|
|
6
|
+
const { Readable } = require('node:stream')
|
|
7
|
+
const { kTimeoutTimer, kOnAbort } = require('../lib/symbols')
|
|
8
|
+
|
|
9
|
+
// --- Option validation ---
|
|
10
|
+
|
|
11
|
+
test('server-level handlerTimeout defaults to 0 in initialConfig', t => {
|
|
12
|
+
t.plan(1)
|
|
13
|
+
const fastify = Fastify()
|
|
14
|
+
t.assert.strictEqual(fastify.initialConfig.handlerTimeout, 0)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('server-level handlerTimeout: 5000 is accepted and exposed in initialConfig', t => {
|
|
18
|
+
t.plan(1)
|
|
19
|
+
const fastify = Fastify({ handlerTimeout: 5000 })
|
|
20
|
+
t.assert.strictEqual(fastify.initialConfig.handlerTimeout, 5000)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('route-level handlerTimeout rejects invalid values', async t => {
|
|
24
|
+
const fastify = Fastify()
|
|
25
|
+
|
|
26
|
+
t.assert.throws(() => {
|
|
27
|
+
fastify.get('/a', { handlerTimeout: 'fast' }, async () => 'ok')
|
|
28
|
+
}, { code: 'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' })
|
|
29
|
+
|
|
30
|
+
t.assert.throws(() => {
|
|
31
|
+
fastify.get('/b', { handlerTimeout: -1 }, async () => 'ok')
|
|
32
|
+
}, { code: 'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' })
|
|
33
|
+
|
|
34
|
+
t.assert.throws(() => {
|
|
35
|
+
fastify.get('/c', { handlerTimeout: 1.5 }, async () => 'ok')
|
|
36
|
+
}, { code: 'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' })
|
|
37
|
+
|
|
38
|
+
t.assert.throws(() => {
|
|
39
|
+
fastify.get('/d', { handlerTimeout: 0 }, async () => 'ok')
|
|
40
|
+
}, { code: 'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' })
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// --- Lazy signal without handlerTimeout ---
|
|
44
|
+
|
|
45
|
+
test('when handlerTimeout is 0 (default), request.signal is lazily created', async t => {
|
|
46
|
+
t.plan(3)
|
|
47
|
+
const fastify = Fastify()
|
|
48
|
+
|
|
49
|
+
fastify.get('/', async (request) => {
|
|
50
|
+
const signal = request.signal
|
|
51
|
+
t.assert.ok(signal instanceof AbortSignal)
|
|
52
|
+
t.assert.strictEqual(signal.aborted, false)
|
|
53
|
+
return { ok: true }
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const res = await fastify.inject({ method: 'GET', url: '/' })
|
|
57
|
+
t.assert.strictEqual(res.statusCode, 200)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('client disconnect aborts lazily created signal (no handlerTimeout)', async t => {
|
|
61
|
+
t.plan(1)
|
|
62
|
+
|
|
63
|
+
const fastify = Fastify()
|
|
64
|
+
let signalAborted = false
|
|
65
|
+
|
|
66
|
+
fastify.get('/', async (request) => {
|
|
67
|
+
await new Promise((resolve) => {
|
|
68
|
+
request.signal.addEventListener('abort', () => {
|
|
69
|
+
signalAborted = true
|
|
70
|
+
resolve()
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
return 'should not reach'
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
await fastify.listen({ port: 0 })
|
|
77
|
+
t.after(() => fastify.close())
|
|
78
|
+
|
|
79
|
+
const address = fastify.server.address()
|
|
80
|
+
await new Promise((resolve) => {
|
|
81
|
+
const client = net.connect(address.port, () => {
|
|
82
|
+
client.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
|
83
|
+
setTimeout(() => {
|
|
84
|
+
client.destroy()
|
|
85
|
+
setTimeout(resolve, 100)
|
|
86
|
+
}, 50)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
t.assert.strictEqual(signalAborted, true)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// --- Basic timeout behavior ---
|
|
94
|
+
|
|
95
|
+
test('slow handler returns 503 with FST_ERR_HANDLER_TIMEOUT', async t => {
|
|
96
|
+
t.plan(2)
|
|
97
|
+
const fastify = Fastify()
|
|
98
|
+
|
|
99
|
+
fastify.get('/', { handlerTimeout: 50 }, async () => {
|
|
100
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
101
|
+
return 'too late'
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const res = await fastify.inject({ method: 'GET', url: '/' })
|
|
105
|
+
t.assert.strictEqual(res.statusCode, 503)
|
|
106
|
+
t.assert.strictEqual(JSON.parse(res.payload).code, 'FST_ERR_HANDLER_TIMEOUT')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('fast handler completes normally with 200', async t => {
|
|
110
|
+
t.plan(2)
|
|
111
|
+
const fastify = Fastify()
|
|
112
|
+
|
|
113
|
+
fastify.get('/', { handlerTimeout: 5000 }, async () => {
|
|
114
|
+
return { hello: 'world' }
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const res = await fastify.inject({ method: 'GET', url: '/' })
|
|
118
|
+
t.assert.strictEqual(res.statusCode, 200)
|
|
119
|
+
t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' })
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// --- Per-route override ---
|
|
123
|
+
|
|
124
|
+
test('route-level handlerTimeout overrides server default', async t => {
|
|
125
|
+
t.plan(4)
|
|
126
|
+
const fastify = Fastify({ handlerTimeout: 5000 })
|
|
127
|
+
|
|
128
|
+
fastify.get('/slow', { handlerTimeout: 50 }, async () => {
|
|
129
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
130
|
+
return 'too late'
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
fastify.get('/fast', async () => {
|
|
134
|
+
return { ok: true }
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
const resSlow = await fastify.inject({ method: 'GET', url: '/slow' })
|
|
138
|
+
t.assert.strictEqual(resSlow.statusCode, 503)
|
|
139
|
+
t.assert.strictEqual(JSON.parse(resSlow.payload).code, 'FST_ERR_HANDLER_TIMEOUT')
|
|
140
|
+
|
|
141
|
+
const resFast = await fastify.inject({ method: 'GET', url: '/fast' })
|
|
142
|
+
t.assert.strictEqual(resFast.statusCode, 200)
|
|
143
|
+
t.assert.deepStrictEqual(JSON.parse(resFast.payload), { ok: true })
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// --- request.signal behavior ---
|
|
147
|
+
|
|
148
|
+
test('request.signal is an AbortSignal when handlerTimeout > 0', async t => {
|
|
149
|
+
t.plan(2)
|
|
150
|
+
const fastify = Fastify()
|
|
151
|
+
|
|
152
|
+
fastify.get('/', { handlerTimeout: 5000 }, async (request) => {
|
|
153
|
+
t.assert.ok(request.signal instanceof AbortSignal)
|
|
154
|
+
t.assert.strictEqual(request.signal.aborted, false)
|
|
155
|
+
return 'ok'
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
await fastify.inject({ method: 'GET', url: '/' })
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('request.signal aborts when timeout fires with reason', async t => {
|
|
162
|
+
t.plan(2)
|
|
163
|
+
const fastify = Fastify()
|
|
164
|
+
|
|
165
|
+
let signalReason = null
|
|
166
|
+
fastify.get('/', { handlerTimeout: 50 }, async (request) => {
|
|
167
|
+
request.signal.addEventListener('abort', () => {
|
|
168
|
+
signalReason = request.signal.reason
|
|
169
|
+
})
|
|
170
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
171
|
+
return 'too late'
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
await fastify.inject({ method: 'GET', url: '/' })
|
|
175
|
+
t.assert.ok(signalReason !== null)
|
|
176
|
+
t.assert.strictEqual(signalReason.code, 'FST_ERR_HANDLER_TIMEOUT')
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
// --- Streaming response ---
|
|
180
|
+
|
|
181
|
+
test('streaming response: timer clears when response finishes', async t => {
|
|
182
|
+
t.plan(1)
|
|
183
|
+
|
|
184
|
+
const fastify = Fastify()
|
|
185
|
+
fastify.get('/', { handlerTimeout: 5000 }, async (request, reply) => {
|
|
186
|
+
const stream = new Readable({
|
|
187
|
+
read () {
|
|
188
|
+
this.push('hello')
|
|
189
|
+
this.push(null)
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
reply.type('text/plain').send(stream)
|
|
193
|
+
return reply
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
await fastify.listen({ port: 0 })
|
|
197
|
+
t.after(() => fastify.close())
|
|
198
|
+
|
|
199
|
+
const address = fastify.server.address()
|
|
200
|
+
const res = await fetch(`http://localhost:${address.port}/`)
|
|
201
|
+
t.assert.strictEqual(res.status, 200)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
// --- SSE with reply.hijack() ---
|
|
205
|
+
|
|
206
|
+
test('reply.hijack() clears timeout timer', async t => {
|
|
207
|
+
t.plan(1)
|
|
208
|
+
|
|
209
|
+
const fastify = Fastify()
|
|
210
|
+
fastify.get('/', { handlerTimeout: 100 }, async (request, reply) => {
|
|
211
|
+
reply.hijack()
|
|
212
|
+
// Write after the original timeout would have fired
|
|
213
|
+
await new Promise(resolve => setTimeout(resolve, 200))
|
|
214
|
+
reply.raw.writeHead(200, { 'Content-Type': 'text/plain' })
|
|
215
|
+
reply.raw.end('hijacked response')
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
await fastify.listen({ port: 0 })
|
|
219
|
+
t.after(() => fastify.close())
|
|
220
|
+
|
|
221
|
+
const address = fastify.server.address()
|
|
222
|
+
const res = await fetch(`http://localhost:${address.port}/`)
|
|
223
|
+
t.assert.strictEqual(res.status, 200)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// --- Error handler integration ---
|
|
227
|
+
|
|
228
|
+
test('route-level errorHandler receives FST_ERR_HANDLER_TIMEOUT', async t => {
|
|
229
|
+
t.plan(3)
|
|
230
|
+
const fastify = Fastify()
|
|
231
|
+
|
|
232
|
+
fastify.get('/', {
|
|
233
|
+
handlerTimeout: 50,
|
|
234
|
+
errorHandler: (error, request, reply) => {
|
|
235
|
+
t.assert.strictEqual(error.code, 'FST_ERR_HANDLER_TIMEOUT')
|
|
236
|
+
reply.code(504).send({ custom: 'timeout' })
|
|
237
|
+
}
|
|
238
|
+
}, async () => {
|
|
239
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
240
|
+
return 'too late'
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
const res = await fastify.inject({ method: 'GET', url: '/' })
|
|
244
|
+
t.assert.strictEqual(res.statusCode, 504)
|
|
245
|
+
t.assert.deepStrictEqual(JSON.parse(res.payload), { custom: 'timeout' })
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
// --- Timer cleanup / no leaks ---
|
|
249
|
+
|
|
250
|
+
test('timer is cleaned up after fast response (no leak)', async t => {
|
|
251
|
+
t.plan(3)
|
|
252
|
+
const fastify = Fastify()
|
|
253
|
+
|
|
254
|
+
let capturedRequest
|
|
255
|
+
fastify.get('/', { handlerTimeout: 60000 }, async (request) => {
|
|
256
|
+
capturedRequest = request
|
|
257
|
+
return 'fast'
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
const res = await fastify.inject({ method: 'GET', url: '/' })
|
|
261
|
+
t.assert.strictEqual(res.statusCode, 200)
|
|
262
|
+
// Timer and listener should be cleaned up
|
|
263
|
+
t.assert.strictEqual(capturedRequest[kTimeoutTimer], null)
|
|
264
|
+
t.assert.strictEqual(capturedRequest[kOnAbort], null)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
// --- routeOptions exposure ---
|
|
268
|
+
|
|
269
|
+
test('request.routeOptions.handlerTimeout reflects configured value', async t => {
|
|
270
|
+
t.plan(2)
|
|
271
|
+
const fastify = Fastify()
|
|
272
|
+
|
|
273
|
+
fastify.get('/', { handlerTimeout: 3000 }, async (request) => {
|
|
274
|
+
t.assert.strictEqual(request.routeOptions.handlerTimeout, 3000)
|
|
275
|
+
return 'ok'
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
const res = await fastify.inject({ method: 'GET', url: '/' })
|
|
279
|
+
t.assert.strictEqual(res.statusCode, 200)
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
test('request.routeOptions.handlerTimeout reflects server default', async t => {
|
|
283
|
+
t.plan(2)
|
|
284
|
+
const fastify = Fastify({ handlerTimeout: 7000 })
|
|
285
|
+
|
|
286
|
+
fastify.get('/', async (request) => {
|
|
287
|
+
t.assert.strictEqual(request.routeOptions.handlerTimeout, 7000)
|
|
288
|
+
return 'ok'
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
const res = await fastify.inject({ method: 'GET', url: '/' })
|
|
292
|
+
t.assert.strictEqual(res.statusCode, 200)
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
// --- Client disconnect aborts signal ---
|
|
296
|
+
|
|
297
|
+
test('client disconnect aborts request.signal', async t => {
|
|
298
|
+
t.plan(1)
|
|
299
|
+
|
|
300
|
+
const fastify = Fastify()
|
|
301
|
+
let signalAborted = false
|
|
302
|
+
|
|
303
|
+
fastify.get('/', { handlerTimeout: 5000 }, async (request) => {
|
|
304
|
+
await new Promise((resolve) => {
|
|
305
|
+
request.signal.addEventListener('abort', () => {
|
|
306
|
+
signalAborted = true
|
|
307
|
+
resolve()
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
return 'should not reach'
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
await fastify.listen({ port: 0 })
|
|
314
|
+
t.after(() => fastify.close())
|
|
315
|
+
|
|
316
|
+
const address = fastify.server.address()
|
|
317
|
+
await new Promise((resolve) => {
|
|
318
|
+
const client = net.connect(address.port, () => {
|
|
319
|
+
client.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
|
320
|
+
setTimeout(() => {
|
|
321
|
+
client.destroy()
|
|
322
|
+
// Give the server time to process the close event
|
|
323
|
+
setTimeout(resolve, 100)
|
|
324
|
+
}, 50)
|
|
325
|
+
})
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
t.assert.strictEqual(signalAborted, true)
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
// --- Race: handler completes just as timeout fires ---
|
|
332
|
+
|
|
333
|
+
test('no double-send when handler completes near timeout boundary', async t => {
|
|
334
|
+
t.plan(2)
|
|
335
|
+
const fastify = Fastify()
|
|
336
|
+
|
|
337
|
+
fastify.get('/', { handlerTimeout: 50 }, async (request, reply) => {
|
|
338
|
+
// Respond just before timeout
|
|
339
|
+
await new Promise(resolve => setTimeout(resolve, 40))
|
|
340
|
+
reply.send({ ok: true })
|
|
341
|
+
return reply
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
const res = await fastify.inject({ method: 'GET', url: '/' })
|
|
345
|
+
// Should get either 200 or 503 depending on race, but never crash
|
|
346
|
+
t.assert.ok(res.statusCode === 200 || res.statusCode === 503)
|
|
347
|
+
// Verify response is valid JSON regardless of which won the race
|
|
348
|
+
t.assert.ok(JSON.parse(res.payload))
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
// --- Server default inherited by routes ---
|
|
352
|
+
|
|
353
|
+
test('routes inherit server-level handlerTimeout', async t => {
|
|
354
|
+
t.plan(3)
|
|
355
|
+
const fastify = Fastify({ handlerTimeout: 50 })
|
|
356
|
+
|
|
357
|
+
fastify.get('/', async (request) => {
|
|
358
|
+
// Verify the signal is present (inherited from server default)
|
|
359
|
+
t.assert.ok(request.signal instanceof AbortSignal)
|
|
360
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
361
|
+
return 'too late'
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
const res = await fastify.inject({ method: 'GET', url: '/' })
|
|
365
|
+
t.assert.strictEqual(res.statusCode, 503)
|
|
366
|
+
t.assert.strictEqual(JSON.parse(res.payload).code, 'FST_ERR_HANDLER_TIMEOUT')
|
|
367
|
+
})
|
|
@@ -5,7 +5,7 @@ const errors = require('../../lib/errors')
|
|
|
5
5
|
const { readFileSync } = require('node:fs')
|
|
6
6
|
const { resolve } = require('node:path')
|
|
7
7
|
|
|
8
|
-
const expectedErrors =
|
|
8
|
+
const expectedErrors = 88
|
|
9
9
|
|
|
10
10
|
test(`should expose ${expectedErrors} errors`, t => {
|
|
11
11
|
t.plan(1)
|
|
@@ -165,7 +165,7 @@ test('FST_ERR_CTP_INVALID_MEDIA_TYPE', t => {
|
|
|
165
165
|
const error = new errors.FST_ERR_CTP_INVALID_MEDIA_TYPE()
|
|
166
166
|
t.assert.strictEqual(error.name, 'FastifyError')
|
|
167
167
|
t.assert.strictEqual(error.code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE')
|
|
168
|
-
t.assert.strictEqual(error.message, 'Unsupported Media Type
|
|
168
|
+
t.assert.strictEqual(error.message, 'Unsupported Media Type')
|
|
169
169
|
t.assert.strictEqual(error.statusCode, 415)
|
|
170
170
|
t.assert.ok(error instanceof Error)
|
|
171
171
|
})
|
|
@@ -34,6 +34,7 @@ test('without options passed to Fastify, initialConfig should expose default val
|
|
|
34
34
|
keepAliveTimeout: 72000,
|
|
35
35
|
maxRequestsPerSocket: 0,
|
|
36
36
|
requestTimeout: 0,
|
|
37
|
+
handlerTimeout: 0,
|
|
37
38
|
bodyLimit: 1024 * 1024,
|
|
38
39
|
caseSensitive: true,
|
|
39
40
|
allowUnsafeRegex: false,
|
|
@@ -273,6 +274,7 @@ test('Should not have issues when passing stream options to Pino.js', (t, done)
|
|
|
273
274
|
keepAliveTimeout: 72000,
|
|
274
275
|
maxRequestsPerSocket: 0,
|
|
275
276
|
requestTimeout: 0,
|
|
277
|
+
handlerTimeout: 0,
|
|
276
278
|
bodyLimit: 1024 * 1024,
|
|
277
279
|
caseSensitive: true,
|
|
278
280
|
allowUnsafeRegex: false,
|
|
@@ -325,6 +325,47 @@ test('default clientError replies with bad request on reused keep-alive connecti
|
|
|
325
325
|
})
|
|
326
326
|
})
|
|
327
327
|
|
|
328
|
+
test('non-numeric content-length is rejected before Fastify body parsing', (t, done) => {
|
|
329
|
+
t.plan(3)
|
|
330
|
+
|
|
331
|
+
let response = ''
|
|
332
|
+
|
|
333
|
+
const fastify = Fastify({
|
|
334
|
+
bodyLimit: 1,
|
|
335
|
+
keepAliveTimeout: 100
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
fastify.post('/', () => {
|
|
339
|
+
t.assert.fail('handler should not be called')
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
fastify.listen({ port: 0 }, function (err) {
|
|
343
|
+
t.assert.ifError(err)
|
|
344
|
+
t.after(() => fastify.close())
|
|
345
|
+
|
|
346
|
+
const client = connect(fastify.server.address().port)
|
|
347
|
+
|
|
348
|
+
client.on('data', chunk => {
|
|
349
|
+
response += chunk.toString('utf-8')
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
client.on('end', () => {
|
|
353
|
+
t.assert.match(response, /^HTTP\/1.1 400 Bad Request/)
|
|
354
|
+
t.assert.match(response, /"message":"Client Error"/)
|
|
355
|
+
done()
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
client.resume()
|
|
359
|
+
client.write('POST / HTTP/1.1\r\n')
|
|
360
|
+
client.write('Host: example.com\r\n')
|
|
361
|
+
client.write('Content-Type: text/plain\r\n')
|
|
362
|
+
client.write('Content-Length: abc\r\n')
|
|
363
|
+
client.write('Connection: close\r\n')
|
|
364
|
+
client.write('\r\n')
|
|
365
|
+
client.write('x'.repeat(32))
|
|
366
|
+
})
|
|
367
|
+
})
|
|
368
|
+
|
|
328
369
|
test('request.routeOptions.method is an uppercase string /1', async t => {
|
|
329
370
|
t.plan(3)
|
|
330
371
|
const fastify = Fastify()
|
|
@@ -1059,8 +1059,50 @@ test('Should support extra find-my-way options', async t => {
|
|
|
1059
1059
|
}
|
|
1060
1060
|
})
|
|
1061
1061
|
|
|
1062
|
+
t.after(() => fastify.close())
|
|
1063
|
+
|
|
1062
1064
|
await fastify.ready()
|
|
1063
1065
|
|
|
1064
1066
|
// Ensure the option is preserved after validation
|
|
1065
1067
|
t.assert.strictEqual(typeof fastify.initialConfig.routerOptions.buildPrettyMeta, 'function')
|
|
1066
1068
|
})
|
|
1069
|
+
|
|
1070
|
+
test('Should allow reusing a routerOptions object across instances', async t => {
|
|
1071
|
+
t.plan(1)
|
|
1072
|
+
|
|
1073
|
+
const options = {
|
|
1074
|
+
routerOptions: {
|
|
1075
|
+
maxParamLength: 2048
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
const app1 = Fastify(options)
|
|
1080
|
+
const app2 = Fastify(options)
|
|
1081
|
+
|
|
1082
|
+
t.after(() => Promise.all([
|
|
1083
|
+
app1.close(),
|
|
1084
|
+
app2.close()
|
|
1085
|
+
]))
|
|
1086
|
+
|
|
1087
|
+
const response = await app2.inject('/not-found')
|
|
1088
|
+
t.assert.strictEqual(response.statusCode, 404)
|
|
1089
|
+
})
|
|
1090
|
+
|
|
1091
|
+
test('Should not mutate user-provided routerOptions object', async t => {
|
|
1092
|
+
t.plan(4)
|
|
1093
|
+
|
|
1094
|
+
const routerOptions = {
|
|
1095
|
+
maxParamLength: 2048
|
|
1096
|
+
}
|
|
1097
|
+
const options = { routerOptions }
|
|
1098
|
+
|
|
1099
|
+
const app = Fastify(options)
|
|
1100
|
+
t.after(() => app.close())
|
|
1101
|
+
|
|
1102
|
+
await app.ready()
|
|
1103
|
+
|
|
1104
|
+
t.assert.deepStrictEqual(Object.keys(routerOptions), ['maxParamLength'])
|
|
1105
|
+
t.assert.strictEqual(routerOptions.maxParamLength, 2048)
|
|
1106
|
+
t.assert.strictEqual(routerOptions.defaultRoute, undefined)
|
|
1107
|
+
t.assert.strictEqual(routerOptions.onBadUrl, undefined)
|
|
1108
|
+
})
|