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.
Files changed (74) hide show
  1. package/PROJECT_CHARTER.md +1 -1
  2. package/README.md +7 -10
  3. package/SPONSORS.md +1 -0
  4. package/build/build-validation.js +2 -2
  5. package/docs/Guides/Delay-Accepting-Requests.md +1 -1
  6. package/docs/Guides/Ecosystem.md +12 -9
  7. package/docs/Guides/Getting-Started.md +10 -10
  8. package/docs/Guides/Migration-Guide-V4.md +2 -2
  9. package/docs/Guides/Migration-Guide-V5.md +1 -1
  10. package/docs/Guides/Plugins-Guide.md +1 -1
  11. package/docs/Guides/Prototype-Poisoning.md +3 -3
  12. package/docs/Guides/Serverless.md +6 -13
  13. package/docs/Guides/Style-Guide.md +1 -1
  14. package/docs/Reference/Errors.md +6 -0
  15. package/docs/Reference/Hooks.md +1 -1
  16. package/docs/Reference/Logging.md +3 -0
  17. package/docs/Reference/Reply.md +2 -2
  18. package/docs/Reference/Request.md +13 -13
  19. package/docs/Reference/Server.md +117 -17
  20. package/docs/Reference/Type-Providers.md +24 -4
  21. package/docs/Reference/TypeScript.md +8 -10
  22. package/docs/Reference/Warnings.md +5 -0
  23. package/eslint.config.js +1 -1
  24. package/fastify.d.ts +60 -13
  25. package/fastify.js +28 -23
  26. package/lib/content-type-parser.js +1 -11
  27. package/lib/content-type.js +70 -19
  28. package/lib/context.js +0 -3
  29. package/lib/error-handler.js +7 -26
  30. package/lib/errors.js +17 -0
  31. package/lib/four-oh-four.js +8 -10
  32. package/lib/handle-request.js +37 -8
  33. package/lib/hooks.js +5 -1
  34. package/lib/log-controller.js +169 -0
  35. package/lib/logger-factory.js +25 -4
  36. package/lib/reply.js +35 -44
  37. package/lib/req-id-gen-factory.js +4 -1
  38. package/lib/request.js +3 -3
  39. package/lib/route.js +28 -36
  40. package/lib/schemas.js +3 -3
  41. package/lib/symbols.js +1 -1
  42. package/lib/validation.js +10 -1
  43. package/lib/warnings.js +19 -1
  44. package/package.json +4 -4
  45. package/test/content-type.test.js +51 -4
  46. package/test/find-route.test.js +35 -0
  47. package/test/fix-6411.test.js +65 -0
  48. package/test/genReqId.test.js +24 -0
  49. package/test/hooks.test.js +71 -0
  50. package/test/internals/all.test.js +2 -1
  51. package/test/internals/errors.test.js +21 -1
  52. package/test/internals/logger.test.js +322 -0
  53. package/test/internals/reply.test.js +57 -4
  54. package/test/internals/request.test.js +10 -7
  55. package/test/logger/logging.test.js +40 -1
  56. package/test/reply-error.test.js +35 -0
  57. package/test/rfc-10008.test.js +147 -0
  58. package/test/route-shorthand.test.js +14 -4
  59. package/test/schema-serialization.test.js +33 -0
  60. package/test/stream.4.test.js +2 -2
  61. package/test/trust-proxy.test.js +49 -30
  62. package/test/types/errors.tst.ts +2 -0
  63. package/test/types/route.tst.ts +3 -3
  64. package/types/content-type-parser.d.ts +26 -6
  65. package/types/errors.d.ts +3 -0
  66. package/types/hooks.d.ts +137 -76
  67. package/types/instance.d.ts +152 -47
  68. package/types/logger.d.ts +57 -2
  69. package/types/plugin.d.ts +7 -3
  70. package/types/register.d.ts +53 -10
  71. package/types/reply.d.ts +62 -14
  72. package/types/route.d.ts +68 -34
  73. package/types/type-provider.d.ts +29 -8
  74. 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
- const response = await instance.request({ path: '/', method })
25
- t.assert.strictEqual(response.statusCode, 200)
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
- const response = await instance.request({ path: '/', method })
45
- t.assert.strictEqual(response.statusCode, 200)
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
 
@@ -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 { kDisableRequestLogging } = require('../lib/symbols.js')
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.log[kDisableRequestLogging] = true
90
+ reply.server[kLogController].disableRequestLogging = true
91
91
 
92
92
  let sent = false
93
93
  const reallyLongStream = new stream.Readable({
@@ -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': 'fastify.test'
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
- t.assert.ok(req.hostname)
32
- t.assert.strictEqual(req.hostname, options.host, 'gets hostname from x-forwarded-host')
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.port) {
42
- t.assert.ok(req.port, 'port is defined')
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(13)
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', port: app.server.address().port })
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
- testRequestValues(t, req, { ip: '2.2.2.2', ips: [localhost, '1.1.1.1', '2.2.2.2'], port: app.server.address().port })
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(8)
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', port: app.server.address().port })
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(8)
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', port: app.server.address().port })
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(9)
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', port: app.server.address().port })
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(9)
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', port: app.server.address().port })
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(30)
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', port: app.server.address().port })
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', port: app.server.address().port })
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', port: app.server.address().port })
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 => {
@@ -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>()
@@ -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
- > = ((request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>, rawBody: RawBody, done: ContentTypeParserDoneFunction) => void)
20
- | ((request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>, rawBody: RawBody) => Promise<any>)
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
- > = ((request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>, payload: RawRequest) => Promise<any>)
32
- | ((request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>, payload: RawRequest, done: ContentTypeParserDoneFunction) => void)
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
- (contentType: string | string[] | RegExp, parser: FastifyContentTypeParser<RawServer, RawRequest, RouteGeneric, SchemaCompiler, TypeProvider>): void;
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 = (onProtoPoisoning: ProtoAction, onConstructorPoisoning: ConstructorAction) => FastifyBodyParser<string>
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' |