fastify 5.9.0 → 5.11.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/PROJECT_CHARTER.md +1 -1
- package/README.md +7 -10
- package/SPONSORS.md +1 -0
- package/build/build-validation.js +2 -2
- package/docs/Guides/Delay-Accepting-Requests.md +1 -1
- package/docs/Guides/Ecosystem.md +12 -9
- package/docs/Guides/Getting-Started.md +10 -10
- package/docs/Guides/Migration-Guide-V4.md +2 -2
- package/docs/Guides/Migration-Guide-V5.md +1 -1
- package/docs/Guides/Plugins-Guide.md +1 -1
- package/docs/Guides/Prototype-Poisoning.md +3 -3
- package/docs/Guides/Serverless.md +6 -13
- package/docs/Guides/Style-Guide.md +1 -1
- package/docs/Reference/Errors.md +6 -0
- package/docs/Reference/Hooks.md +1 -1
- package/docs/Reference/Logging.md +3 -0
- package/docs/Reference/Reply.md +2 -2
- package/docs/Reference/Request.md +13 -13
- package/docs/Reference/Server.md +117 -17
- package/docs/Reference/Type-Providers.md +24 -4
- package/docs/Reference/TypeScript.md +8 -10
- package/docs/Reference/Warnings.md +5 -0
- package/eslint.config.js +1 -1
- package/fastify.d.ts +60 -13
- package/fastify.js +28 -23
- package/lib/content-type-parser.js +1 -11
- package/lib/content-type.js +70 -19
- package/lib/context.js +0 -3
- package/lib/error-handler.js +7 -26
- package/lib/errors.js +17 -0
- package/lib/four-oh-four.js +8 -10
- package/lib/handle-request.js +37 -8
- package/lib/hooks.js +5 -1
- package/lib/log-controller.js +169 -0
- package/lib/logger-factory.js +25 -4
- package/lib/reply.js +35 -44
- package/lib/req-id-gen-factory.js +4 -1
- package/lib/request.js +3 -3
- package/lib/route.js +28 -36
- package/lib/schemas.js +3 -3
- package/lib/symbols.js +1 -1
- package/lib/validation.js +10 -1
- package/lib/warnings.js +19 -1
- package/package.json +4 -4
- package/test/content-type.test.js +51 -4
- package/test/find-route.test.js +35 -0
- package/test/fix-6411.test.js +65 -0
- package/test/genReqId.test.js +24 -0
- package/test/hooks.test.js +71 -0
- package/test/internals/all.test.js +2 -1
- package/test/internals/errors.test.js +21 -1
- package/test/internals/logger.test.js +322 -0
- package/test/internals/reply.test.js +57 -4
- package/test/internals/request.test.js +10 -7
- package/test/logger/logging.test.js +40 -1
- package/test/reply-error.test.js +35 -0
- package/test/rfc-10008.test.js +147 -0
- package/test/route-shorthand.test.js +14 -4
- package/test/schema-serialization.test.js +33 -0
- package/test/stream.4.test.js +2 -2
- package/test/trust-proxy.test.js +49 -30
- package/test/types/errors.tst.ts +2 -0
- package/test/types/route.tst.ts +3 -3
- package/types/content-type-parser.d.ts +26 -6
- package/types/errors.d.ts +3 -0
- package/types/hooks.d.ts +137 -76
- package/types/instance.d.ts +152 -47
- package/types/logger.d.ts +57 -2
- package/types/plugin.d.ts +7 -3
- package/types/register.d.ts +53 -10
- package/types/reply.d.ts +62 -14
- package/types/route.d.ts +68 -34
- package/types/type-provider.d.ts +29 -8
- package/types/utils.d.ts +2 -2
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { describe, test } = require('node:test')
|
|
4
|
+
const Fastify = require('..')
|
|
5
|
+
|
|
6
|
+
describe('RFC 10008', () => {
|
|
7
|
+
test('support adding QUERY method though fastify.route', async (t) => {
|
|
8
|
+
t.plan(2)
|
|
9
|
+
|
|
10
|
+
const fastify = Fastify()
|
|
11
|
+
fastify.route({
|
|
12
|
+
method: 'QUERY',
|
|
13
|
+
url: '/query',
|
|
14
|
+
handler: function (request, reply) {
|
|
15
|
+
reply.send(request.body)
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
await fastify.ready()
|
|
19
|
+
|
|
20
|
+
const res = await fastify.inject({
|
|
21
|
+
method: 'QUERY',
|
|
22
|
+
url: '/query',
|
|
23
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24
|
+
body: JSON.stringify({ hello: 'world' })
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
t.assert.equal(res.statusCode, 200)
|
|
28
|
+
t.assert.equal(res.json().hello, 'world')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('support adding QUERY method though fastify.query', async (t) => {
|
|
32
|
+
t.plan(2)
|
|
33
|
+
|
|
34
|
+
const fastify = Fastify()
|
|
35
|
+
fastify.query('/query', function (request, reply) {
|
|
36
|
+
reply.send(request.body)
|
|
37
|
+
})
|
|
38
|
+
await fastify.ready()
|
|
39
|
+
|
|
40
|
+
const res = await fastify.inject({
|
|
41
|
+
method: 'QUERY',
|
|
42
|
+
url: '/query',
|
|
43
|
+
headers: { 'Content-Type': 'application/json' },
|
|
44
|
+
body: JSON.stringify({ hello: 'world' })
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
t.assert.equal(res.statusCode, 200)
|
|
48
|
+
t.assert.equal(res.json().hello, 'world')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* https://datatracker.ietf.org/doc/html/rfc10008#section-2.1
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
test('should return 400 if Content-Type header is missing for QUERY method', async (t) => {
|
|
56
|
+
// If a request lacks media type information, it is
|
|
57
|
+
// incorrect by definition and needs to fail with a
|
|
58
|
+
// 4xx status code such as 400 (Client Error).
|
|
59
|
+
t.plan(2)
|
|
60
|
+
|
|
61
|
+
const fastify = Fastify()
|
|
62
|
+
fastify.query('/query', function (request, reply) {
|
|
63
|
+
reply.send(request.body)
|
|
64
|
+
})
|
|
65
|
+
await fastify.ready()
|
|
66
|
+
|
|
67
|
+
const res = await fastify.inject({
|
|
68
|
+
method: 'QUERY',
|
|
69
|
+
url: '/query',
|
|
70
|
+
body: JSON.stringify({ hello: 'world' })
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
t.assert.equal(res.statusCode, 400)
|
|
74
|
+
t.assert.equal(res.json().code, 'FST_ERR_ROUTE_MISSING_CONTENT_TYPE')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('should return 400 if invalid content is provided for QUERY method', async (t) => {
|
|
78
|
+
// If a media type is specified but is inconsistent
|
|
79
|
+
// with the actual request content, a 400 (Bad Request)
|
|
80
|
+
// can be returned. That is, a server is not allowed to
|
|
81
|
+
// infer a media type from the request content and then
|
|
82
|
+
// override a missing or "erroneous" value (i.e., "content sniffing").
|
|
83
|
+
t.plan(2)
|
|
84
|
+
|
|
85
|
+
const fastify = Fastify()
|
|
86
|
+
fastify.query('/query', function (request, reply) {
|
|
87
|
+
reply.send(request.body)
|
|
88
|
+
})
|
|
89
|
+
await fastify.ready()
|
|
90
|
+
|
|
91
|
+
const res = await fastify.inject({
|
|
92
|
+
method: 'QUERY',
|
|
93
|
+
url: '/query',
|
|
94
|
+
headers: { 'Content-Type': 'application/json' },
|
|
95
|
+
body: 'it is not a valid json'
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
t.assert.equal(res.statusCode, 400)
|
|
99
|
+
t.assert.equal(res.json().code, 'FST_ERR_CTP_INVALID_JSON_BODY')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('should return 400 if no content is provided for QUERY method', async (t) => {
|
|
103
|
+
// If a media type is specified but is inconsistent
|
|
104
|
+
// with the actual request content, a 400 (Bad Request)
|
|
105
|
+
// can be returned. That is, a server is not allowed to
|
|
106
|
+
// infer a media type from the request content and then
|
|
107
|
+
// override a missing or "erroneous" value (i.e., "content sniffing").
|
|
108
|
+
t.plan(2)
|
|
109
|
+
|
|
110
|
+
const fastify = Fastify()
|
|
111
|
+
fastify.query('/query', function (request, reply) {
|
|
112
|
+
reply.send(request.body)
|
|
113
|
+
})
|
|
114
|
+
await fastify.ready()
|
|
115
|
+
|
|
116
|
+
const res = await fastify.inject({
|
|
117
|
+
method: 'QUERY',
|
|
118
|
+
url: '/query',
|
|
119
|
+
headers: { 'Content-Type': 'application/json' }
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
t.assert.equal(res.statusCode, 400)
|
|
123
|
+
t.assert.equal(res.json().code, 'FST_ERR_ROUTE_MISSING_CONTENT')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('should return 415 if unsupported media type is provided for QUERY method', async (t) => {
|
|
127
|
+
// If a media type is specified but is not supported by the
|
|
128
|
+
// resource, a 415 (Unsupported Media Type) is appropriate.
|
|
129
|
+
t.plan(2)
|
|
130
|
+
|
|
131
|
+
const fastify = Fastify()
|
|
132
|
+
fastify.query('/query', function (request, reply) {
|
|
133
|
+
reply.send(request.body)
|
|
134
|
+
})
|
|
135
|
+
await fastify.ready()
|
|
136
|
+
|
|
137
|
+
const res = await fastify.inject({
|
|
138
|
+
method: 'QUERY',
|
|
139
|
+
url: '/query',
|
|
140
|
+
headers: { 'Content-Type': 'application/ld+json' },
|
|
141
|
+
body: JSON.stringify({ hello: 'world' })
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
t.assert.equal(res.statusCode, 415)
|
|
145
|
+
t.assert.equal(res.json().code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE')
|
|
146
|
+
})
|
|
147
|
+
})
|
|
@@ -21,8 +21,13 @@ describe('route-shorthand', () => {
|
|
|
21
21
|
|
|
22
22
|
const instance = new Client(`http://localhost:${fastify.server.address().port}`)
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
if (method === 'QUERY') {
|
|
25
|
+
const response = await instance.request({ path: '/', method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ hello: 'world' }) })
|
|
26
|
+
t.assert.strictEqual(response.statusCode, 200)
|
|
27
|
+
} else {
|
|
28
|
+
const response = await instance.request({ path: '/', method })
|
|
29
|
+
t.assert.strictEqual(response.statusCode, 200)
|
|
30
|
+
}
|
|
26
31
|
})
|
|
27
32
|
}
|
|
28
33
|
|
|
@@ -41,8 +46,13 @@ describe('route-shorthand', () => {
|
|
|
41
46
|
currentMethod = method
|
|
42
47
|
const instance = new Client(`http://localhost:${fastify.server.address().port}`)
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
if (method === 'QUERY') {
|
|
50
|
+
const response = await instance.request({ path: '/', method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ hello: 'world' }) })
|
|
51
|
+
t.assert.strictEqual(response.statusCode, 200)
|
|
52
|
+
} else {
|
|
53
|
+
const response = await instance.request({ path: '/', method })
|
|
54
|
+
t.assert.strictEqual(response.statusCode, 200)
|
|
55
|
+
}
|
|
46
56
|
}
|
|
47
57
|
})
|
|
48
58
|
})
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { test } = require('node:test')
|
|
4
4
|
const Fastify = require('..')
|
|
5
|
+
const ContentType = require('../lib/content-type')
|
|
5
6
|
const { waitForCb } = require('./toolkit')
|
|
6
7
|
|
|
7
8
|
const echoBody = (req, reply) => { reply.send(req.body) }
|
|
@@ -62,6 +63,38 @@ test('custom serializer options', (t, testDone) => {
|
|
|
62
63
|
})
|
|
63
64
|
})
|
|
64
65
|
|
|
66
|
+
test('reuse parsed content type when selecting response serializer', async t => {
|
|
67
|
+
const fastify = Fastify()
|
|
68
|
+
const contentType = 'application/x-fastify-cache-test+json; version=1'
|
|
69
|
+
|
|
70
|
+
fastify.get('/', {
|
|
71
|
+
schema: {
|
|
72
|
+
response: {
|
|
73
|
+
200: {
|
|
74
|
+
content: {
|
|
75
|
+
'application/x-fastify-cache-test+json': {
|
|
76
|
+
schema: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
hello: { type: 'string' }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}, function (request, reply) {
|
|
88
|
+
reply.type(contentType).send({ hello: 'world', ignored: true })
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const response = await fastify.inject('/')
|
|
92
|
+
const normalizedContentType = response.headers['content-type']
|
|
93
|
+
|
|
94
|
+
t.assert.deepStrictEqual(response.json(), { hello: 'world' })
|
|
95
|
+
t.assert.ok(ContentType.cache.get(normalizedContentType))
|
|
96
|
+
})
|
|
97
|
+
|
|
65
98
|
test('Different content types', (t, testDone) => {
|
|
66
99
|
t.plan(46)
|
|
67
100
|
|
package/test/stream.4.test.js
CHANGED
|
@@ -6,7 +6,7 @@ const JSONStream = require('JSONStream')
|
|
|
6
6
|
const Readable = require('node:stream').Readable
|
|
7
7
|
const split = require('split2')
|
|
8
8
|
const Fastify = require('..')
|
|
9
|
-
const {
|
|
9
|
+
const { kLogController } = require('../lib/symbols.js')
|
|
10
10
|
|
|
11
11
|
test('Destroying streams prematurely should call abort method', (t, testDone) => {
|
|
12
12
|
t.plan(7)
|
|
@@ -87,7 +87,7 @@ test('Destroying streams prematurely, log is disabled', (t, testDone) => {
|
|
|
87
87
|
const http = require('node:http')
|
|
88
88
|
|
|
89
89
|
fastify.get('/', function (request, reply) {
|
|
90
|
-
reply.
|
|
90
|
+
reply.server[kLogController].disableRequestLogging = true
|
|
91
91
|
|
|
92
92
|
let sent = false
|
|
93
93
|
const reallyLongStream = new stream.Readable({
|
package/test/trust-proxy.test.js
CHANGED
|
@@ -6,10 +6,10 @@ const helper = require('./helper')
|
|
|
6
6
|
const Request = require('../lib/request')
|
|
7
7
|
const buildRequest = Request.buildRequest
|
|
8
8
|
|
|
9
|
-
const fetchForwardedRequest = async (fastifyServer, forHeader, path, protoHeader) => {
|
|
9
|
+
const fetchForwardedRequest = async (fastifyServer, forHeader, path, protoHeader, hostHeader = 'fastify.test') => {
|
|
10
10
|
const headers = {
|
|
11
11
|
'X-Forwarded-For': forHeader,
|
|
12
|
-
'X-Forwarded-Host':
|
|
12
|
+
'X-Forwarded-Host': hostHeader
|
|
13
13
|
}
|
|
14
14
|
if (protoHeader) {
|
|
15
15
|
headers['X-Forwarded-Proto'] = protoHeader
|
|
@@ -28,8 +28,10 @@ const testRequestValues = (t, req, options) => {
|
|
|
28
28
|
if (options.host) {
|
|
29
29
|
t.assert.ok(req.host, 'host is defined')
|
|
30
30
|
t.assert.strictEqual(req.host, options.host, 'gets host from x-forwarded-host')
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
}
|
|
32
|
+
if (options.hostname) {
|
|
33
|
+
t.assert.ok(req.hostname, 'hostname is defined')
|
|
34
|
+
t.assert.strictEqual(req.hostname, options.hostname, 'gets hostname from x-forwarded-host')
|
|
33
35
|
}
|
|
34
36
|
if (options.ips) {
|
|
35
37
|
t.assert.deepStrictEqual(req.ips, options.ips, 'gets ips from x-forwarded-for')
|
|
@@ -38,9 +40,8 @@ const testRequestValues = (t, req, options) => {
|
|
|
38
40
|
t.assert.ok(req.protocol, 'protocol is defined')
|
|
39
41
|
t.assert.strictEqual(req.protocol, options.protocol, 'gets protocol from x-forwarded-proto')
|
|
40
42
|
}
|
|
41
|
-
if (options
|
|
42
|
-
t.assert.
|
|
43
|
-
t.assert.strictEqual(req.port, options.port, 'port is taken from x-forwarded-for or host')
|
|
43
|
+
if ('port' in options) {
|
|
44
|
+
t.assert.strictEqual(req.port, options.port, 'port is parsed from x-forwarded-host')
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -50,117 +51,135 @@ before(async function () {
|
|
|
50
51
|
})
|
|
51
52
|
|
|
52
53
|
test('trust proxy, not add properties to node req', async t => {
|
|
53
|
-
t.plan(
|
|
54
|
+
t.plan(11)
|
|
54
55
|
const app = fastify({
|
|
55
56
|
trustProxy: true
|
|
56
57
|
})
|
|
57
58
|
t.after(() => app.close())
|
|
58
59
|
|
|
59
60
|
app.get('/trustproxy', function (req, reply) {
|
|
60
|
-
testRequestValues(t, req, { ip: '1.1.1.1', host: 'fastify.test',
|
|
61
|
+
testRequestValues(t, req, { ip: '1.1.1.1', host: 'fastify.test:1234', hostname: 'fastify.test', port: 1234 })
|
|
61
62
|
reply.code(200).send({ ip: req.ip, host: req.host })
|
|
62
63
|
})
|
|
63
64
|
|
|
64
65
|
app.get('/trustproxychain', function (req, reply) {
|
|
65
|
-
|
|
66
|
+
// x-forwarded-host carries no port, so req.port is null
|
|
67
|
+
testRequestValues(t, req, { ip: '2.2.2.2', ips: [localhost, '1.1.1.1', '2.2.2.2'], port: null })
|
|
66
68
|
reply.code(200).send({ ip: req.ip, host: req.host })
|
|
67
69
|
})
|
|
68
70
|
|
|
69
71
|
const fastifyServer = await app.listen({ port: 0 })
|
|
70
72
|
|
|
71
|
-
await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxy', undefined)
|
|
73
|
+
await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxy', undefined, 'fastify.test:1234')
|
|
72
74
|
await fetchForwardedRequest(fastifyServer, '2.2.2.2, 1.1.1.1', '/trustproxychain', undefined)
|
|
73
75
|
})
|
|
74
76
|
|
|
75
77
|
test('trust proxy chain', async t => {
|
|
76
|
-
t.plan(
|
|
78
|
+
t.plan(7)
|
|
77
79
|
const app = fastify({
|
|
78
80
|
trustProxy: [localhost, '192.168.1.1']
|
|
79
81
|
})
|
|
80
82
|
t.after(() => app.close())
|
|
81
83
|
|
|
82
84
|
app.get('/trustproxychain', function (req, reply) {
|
|
83
|
-
testRequestValues(t, req, { ip: '1.1.1.1', host: 'fastify.test',
|
|
85
|
+
testRequestValues(t, req, { ip: '1.1.1.1', host: 'fastify.test:1234', hostname: 'fastify.test', port: 1234 })
|
|
84
86
|
reply.code(200).send({ ip: req.ip, host: req.host })
|
|
85
87
|
})
|
|
86
88
|
|
|
87
89
|
const fastifyServer = await app.listen({ port: 0 })
|
|
88
|
-
await fetchForwardedRequest(fastifyServer, '192.168.1.1, 1.1.1.1', '/trustproxychain', undefined)
|
|
90
|
+
await fetchForwardedRequest(fastifyServer, '192.168.1.1, 1.1.1.1', '/trustproxychain', undefined, 'fastify.test:1234')
|
|
89
91
|
})
|
|
90
92
|
|
|
91
93
|
test('trust proxy function', async t => {
|
|
92
|
-
t.plan(
|
|
94
|
+
t.plan(7)
|
|
93
95
|
const app = fastify({
|
|
94
96
|
trustProxy: (address) => address === localhost
|
|
95
97
|
})
|
|
96
98
|
t.after(() => app.close())
|
|
97
99
|
|
|
98
100
|
app.get('/trustproxyfunc', function (req, reply) {
|
|
99
|
-
testRequestValues(t, req, { ip: '1.1.1.1', host: 'fastify.test',
|
|
101
|
+
testRequestValues(t, req, { ip: '1.1.1.1', host: 'fastify.test:1234', hostname: 'fastify.test', port: 1234 })
|
|
100
102
|
reply.code(200).send({ ip: req.ip, host: req.host })
|
|
101
103
|
})
|
|
102
104
|
|
|
103
105
|
const fastifyServer = await app.listen({ port: 0 })
|
|
104
|
-
await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxyfunc', undefined)
|
|
106
|
+
await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxyfunc', undefined, 'fastify.test:1234')
|
|
105
107
|
})
|
|
106
108
|
|
|
107
109
|
test('trust proxy number', async t => {
|
|
108
|
-
t.plan(
|
|
110
|
+
t.plan(8)
|
|
109
111
|
const app = fastify({
|
|
110
112
|
trustProxy: 1
|
|
111
113
|
})
|
|
112
114
|
t.after(() => app.close())
|
|
113
115
|
|
|
114
116
|
app.get('/trustproxynumber', function (req, reply) {
|
|
115
|
-
testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'fastify.test',
|
|
117
|
+
testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'fastify.test:1234', hostname: 'fastify.test', port: 1234 })
|
|
116
118
|
reply.code(200).send({ ip: req.ip, host: req.host })
|
|
117
119
|
})
|
|
118
120
|
|
|
119
121
|
const fastifyServer = await app.listen({ port: 0 })
|
|
120
|
-
await fetchForwardedRequest(fastifyServer, '2.2.2.2, 1.1.1.1', '/trustproxynumber', undefined)
|
|
122
|
+
await fetchForwardedRequest(fastifyServer, '2.2.2.2, 1.1.1.1', '/trustproxynumber', undefined, 'fastify.test:1234')
|
|
121
123
|
})
|
|
122
124
|
|
|
123
125
|
test('trust proxy IP addresses', async t => {
|
|
124
|
-
t.plan(
|
|
126
|
+
t.plan(8)
|
|
125
127
|
const app = fastify({
|
|
126
128
|
trustProxy: `${localhost}, 2.2.2.2`
|
|
127
129
|
})
|
|
128
130
|
t.after(() => app.close())
|
|
129
131
|
|
|
130
132
|
app.get('/trustproxyipaddrs', function (req, reply) {
|
|
131
|
-
testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'fastify.test',
|
|
133
|
+
testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'fastify.test:1234', hostname: 'fastify.test', port: 1234 })
|
|
132
134
|
reply.code(200).send({ ip: req.ip, host: req.host })
|
|
133
135
|
})
|
|
134
136
|
|
|
135
137
|
const fastifyServer = await app.listen({ port: 0 })
|
|
136
|
-
await fetchForwardedRequest(fastifyServer, '3.3.3.3, 2.2.2.2, 1.1.1.1', '/trustproxyipaddrs', undefined)
|
|
138
|
+
await fetchForwardedRequest(fastifyServer, '3.3.3.3, 2.2.2.2, 1.1.1.1', '/trustproxyipaddrs', undefined, 'fastify.test:1234')
|
|
137
139
|
})
|
|
138
140
|
|
|
139
141
|
test('trust proxy protocol', async t => {
|
|
140
|
-
t.plan(
|
|
142
|
+
t.plan(27)
|
|
141
143
|
const app = fastify({
|
|
142
144
|
trustProxy: true
|
|
143
145
|
})
|
|
144
146
|
t.after(() => app.close())
|
|
145
147
|
|
|
146
148
|
app.get('/trustproxyprotocol', function (req, reply) {
|
|
147
|
-
testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'lorem', host: 'fastify.test',
|
|
149
|
+
testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'lorem', host: 'fastify.test:1234', hostname: 'fastify.test', port: 1234 })
|
|
148
150
|
reply.code(200).send({ ip: req.ip, host: req.host })
|
|
149
151
|
})
|
|
150
152
|
app.get('/trustproxynoprotocol', function (req, reply) {
|
|
151
|
-
testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'http', host: 'fastify.test',
|
|
153
|
+
testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'http', host: 'fastify.test:1234', hostname: 'fastify.test', port: 1234 })
|
|
152
154
|
reply.code(200).send({ ip: req.ip, host: req.host })
|
|
153
155
|
})
|
|
154
156
|
app.get('/trustproxyprotocols', function (req, reply) {
|
|
155
|
-
testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'dolor', host: 'fastify.test',
|
|
157
|
+
testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'dolor', host: 'fastify.test:1234', hostname: 'fastify.test', port: 1234 })
|
|
156
158
|
reply.code(200).send({ ip: req.ip, host: req.host })
|
|
157
159
|
})
|
|
158
160
|
|
|
159
161
|
const fastifyServer = await app.listen({ port: 0 })
|
|
160
162
|
|
|
161
|
-
await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxyprotocol', 'lorem')
|
|
162
|
-
await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxynoprotocol', undefined)
|
|
163
|
-
await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxyprotocols', 'ipsum, dolor')
|
|
163
|
+
await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxyprotocol', 'lorem', 'fastify.test:1234')
|
|
164
|
+
await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxynoprotocol', undefined, 'fastify.test:1234')
|
|
165
|
+
await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxyprotocols', 'ipsum, dolor', 'fastify.test:1234')
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('trust proxy port is null when x-forwarded-host has no port', async t => {
|
|
169
|
+
t.plan(5)
|
|
170
|
+
const app = fastify({
|
|
171
|
+
trustProxy: true
|
|
172
|
+
})
|
|
173
|
+
t.after(() => app.close())
|
|
174
|
+
|
|
175
|
+
app.get('/trustproxynoport', function (req, reply) {
|
|
176
|
+
// req.port is derived from req.host; a forwarded host without a port yields null
|
|
177
|
+
testRequestValues(t, req, { host: 'fastify.test', hostname: 'fastify.test', port: null })
|
|
178
|
+
reply.code(200).send({ host: req.host, port: req.port })
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
const fastifyServer = await app.listen({ port: 0 })
|
|
182
|
+
await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxynoport', undefined)
|
|
164
183
|
})
|
|
165
184
|
|
|
166
185
|
test('trust proxy ignores forwarded headers from untrusted connections', async t => {
|
package/test/types/errors.tst.ts
CHANGED
|
@@ -77,6 +77,8 @@ expect(errorCodes.FST_ERR_ROUTE_METHOD_NOT_SUPPORTED).type.toBeAssignableTo<Fast
|
|
|
77
77
|
expect(errorCodes.FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED).type.toBeAssignableTo<FastifyErrorConstructor>()
|
|
78
78
|
expect(errorCodes.FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT).type.toBeAssignableTo<FastifyErrorConstructor>()
|
|
79
79
|
expect(errorCodes.FST_ERR_ROUTE_REWRITE_NOT_STR).type.toBeAssignableTo<FastifyErrorConstructor>()
|
|
80
|
+
expect(errorCodes.FST_ERR_ROUTE_MISSING_CONTENT_TYPE).type.toBeAssignableTo<FastifyErrorConstructor>()
|
|
81
|
+
expect(errorCodes.FST_ERR_ROUTE_MISSING_CONTENT).type.toBeAssignableTo<FastifyErrorConstructor>()
|
|
80
82
|
expect(errorCodes.FST_ERR_REOPENED_CLOSE_SERVER).type.toBeAssignableTo<FastifyErrorConstructor>()
|
|
81
83
|
expect(errorCodes.FST_ERR_REOPENED_SERVER).type.toBeAssignableTo<FastifyErrorConstructor>()
|
|
82
84
|
expect(errorCodes.FST_ERR_INSTANCE_ALREADY_LISTENING).type.toBeAssignableTo<FastifyErrorConstructor>()
|
package/test/types/route.tst.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as http from 'node:http'
|
|
2
1
|
import { FastifyError } from '@fastify/error'
|
|
2
|
+
import * as http from 'node:http'
|
|
3
3
|
import { expect } from 'tstyche'
|
|
4
4
|
import fastify, { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from '../../fastify.js'
|
|
5
5
|
import { RequestPayload } from '../../types/hooks.js'
|
|
@@ -75,10 +75,10 @@ fastify().get(
|
|
|
75
75
|
)
|
|
76
76
|
|
|
77
77
|
type LowerCaseHTTPMethods = 'delete' | 'get' | 'head' | 'patch' | 'post' | 'put' |
|
|
78
|
-
'options' | 'propfind' | 'proppatch' | 'mkcol' | 'copy' | 'move' | 'lock' |
|
|
78
|
+
'options' | 'query' | 'propfind' | 'proppatch' | 'mkcol' | 'copy' | 'move' | 'lock' |
|
|
79
79
|
'unlock' | 'trace' | 'search' | 'mkcalendar' | 'report'
|
|
80
80
|
|
|
81
|
-
;['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS', 'PROPFIND',
|
|
81
|
+
;['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS', 'QUERY', 'PROPFIND',
|
|
82
82
|
'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK', 'TRACE', 'SEARCH', 'MKCALENDAR', 'REPORT'
|
|
83
83
|
].forEach(method => {
|
|
84
84
|
// route method
|
|
@@ -16,8 +16,15 @@ export type FastifyBodyParser<
|
|
|
16
16
|
RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
|
|
17
17
|
SchemaCompiler extends FastifySchema = FastifySchema,
|
|
18
18
|
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
|
|
19
|
-
> = ((
|
|
20
|
-
|
|
19
|
+
> = ((
|
|
20
|
+
request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>,
|
|
21
|
+
rawBody: RawBody,
|
|
22
|
+
done: ContentTypeParserDoneFunction
|
|
23
|
+
) => void)
|
|
24
|
+
| ((
|
|
25
|
+
request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>,
|
|
26
|
+
rawBody: RawBody
|
|
27
|
+
) => Promise<any>)
|
|
21
28
|
|
|
22
29
|
/**
|
|
23
30
|
* Content Type Parser method that operates on request content
|
|
@@ -28,8 +35,15 @@ export type FastifyContentTypeParser<
|
|
|
28
35
|
RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
|
|
29
36
|
SchemaCompiler extends FastifySchema = FastifySchema,
|
|
30
37
|
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
|
|
31
|
-
> = ((
|
|
32
|
-
|
|
38
|
+
> = ((
|
|
39
|
+
request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>,
|
|
40
|
+
payload: RawRequest
|
|
41
|
+
) => Promise<any>)
|
|
42
|
+
| ((
|
|
43
|
+
request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>,
|
|
44
|
+
payload: RawRequest,
|
|
45
|
+
done: ContentTypeParserDoneFunction
|
|
46
|
+
) => void)
|
|
33
47
|
|
|
34
48
|
/**
|
|
35
49
|
* Natively, Fastify only supports 'application/json' and 'text/plain' content types. The default charset is utf-8. If you need to support different content types, you can use the addContentTypeParser API. The default JSON and/or plain text parser can be changed.
|
|
@@ -48,7 +62,10 @@ export interface AddContentTypeParser<
|
|
|
48
62
|
},
|
|
49
63
|
parser: FastifyContentTypeParser<RawServer, RawRequest, RouteGeneric, SchemaCompiler, TypeProvider>
|
|
50
64
|
): void;
|
|
51
|
-
(
|
|
65
|
+
(
|
|
66
|
+
contentType: string | string[] | RegExp,
|
|
67
|
+
parser: FastifyContentTypeParser<RawServer, RawRequest, RouteGeneric, SchemaCompiler, TypeProvider>
|
|
68
|
+
): void;
|
|
52
69
|
<parseAs extends string | Buffer>(
|
|
53
70
|
contentType: string | string[] | RegExp,
|
|
54
71
|
opts: {
|
|
@@ -68,7 +85,10 @@ export type ProtoAction = 'error' | 'remove' | 'ignore'
|
|
|
68
85
|
|
|
69
86
|
export type ConstructorAction = 'error' | 'remove' | 'ignore'
|
|
70
87
|
|
|
71
|
-
export type getDefaultJsonParser = (
|
|
88
|
+
export type getDefaultJsonParser = (
|
|
89
|
+
onProtoPoisoning: ProtoAction,
|
|
90
|
+
onConstructorPoisoning: ConstructorAction
|
|
91
|
+
) => FastifyBodyParser<string>
|
|
72
92
|
|
|
73
93
|
export type removeContentTypeParser = (contentType: string | RegExp | (string | RegExp)[]) => void
|
|
74
94
|
|
package/types/errors.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export type FastifyErrorCodes = Record<
|
|
|
40
40
|
'FST_ERR_LOG_INVALID_LOGGER_INSTANCE' |
|
|
41
41
|
'FST_ERR_LOG_INVALID_LOGGER_CONFIG' |
|
|
42
42
|
'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED' |
|
|
43
|
+
'FST_ERR_LOG_INVALID_LOG_CONTROLLER' |
|
|
43
44
|
'FST_ERR_REP_INVALID_PAYLOAD_TYPE' |
|
|
44
45
|
'FST_ERR_REP_RESPONSE_BODY_CONSUMED' |
|
|
45
46
|
'FST_ERR_REP_READABLE_STREAM_LOCKED' |
|
|
@@ -80,6 +81,8 @@ export type FastifyErrorCodes = Record<
|
|
|
80
81
|
'FST_ERR_HANDLER_TIMEOUT' |
|
|
81
82
|
'FST_ERR_ROUTE_HANDLER_TIMEOUT_OPTION_NOT_INT' |
|
|
82
83
|
'FST_ERR_ROUTE_REWRITE_NOT_STR' |
|
|
84
|
+
'FST_ERR_ROUTE_MISSING_CONTENT_TYPE' |
|
|
85
|
+
'FST_ERR_ROUTE_MISSING_CONTENT' |
|
|
83
86
|
'FST_ERR_REOPENED_CLOSE_SERVER' |
|
|
84
87
|
'FST_ERR_REOPENED_SERVER' |
|
|
85
88
|
'FST_ERR_INSTANCE_ALREADY_LISTENING' |
|