fastify 4.0.0-rc.2 → 4.0.0-rc.3

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 CHANGED
@@ -8,7 +8,6 @@
8
8
  [![CI](https://github.com/fastify/fastify/workflows/ci/badge.svg)](https://github.com/fastify/fastify/actions/workflows/ci.yml)
9
9
  [![Package Manager CI](https://github.com/fastify/fastify/workflows/package-manager-ci/badge.svg)](https://github.com/fastify/fastify/actions/workflows/package-manager-ci.yml)
10
10
  [![Web SIte](https://github.com/fastify/fastify/workflows/website/badge.svg)](https://github.com/fastify/fastify/actions/workflows/website.yml)
11
- [![Known Vulnerabilities](https://snyk.io/test/github/fastify/fastify/badge.svg)](https://snyk.io/test/github/fastify/fastify)
12
11
  [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
13
12
 
14
13
  </div>
@@ -18,7 +17,7 @@
18
17
  [![NPM version](https://img.shields.io/npm/v/fastify.svg?style=flat)](https://www.npmjs.com/package/fastify)
19
18
  [![NPM downloads](https://img.shields.io/npm/dm/fastify.svg?style=flat)](https://www.npmjs.com/package/fastify)
20
19
  [![Security Responsible
21
- Disclosure](https://img.shields.io/badge/Security-Responsible%20Disclosure-yellow.svg)](https://github.com/nodejs/security-wg/blob/HEAD/processes/responsible_disclosure_template.md)
20
+ Disclosure](https://img.shields.io/badge/Security-Responsible%20Disclosure-yellow.svg)](https://github.com/fastify/fastify/blob/main/SECURITY.md)
22
21
  [![Discord](https://img.shields.io/discord/725613461949906985)](https://discord.gg/fastify)
23
22
 
24
23
  </div>
@@ -282,7 +281,7 @@ Great contributors on a specific area in the Fastify ecosystem will be invited t
282
281
 
283
282
  ## Hosted by
284
283
 
285
- [<img src="https://github.com/openjs-foundation/cross-project-council/blob/HEAD/logos/openjsf-color.png?raw=true" width="250px;"/>](https://openjsf.org/projects/#growth)
284
+ [<img src="https://github.com/openjs-foundation/artwork/blob/main/openjs_foundation/openjs_foundation-logo-horizontal-color.png?raw=true" width="250px;"/>](https://openjsf.org/projects/#growth)
286
285
 
287
286
  We are a [Growth Project](https://github.com/openjs-foundation/cross-project-council/blob/HEAD/PROJECT_PROGRESSION.md#growth-stage) in the [OpenJS Foundation](https://openjsf.org/).
288
287
 
@@ -22,8 +22,6 @@ section.
22
22
  - [`fastify-awilix`](https://github.com/fastify/fastify-awilix) Dependency
23
23
  injection support for Fastify, based on
24
24
  [awilix](https://github.com/jeffijoe/awilix).
25
- - [`@fastify/bankai`](https://github.com/fastify/fastify-bankai)
26
- [Bankai](https://github.com/yoshuawuyts/bankai) assets compiler for Fastify.
27
25
  - [`@fastify/basic-auth`](https://github.com/fastify/fastify-basic-auth) Basic
28
26
  auth plugin for Fastify.
29
27
  - [`@fastify/bearer-auth`](https://github.com/fastify/fastify-bearer-auth) Bearer
@@ -38,7 +36,7 @@ section.
38
36
  cookie headers.
39
37
  - [`@fastify/cors`](https://github.com/fastify/fastify-cors) Enables the use of
40
38
  CORS in a Fastify application.
41
- - [`fastify-csrf`](https://github.com/fastify/fastify-csrf) A plugin for adding
39
+ - [`@fastify/csrf-protection`](https://github.com/fastify/csrf-protection) A plugin for adding
42
40
  [CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery) protection to
43
41
  Fastify.
44
42
  - [`@fastify/diagnostics-channel`](https://github.com/fastify/fastify-diagnostics-channel)
@@ -59,6 +57,7 @@ section.
59
57
  function.
60
58
  - [`@fastify/helmet`](https://github.com/fastify/fastify-helmet) Important
61
59
  security headers for Fastify.
60
+ - [`@fastify/hotwire`](https://github.com/fastify/fastify-hotwire) Use the Hotwire pattern with Fastify.
62
61
  - [`@fastify/http-proxy`](https://github.com/fastify/fastify-http-proxy) Proxy
63
62
  your HTTP requests to another server, with hooks.
64
63
  - [`@fastify/jwt`](https://github.com/fastify/fastify-jwt) JWT utils for Fastify,
@@ -405,6 +404,7 @@ section.
405
404
  - [`fastify-qs`](https://github.com/webdevium/fastify-qs) A plugin for Fastify
406
405
  that adds support for parsing URL query parameters with
407
406
  [qs](https://github.com/ljharb/qs).
407
+ - [`fastify-racing`](https://github.com/metcoder95/fastify-racing) Fastify's plugin that adds support to handle an aborted request asynchronous.
408
408
  - [`fastify-raw-body`](https://github.com/Eomm/fastify-raw-body) Add the
409
409
  `request.rawBody` field.
410
410
  - [`fastify-rbac`](https://gitlab.com/m03geek/fastify-rbac) Fastify role-based
@@ -56,9 +56,6 @@ fastify.listen({ port: 3000 }, function (err, address) {
56
56
 
57
57
  Do you prefer to use `async/await`? Fastify supports it out-of-the-box.
58
58
 
59
- *(We also suggest using
60
- [make-promises-safe](https://github.com/mcollina/make-promises-safe) to avoid
61
- file descriptor and memory leaks.)*
62
59
  ```js
63
60
  // ESM
64
61
  import Fastify from 'fastify'
@@ -74,6 +71,9 @@ fastify.get('/', async (request, reply) => {
74
71
  return { hello: 'world' }
75
72
  })
76
73
 
74
+ /**
75
+ * Run the server!
76
+ */
77
77
  const start = async () => {
78
78
  try {
79
79
  await fastify.listen({ port: 3000 })
@@ -133,6 +133,9 @@ declaration](../Reference/Routes.md) docs).
133
133
  // ESM
134
134
  import Fastify from 'fastify'
135
135
  import firstRoute from './our-first-route'
136
+ /**
137
+ * @type {import('fastify').FastifyInstance} Instance of Fastify
138
+ */
136
139
  const fastify = Fastify({
137
140
  logger: true
138
141
  })
@@ -150,6 +153,9 @@ fastify.listen({ port: 3000 }, function (err, address) {
150
153
 
151
154
  ```js
152
155
  // CommonJs
156
+ /**
157
+ * @type {import('fastify').FastifyInstance} Instance of Fastify
158
+ */
153
159
  const fastify = require('fastify')({
154
160
  logger: true
155
161
  })
@@ -168,6 +174,11 @@ fastify.listen({ port: 3000 }, function (err, address) {
168
174
  ```js
169
175
  // our-first-route.js
170
176
 
177
+ /**
178
+ * Encapsulates the routes
179
+ * @param {FastifyInstance} fastify Encapsulated Fastify Instance
180
+ * @param {Object} options plugin options, refer to https://www.fastify.io/docs/latest/Reference/Plugins/#plugin-options
181
+ */
171
182
  async function routes (fastify, options) {
172
183
  fastify.get('/', async (request, reply) => {
173
184
  return { hello: 'world' }
@@ -208,6 +219,9 @@ import Fastify from 'fastify'
208
219
  import dbConnector from './our-db-connector'
209
220
  import firstRoute from './our-first-route'
210
221
 
222
+ /**
223
+ * @type {import('fastify').FastifyInstance} Instance of Fastify
224
+ */
211
225
  const fastify = Fastify({
212
226
  logger: true
213
227
  })
@@ -225,6 +239,9 @@ fastify.listen({ port: 3000 }, function (err, address) {
225
239
 
226
240
  ```js
227
241
  // CommonJs
242
+ /**
243
+ * @type {import('fastify').FastifyInstance} Instance of Fastify
244
+ */
228
245
  const fastify = require('fastify')({
229
246
  logger: true
230
247
  })
@@ -248,6 +265,10 @@ fastify.listen({ port: 3000 }, function (err, address) {
248
265
  import fastifyPlugin from 'fastify-plugin'
249
266
  import fastifyMongo from '@fastify/mongodb'
250
267
 
268
+ /**
269
+ * @param {FastifyInstance} fastify
270
+ * @param {Object} options
271
+ */
251
272
  async function dbConnector (fastify, options) {
252
273
  fastify.register(fastifyMongo, {
253
274
  url: 'mongodb://localhost:27017/test_database'
@@ -262,8 +283,17 @@ module.exports = fastifyPlugin(dbConnector)
262
283
 
263
284
  ```js
264
285
  // CommonJs
286
+ /**
287
+ * @type {import('fastify-plugin').FastifyPlugin}
288
+ */
265
289
  const fastifyPlugin = require('fastify-plugin')
266
290
 
291
+
292
+ /**
293
+ * Connects to a MongoDB database
294
+ * @param {FastifyInstance} fastify Encapsulated Fastify Instance
295
+ * @param {Object} options plugin options, refer to https://www.fastify.io/docs/latest/Reference/Plugins/#plugin-options
296
+ */
267
297
  async function dbConnector (fastify, options) {
268
298
  fastify.register(require('@fastify/mongodb'), {
269
299
  url: 'mongodb://localhost:27017/test_database'
@@ -278,6 +308,11 @@ module.exports = fastifyPlugin(dbConnector)
278
308
 
279
309
  **our-first-route.js**
280
310
  ```js
311
+ /**
312
+ * A plugin that provide encapsulated routes
313
+ * @param {FastifyInstance} fastify encapsulated fastify instance
314
+ * @param {Object} options plugin options, refer to https://www.fastify.io/docs/latest/Reference/Plugins/#plugin-options
315
+ */
281
316
  async function routes (fastify, options) {
282
317
  const collection = fastify.mongo.db.collection('test_collection')
283
318
 
@@ -403,6 +438,10 @@ Schema](https://json-schema.org/).
403
438
 
404
439
  Let's look at an example demonstrating validation for routes:
405
440
  ```js
441
+ /**
442
+ * @type {import('fastify').RouteShorthandOptions}
443
+ * @const
444
+ */
406
445
  const opts = {
407
446
  schema: {
408
447
  body: {
@@ -435,6 +474,10 @@ JSON bodies and serialize JSON output.
435
474
  To speed up JSON serialization (yes, it is slow!) use the `response` key of the
436
475
  schema option as shown in the following example:
437
476
  ```js
477
+ /**
478
+ * @type {import('fastify').RouteShorthandOptions}
479
+ * @const
480
+ */
438
481
  const opts = {
439
482
  schema: {
440
483
  response: {
@@ -468,6 +511,9 @@ request](../Reference/Request.md) object at `request.body`.
468
511
  The following example returns the parsed body of a request back to the client:
469
512
 
470
513
  ```js
514
+ /**
515
+ * @type {import('fastify').RouteShorthandOptions}
516
+ */
471
517
  const opts = {}
472
518
  fastify.post('/', opts, async (request, reply) => {
473
519
  return request.body
@@ -80,9 +80,9 @@ to show that the plugin works as intended. Both
80
80
  Actions](https://github.com/features/actions) are free for open source projects
81
81
  and easy to set up.
82
82
 
83
- In addition, you can enable services like [Dependabot](https://dependabot.com/)
84
- or [Snyk](https://snyk.io/), which will help you keep your dependencies up to
85
- date and discover if a new release of Fastify has some issues with your plugin.
83
+ In addition, you can enable services like [Dependabot](https://dependabot.com/),
84
+ which will help you keep your dependencies up to date and discover if a new
85
+ release of Fastify has some issues with your plugin.
86
86
 
87
87
  ## Let's start!
88
88
  Awesome, now you know everything you need to know about how to write a good
@@ -9,4 +9,4 @@ All v3 deprecations have been removed and they will no longer work after upgradi
9
9
 
10
10
  ### Deprecation of `app.use()`
11
11
 
12
- Starting this version of Fastify, we have deprecated the use of `app.use()`. We have decided not to support the use of middlewares. Both [`middie`](https://github.com/fastify/middie) and [`@fastify/express`](https://github.com/fastify/fastify-express) will still be there and maintained. Use Fastify's [hooks](./Hooks.md) instead.
12
+ Starting this version of Fastify, we have deprecated the use of `app.use()`. We have decided not to support the use of middlewares. Both [`middie`](https://github.com/fastify/middie) and [`@fastify/express`](https://github.com/fastify/fastify-express) will still be there and maintained. Use Fastify's [hooks](./Reference/Hooks.md) instead.
@@ -17,13 +17,6 @@ way to deal with them is to
17
17
  [crash](https://nodejs.org/api/process.html#process_warning_using_uncaughtexception_correctly).
18
18
 
19
19
  #### Catching Errors In Promises
20
- In Node.js, unhandled promise rejections (that is, without a `.catch()` handler)
21
- can also cause memory and file descriptor leaks. While `unhandledRejection` is
22
- deprecated in Node.js, unhandled rejections will not throw, and still
23
- potentially leak. You should use a module like
24
- [`make-promises-safe`](https://github.com/mcollina/make-promises-safe) to ensure
25
- unhandled rejections _always_ throw.
26
-
27
20
  If you are using promises, you should attach a `.catch()` handler synchronously.
28
21
 
29
22
  ### Errors In Fastify
@@ -9,7 +9,7 @@ By default, `register` creates a *new scope*, this means that if you make some
9
9
  changes to the Fastify instance (via `decorate`), this change will not be
10
10
  reflected by the current context ancestors, but only by its descendants. This
11
11
  feature allows us to achieve plugin *encapsulation* and *inheritance*, in this
12
- way we create a *direct acyclic graph* (DAG) and we will not have issues caused
12
+ way we create a *directed acyclic graph* (DAG) and we will not have issues caused
13
13
  by cross dependencies.
14
14
 
15
15
  You may have already seen in the [Getting Started]((../Guides/Getting-Started.md#your-first-plugin)) guide how easy it is to use this API:
@@ -653,3 +653,24 @@ fastify.route({
653
653
  }
654
654
  })
655
655
  ```
656
+
657
+ ### ⚠ HTTP version check
658
+
659
+ Fastify will check the HTTP version of every request, based on configuration
660
+ options ([http2](./Server.md#http2), [https](./Server.md#https), and
661
+ [serverFactory](./Server.md#serverfactory)), to determine if it matches one or
662
+ all of the > following versions: `2.0`, `1.1`, and `1.0`. If Fastify receives
663
+ a different HTTP version in the request it will return a
664
+ `505 HTTP Version Not Supported` error.
665
+
666
+ | | 2.0 | 1.1 | 1.0 | skip |
667
+ |:------------------------:|:---:|:---:|:---:|:----:|
668
+ | http2 | ✓ | | | |
669
+ | http2 + https | ✓ | | | |
670
+ | http2 + https.allowHTTP1 | ✓ | ✓ | ✓ | |
671
+ | https | | ✓ | ✓ | |
672
+ | http | | ✓ | ✓ | |
673
+ | serverFactory | | | | ✓ |
674
+
675
+ Note: The internal HTTP version check will be removed in the future when Node
676
+ implements [this feature](https://github.com/nodejs/node/issues/43115).
@@ -54,7 +54,7 @@ describes the properties available in that options object.
54
54
  - [routing](#routing)
55
55
  - [route](#route)
56
56
  - [close](#close)
57
- - [decorate*](#decorate)
57
+ - [decorate\*](#decorate)
58
58
  - [register](#register)
59
59
  - [addHook](#addhook)
60
60
  - [prefix](#prefix)
@@ -75,6 +75,8 @@ describes the properties available in that options object.
75
75
  - [schemaController](#schemacontroller)
76
76
  - [setNotFoundHandler](#setnotfoundhandler)
77
77
  - [setErrorHandler](#seterrorhandler)
78
+ - [addConstraintStrategy](#addconstraintstrategy)
79
+ - [hasConstraintStrategy](#hasconstraintstrategy)
78
80
  - [printRoutes](#printroutes)
79
81
  - [printPlugins](#printplugins)
80
82
  - [addContentTypeParser](#addcontenttypeparser)
@@ -463,7 +465,7 @@ Defines the label used for the request identifier when logging the request.
463
465
  <a id="factory-gen-request-id"></a>
464
466
 
465
467
  Function for generating the request-id. It will receive the incoming request as
466
- a parameter.
468
+ a parameter. This function is expected to be error-free.
467
469
 
468
470
  + Default: `value of 'request-id' header if provided or monotonically increasing
469
471
  integers`
@@ -588,12 +590,10 @@ constraint strategies in the
588
590
  ```js
589
591
  const customVersionStrategy = {
590
592
  storage: function () {
591
- let versions = {}
593
+ const versions = {}
592
594
  return {
593
595
  get: (version) => { return versions[version] || null },
594
- set: (version, store) => { versions[version] = store },
595
- del: (version) => { delete versions[version] },
596
- empty: () => { versions = {} }
596
+ set: (version, store) => { versions[version] = store }
597
597
  }
598
598
  },
599
599
  deriveVersion: (req, ctx) => {
@@ -952,6 +952,8 @@ Note that the array contains the `fastify.server.address()` too.
952
952
  #### getDefaultRoute
953
953
  <a id="getDefaultRoute"></a>
954
954
 
955
+ The `defaultRoute` handler handles requests that do not match any URL specified by your Fastify application.
956
+ This defaults to the 404 handler, but can be overridden with [setDefaultRoute](#setdefaultroute).
955
957
  Method to get the `defaultRoute` for the server:
956
958
 
957
959
  ```js
@@ -961,6 +963,8 @@ const defaultRoute = fastify.getDefaultRoute()
961
963
  #### setDefaultRoute
962
964
  <a id="setDefaultRoute"></a>
963
965
 
966
+ **Note**: The default 404 handler, or one set using `setNotFoundHandler`, will never trigger if the default route is overridden.
967
+ Use [setNotFoundHandler](#setnotfoundhandler) if you want to customize 404 handling instead.
964
968
  Method to set the `defaultRoute` for the server:
965
969
 
966
970
  ```js
@@ -1349,6 +1353,42 @@ if (statusCode >= 500) {
1349
1353
  }
1350
1354
  ```
1351
1355
 
1356
+ #### addConstraintStrategy
1357
+ <a id="addConstraintStrategy"></a>
1358
+
1359
+ Function to add a custom constraint strategy. To register a new type of constraint, you must add a new constraint strategy that knows how to match values to handlers, and that knows how to get the constraint value from a request.
1360
+
1361
+ Add a custom constraint strategy using the `fastify.addConstraintStrategy` method:
1362
+
1363
+ ```js
1364
+ const customResponseTypeStrategy = {
1365
+ // strategy name for referencing in the route handler `constraints` options
1366
+ name: 'accept',
1367
+ // storage factory for storing routes in the find-my-way route tree
1368
+ storage: function () {
1369
+ let handlers = {}
1370
+ return {
1371
+ get: (type) => { return handlers[type] || null },
1372
+ set: (type, store) => { handlers[type] = store }
1373
+ }
1374
+ },
1375
+ // function to get the value of the constraint from each incoming request
1376
+ deriveConstraint: (req, ctx) => {
1377
+ return req.headers['accept']
1378
+ },
1379
+ // optional flag marking if handlers without constraints can match requests that have a value for this constraint
1380
+ mustMatchWhenDerived: true
1381
+ }
1382
+
1383
+ const router = Fastify();
1384
+ router.addConstraintStrategy(customResponseTypeStrategy);
1385
+ ```
1386
+
1387
+ #### hasConstraintStrategy
1388
+ <a id="hasConstraintStrategy"></a>
1389
+
1390
+ The `fastify.hasConstraintStrategy(strategyName)` checks if there already exists a custom constraint strategy with the same name.
1391
+
1352
1392
  #### printRoutes
1353
1393
  <a id="print-routes"></a>
1354
1394
 
@@ -183,6 +183,16 @@ Also it has the advantage to use the defined type within your handlers
183
183
 
184
184
  Here are some options how to achieve this.
185
185
 
186
+ #### Fastify Type Providers
187
+
188
+ Fastify offers two packages wrapping `json-schema-to-ts` and `typebox`:
189
+
190
+ - `@fastify/type-provider-json-schema-to-ts`
191
+ - `@fastify/type-provider-typebox`
192
+
193
+ They simplify schema validation setup and you can read more about them in [Type Providers](./Type-Providers.md) page.
194
+
195
+ Below is how to setup schema validation using vanilla `typebox` and `json-schema-to-ts` packages.
186
196
 
187
197
  #### typebox
188
198
 
@@ -330,7 +340,7 @@ into TypeScript interfaces!
330
340
  // or if using async
331
341
  // preValidation: async (request, reply) => {
332
342
  // const { username, password } = request.query
333
- // return username !== "admin" ? new Error("Must be admin") : undefined;
343
+ // if (username !== "admin") throw new Error("Must be admin");
334
344
  // }
335
345
  }, async (request, reply) => {
336
346
  const customerHeader = request.headers['h-Custom']
package/fastify.js CHANGED
@@ -1,10 +1,9 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '4.0.0-rc.2'
3
+ const VERSION = '4.0.0-rc.3'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('http')
7
- const querystring = require('querystring')
8
7
  let lightMyRequest
9
8
 
10
9
  const {
@@ -32,7 +31,7 @@ const {
32
31
  kFourOhFourContext
33
32
  } = require('./lib/symbols.js')
34
33
 
35
- const createServer = require('./lib/server')
34
+ const { createServer, compileValidateHTTPVersion } = require('./lib/server')
36
35
  const Reply = require('./lib/reply')
37
36
  const Request = require('./lib/request')
38
37
  const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS']
@@ -99,7 +98,6 @@ function fastify (options) {
99
98
  validateBodyLimitOption(options.bodyLimit)
100
99
 
101
100
  const requestIdHeader = options.requestIdHeader || defaultInitOptions.requestIdHeader
102
- const querystringParser = options.querystringParser || querystring.parse
103
101
  const genReqId = options.genReqId || reqIdGenFactory()
104
102
  const requestIdLogLabel = options.requestIdLogLabel || 'reqId'
105
103
  const bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit
@@ -131,7 +129,6 @@ function fastify (options) {
131
129
  options.logger = logger
132
130
  options.genReqId = genReqId
133
131
  options.requestIdHeader = requestIdHeader
134
- options.querystringParser = querystringParser
135
132
  options.requestIdLogLabel = requestIdLogLabel
136
133
  options.disableRequestLogging = disableRequestLogging
137
134
  options.ajv = ajvOptions
@@ -140,7 +137,7 @@ function fastify (options) {
140
137
  const initialConfig = getSecuredInitialConfig(options)
141
138
  const keepAliveConnections = options.forceCloseConnections === true ? new Set() : noopSet()
142
139
 
143
- // exposeHeadRoutes have its defult set from the validatorfrom the validatorfrom the validatorfrom the validator
140
+ // exposeHeadRoutes have its default set from the validator
144
141
  options.exposeHeadRoutes = initialConfig.exposeHeadRoutes
145
142
 
146
143
  let constraints = options.constraints
@@ -172,7 +169,8 @@ function fastify (options) {
172
169
  maxParamLength: options.maxParamLength || defaultInitOptions.maxParamLength,
173
170
  caseSensitive: options.caseSensitive,
174
171
  allowUnsafeRegex: options.allowUnsafeRegex || defaultInitOptions.allowUnsafeRegex,
175
- buildPrettyMeta: defaultBuildPrettyMeta
172
+ buildPrettyMeta: defaultBuildPrettyMeta,
173
+ querystringParser: options.querystringParser
176
174
  },
177
175
  keepAliveConnections
178
176
  })
@@ -310,7 +308,10 @@ function fastify (options) {
310
308
  setNotFoundHandler,
311
309
  setErrorHandler,
312
310
  // Set fastify initial configuration options read-only object
313
- initialConfig
311
+ initialConfig,
312
+ // constraint strategies
313
+ addConstraintStrategy: router.addConstraintStrategy.bind(router),
314
+ hasConstraintStrategy: router.hasConstraintStrategy.bind(router)
314
315
  }
315
316
 
316
317
  Object.defineProperties(fastify, {
@@ -408,7 +409,8 @@ function fastify (options) {
408
409
  logger,
409
410
  hasLogger,
410
411
  setupResponseListeners,
411
- throwIfAlreadyStarted
412
+ throwIfAlreadyStarted,
413
+ validateHTTPVersion: compileValidateHTTPVersion(options)
412
414
  })
413
415
 
414
416
  // Delay configuring clientError handler so that it can access fastify state.
@@ -607,7 +609,7 @@ function fastify (options) {
607
609
  if (req.headers['accept-version'] !== undefined) {
608
610
  // we remove the accept-version header for performance result
609
611
  // because we do not want to go through the constraint checking
610
- // the usage of symbol here to prevent any colision on custom header name
612
+ // the usage of symbol here to prevent any collision on custom header name
611
613
  req.headers[kRequestAcceptVersion] = req.headers['accept-version']
612
614
  req.headers['accept-version'] = undefined
613
615
  }
package/lib/hooks.js CHANGED
@@ -231,6 +231,10 @@ function onSendHookRunner (functions, request, reply, payload, cb) {
231
231
  }
232
232
 
233
233
  function handleReject (err) {
234
+ if (!err) {
235
+ err = new FST_ERR_SEND_UNDEFINED_ERR()
236
+ }
237
+
234
238
  cb(err, request, reply, payload)
235
239
  }
236
240
 
@@ -103,8 +103,8 @@ function checkVersion (fn) {
103
103
 
104
104
  const requiredVersion = meta.fastify
105
105
 
106
- const sanitizedVersion = this.version.replace(/-rc.\d+$/, '')
107
- if (requiredVersion && !semver.satisfies(sanitizedVersion, requiredVersion)) {
106
+ const fastifyRc = /-rc.+$/.test(this.version)
107
+ if (requiredVersion && semver.satisfies(this.version, requiredVersion, { includePrerelease: fastifyRc }) === false) {
108
108
  throw new FST_ERR_PLUGIN_VERSION_MISMATCH(meta.name, requiredVersion, this.version)
109
109
  }
110
110
  }
package/lib/route.js CHANGED
@@ -20,7 +20,8 @@ const {
20
20
  FST_ERR_SCH_SERIALIZATION_BUILD,
21
21
  FST_ERR_DEFAULT_ROUTE_INVALID_TYPE,
22
22
  FST_ERR_DUPLICATED_ROUTE,
23
- FST_ERR_INVALID_URL
23
+ FST_ERR_INVALID_URL,
24
+ FST_ERR_SEND_UNDEFINED_ERR
24
25
  } = require('./errors')
25
26
 
26
27
  const {
@@ -47,7 +48,6 @@ function buildRouting (options) {
47
48
  let avvio
48
49
  let fourOhFour
49
50
  let requestIdHeader
50
- let querystringParser
51
51
  let requestIdLogLabel
52
52
  let logger
53
53
  let hasLogger
@@ -58,6 +58,7 @@ function buildRouting (options) {
58
58
  let ignoreTrailingSlash
59
59
  let return503OnClosing
60
60
  let globalExposeHeadRoutes
61
+ let validateHTTPVersion
61
62
 
62
63
  let closing = false
63
64
 
@@ -69,10 +70,10 @@ function buildRouting (options) {
69
70
  hasLogger = fastifyArgs.hasLogger
70
71
  setupResponseListeners = fastifyArgs.setupResponseListeners
71
72
  throwIfAlreadyStarted = fastifyArgs.throwIfAlreadyStarted
73
+ validateHTTPVersion = fastifyArgs.validateHTTPVersion
72
74
 
73
75
  globalExposeHeadRoutes = options.exposeHeadRoutes
74
76
  requestIdHeader = options.requestIdHeader
75
- querystringParser = options.querystringParser
76
77
  requestIdLogLabel = options.requestIdLogLabel
77
78
  genReqId = options.genReqId
78
79
  disableRequestLogging = options.disableRequestLogging
@@ -94,7 +95,18 @@ function buildRouting (options) {
94
95
  },
95
96
  routeHandler,
96
97
  closeRoutes: () => { closing = true },
97
- printRoutes: router.prettyPrint.bind(router)
98
+ printRoutes: router.prettyPrint.bind(router),
99
+ addConstraintStrategy,
100
+ hasConstraintStrategy
101
+ }
102
+
103
+ function addConstraintStrategy (strategy) {
104
+ throwIfAlreadyStarted('Cannot add constraint strategy when fastify instance is already started!')
105
+ return router.addConstraintStrategy(strategy)
106
+ }
107
+
108
+ function hasConstraintStrategy (strategyName) {
109
+ return router.hasConstraintStrategy(strategyName)
98
110
  }
99
111
 
100
112
  // Convert shorthand to extended route declaration
@@ -322,7 +334,19 @@ function buildRouting (options) {
322
334
  }
323
335
 
324
336
  // HTTP request entry point, the routing has already been executed
325
- function routeHandler (req, res, params, context) {
337
+ function routeHandler (req, res, params, context, query) {
338
+ // TODO: The check here should be removed once https://github.com/nodejs/node/issues/43115 resolve in core.
339
+ if (!validateHTTPVersion(req.httpVersion)) {
340
+ const message = '{"error":"HTTP Version Not Supported","message":"HTTP Version Not Supported","statusCode":505}'
341
+ const headers = {
342
+ 'Content-Type': 'application/json',
343
+ 'Content-Length': message.length
344
+ }
345
+ res.writeHead(505, headers)
346
+ res.end(message)
347
+ return
348
+ }
349
+
326
350
  if (closing === true) {
327
351
  /* istanbul ignore next mac, windows */
328
352
  if (req.httpVersionMajor !== 2) {
@@ -374,8 +398,6 @@ function buildRouting (options) {
374
398
  const childLogger = logger.child(loggerBinding, loggerOpts)
375
399
  childLogger[kDisableRequestLogging] = disableRequestLogging
376
400
 
377
- const queryPrefix = req.url.indexOf('?')
378
- const query = querystringParser(queryPrefix > -1 ? req.url.slice(queryPrefix + 1) : '')
379
401
  const request = new context.Request(id, params, req, query, childLogger, context)
380
402
  const reply = new context.Reply(res, request, childLogger)
381
403
 
@@ -489,6 +511,10 @@ function preParsingHookRunner (functions, request, reply, cb) {
489
511
  }
490
512
 
491
513
  function handleReject (err) {
514
+ if (!err) {
515
+ err = new FST_ERR_SEND_UNDEFINED_ERR()
516
+ }
517
+
492
518
  next(err)
493
519
  }
494
520
 
package/lib/server.js CHANGED
@@ -8,7 +8,8 @@ const warnings = require('./warnings')
8
8
  const { kState, kOptions, kServerBindings } = require('./symbols')
9
9
  const { FST_ERR_HTTP2_INVALID_VERSION, FST_ERR_REOPENED_CLOSE_SERVER, FST_ERR_REOPENED_SERVER } = require('./errors')
10
10
 
11
- module.exports = createServer
11
+ module.exports.createServer = createServer
12
+ module.exports.compileValidateHTTPVersion = compileValidateHTTPVersion
12
13
 
13
14
  function createServer (options, httpHandler) {
14
15
  const server = getServerInstance(options, httpHandler)
@@ -208,6 +209,56 @@ function listenPromise (server, listenOptions) {
208
209
  })
209
210
  }
210
211
 
212
+ /**
213
+ * Creates a function that, based upon initial configuration, will
214
+ * verify that every incoming request conforms to allowed
215
+ * HTTP versions for the Fastify instance, e.g. a Fastify HTTP/1.1
216
+ * server will not serve HTTP/2 requests upon the result of the
217
+ * verification function.
218
+ *
219
+ * @param {object} options fastify option
220
+ * @param {function} [options.serverFactory] If present, the
221
+ * validator function will skip all checks.
222
+ * @param {boolean} [options.http2 = false] If true, the validator
223
+ * function will allow HTTP/2 requests.
224
+ * @param {object} [options.https = null] https server options
225
+ * @param {boolean} [options.https.allowHTTP1] If true and use
226
+ * with options.http2 the validator function will allow HTTP/1
227
+ * request to http2 server.
228
+ *
229
+ * @returns {function} HTTP version validator function.
230
+ */
231
+ function compileValidateHTTPVersion (options) {
232
+ let bypass = false
233
+ // key-value map to store valid http version
234
+ const map = new Map()
235
+ if (options.serverFactory) {
236
+ // When serverFactory is passed, we cannot identify how to check http version reliably
237
+ // So, we should skip the http version check
238
+ bypass = true
239
+ }
240
+ if (options.http2) {
241
+ // HTTP2 must serve HTTP/2.0
242
+ map.set('2.0', true)
243
+ if (options.https && options.https.allowHTTP1 === true) {
244
+ // HTTP2 with HTTPS.allowHTTP1 allow fallback to HTTP/1.1 and HTTP/1.0
245
+ map.set('1.1', true)
246
+ map.set('1.0', true)
247
+ }
248
+ } else {
249
+ // HTTP must server HTTP/1.1 and HTTP/1.0
250
+ map.set('1.1', true)
251
+ map.set('1.0', true)
252
+ }
253
+ // The compiled function here placed in one of the hottest path inside fastify
254
+ // the implementation here must be as performant as possible
255
+ return function validateHTTPVersion (httpVersion) {
256
+ // `bypass` skip the check when custom server factory provided
257
+ // `httpVersion in obj` check for the valid http version we should support
258
+ return bypass || map.has(httpVersion)
259
+ }
260
+ }
261
+
211
262
  function getServerInstance (options, httpHandler) {
212
263
  let server = null
213
264
  if (options.serverFactory) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "4.0.0-rc.2",
3
+ "version": "4.0.0-rc.3",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -180,7 +180,7 @@
180
180
  "@fastify/fast-json-stringify-compiler": "^1.0.0",
181
181
  "abstract-logging": "^2.0.1",
182
182
  "avvio": "^8.1.0",
183
- "find-my-way": "^5.3.0",
183
+ "find-my-way": "^6.0.0",
184
184
  "light-my-request": "^4.7.0",
185
185
  "pino": "^7.5.1",
186
186
  "process-warning": "^1.0.0",
@@ -113,12 +113,10 @@ test('Should allow registering custom constrained routes', t => {
113
113
  const constraint = {
114
114
  name: 'secret',
115
115
  storage: function () {
116
- let secrets = {}
116
+ const secrets = {}
117
117
  return {
118
118
  get: (secret) => { return secrets[secret] || null },
119
- set: (secret, store) => { secrets[secret] = store },
120
- del: (secret) => { delete secrets[secret] },
121
- empty: () => { secrets = {} }
119
+ set: (secret, store) => { secrets[secret] = store }
122
120
  }
123
121
  },
124
122
  deriveConstraint: (req, ctx) => {
@@ -183,6 +181,266 @@ test('Should allow registering custom constrained routes', t => {
183
181
  })
184
182
  })
185
183
 
184
+ test('Should allow registering custom constrained routes outside constructor', t => {
185
+ t.plan(8)
186
+
187
+ const constraint = {
188
+ name: 'secret',
189
+ storage: function () {
190
+ const secrets = {}
191
+ return {
192
+ get: (secret) => { return secrets[secret] || null },
193
+ set: (secret, store) => { secrets[secret] = store }
194
+ }
195
+ },
196
+ deriveConstraint: (req, ctx) => {
197
+ return req.headers['x-secret']
198
+ },
199
+ validate () { return true }
200
+ }
201
+
202
+ const fastify = Fastify()
203
+ fastify.addConstraintStrategy(constraint)
204
+
205
+ fastify.route({
206
+ method: 'GET',
207
+ url: '/',
208
+ constraints: { secret: 'alpha' },
209
+ handler: (req, reply) => {
210
+ reply.send({ hello: 'from alpha' })
211
+ }
212
+ })
213
+
214
+ fastify.route({
215
+ method: 'GET',
216
+ url: '/',
217
+ constraints: { secret: 'beta' },
218
+ handler: (req, reply) => {
219
+ reply.send({ hello: 'from beta' })
220
+ }
221
+ })
222
+
223
+ fastify.inject({
224
+ method: 'GET',
225
+ url: '/',
226
+ headers: {
227
+ 'X-Secret': 'alpha'
228
+ }
229
+ }, (err, res) => {
230
+ t.error(err)
231
+ t.same(JSON.parse(res.payload), { hello: 'from alpha' })
232
+ t.equal(res.statusCode, 200)
233
+ })
234
+
235
+ fastify.inject({
236
+ method: 'GET',
237
+ url: '/',
238
+ headers: {
239
+ 'X-Secret': 'beta'
240
+ }
241
+ }, (err, res) => {
242
+ t.error(err)
243
+ t.same(JSON.parse(res.payload), { hello: 'from beta' })
244
+ t.equal(res.statusCode, 200)
245
+ })
246
+
247
+ fastify.inject({
248
+ method: 'GET',
249
+ url: '/',
250
+ headers: {
251
+ 'X-Secret': 'gamma'
252
+ }
253
+ }, (err, res) => {
254
+ t.error(err)
255
+ t.equal(res.statusCode, 404)
256
+ })
257
+ })
258
+
259
+ test('Add a constraint strategy after fastify instance was started', t => {
260
+ t.plan(4)
261
+
262
+ const constraint = {
263
+ name: 'secret',
264
+ storage: function () {
265
+ const secrets = {}
266
+ return {
267
+ get: (secret) => { return secrets[secret] || null },
268
+ set: (secret, store) => { secrets[secret] = store }
269
+ }
270
+ },
271
+ deriveConstraint: (req, ctx) => {
272
+ return req.headers['x-secret']
273
+ },
274
+ validate () { return true }
275
+ }
276
+
277
+ const fastify = Fastify()
278
+
279
+ fastify.route({
280
+ method: 'GET',
281
+ url: '/',
282
+ handler: (req, reply) => { reply.send('ok') }
283
+ })
284
+
285
+ fastify.inject({
286
+ method: 'GET',
287
+ url: '/'
288
+ }, (err, res) => {
289
+ t.error(err)
290
+ t.same(res.payload, 'ok')
291
+ t.equal(res.statusCode, 200)
292
+
293
+ t.throws(
294
+ () => fastify.addConstraintStrategy(constraint),
295
+ 'Cannot add constraint strategy when fastify instance is already started!'
296
+ )
297
+ })
298
+ })
299
+
300
+ test('Add a constraint strategy should throw an error if there already exist custom strategy with the same name', t => {
301
+ t.plan(1)
302
+
303
+ const constraint = {
304
+ name: 'secret',
305
+ storage: function () {
306
+ const secrets = {}
307
+ return {
308
+ get: (secret) => { return secrets[secret] || null },
309
+ set: (secret, store) => { secrets[secret] = store }
310
+ }
311
+ },
312
+ deriveConstraint: (req, ctx) => {
313
+ return req.headers['x-secret']
314
+ },
315
+ validate () { return true }
316
+ }
317
+
318
+ const fastify = Fastify()
319
+
320
+ fastify.addConstraintStrategy(constraint)
321
+ t.throws(
322
+ () => fastify.addConstraintStrategy(constraint),
323
+ 'There already exists a custom constraint with the name secret.'
324
+ )
325
+ })
326
+
327
+ test('Add a constraint strategy shouldn\'t throw an error if default constraint with the same name isn\'t used', t => {
328
+ t.plan(1)
329
+
330
+ const constraint = {
331
+ name: 'version',
332
+ storage: function () {
333
+ const secrets = {}
334
+ return {
335
+ get: (secret) => { return secrets[secret] || null },
336
+ set: (secret, store) => { secrets[secret] = store }
337
+ }
338
+ },
339
+ deriveConstraint: (req, ctx) => {
340
+ return req.headers['x-secret']
341
+ },
342
+ validate () { return true }
343
+ }
344
+
345
+ const fastify = Fastify()
346
+ fastify.addConstraintStrategy(constraint)
347
+
348
+ t.pass()
349
+ })
350
+
351
+ test('Add a constraint strategy should throw an error if default constraint with the same name is used', t => {
352
+ t.plan(1)
353
+
354
+ const constraint = {
355
+ name: 'version',
356
+ storage: function () {
357
+ const secrets = {}
358
+ return {
359
+ get: (secret) => { return secrets[secret] || null },
360
+ set: (secret, store) => { secrets[secret] = store }
361
+ }
362
+ },
363
+ deriveConstraint: (req, ctx) => {
364
+ return req.headers['x-secret']
365
+ },
366
+ validate () { return true }
367
+ }
368
+
369
+ const fastify = Fastify()
370
+
371
+ fastify.route({
372
+ method: 'GET',
373
+ url: '/',
374
+ constraints: { version: '1.0.0' },
375
+ handler: (req, reply) => {
376
+ reply.send('ok')
377
+ }
378
+ })
379
+
380
+ t.throws(
381
+ () => fastify.addConstraintStrategy(constraint),
382
+ 'There already exists a route with version constraint.'
383
+ )
384
+ })
385
+
386
+ test('The hasConstraintStrategy should return false for default constraints until they are used', t => {
387
+ t.plan(6)
388
+
389
+ const fastify = Fastify()
390
+
391
+ t.equal(fastify.hasConstraintStrategy('version'), false)
392
+ t.equal(fastify.hasConstraintStrategy('host'), false)
393
+
394
+ fastify.route({
395
+ method: 'GET',
396
+ url: '/',
397
+ constraints: { host: 'fastify.io' },
398
+ handler: (req, reply) => {
399
+ reply.send({ hello: 'from any other domain' })
400
+ }
401
+ })
402
+
403
+ t.equal(fastify.hasConstraintStrategy('version'), false)
404
+ t.equal(fastify.hasConstraintStrategy('host'), true)
405
+
406
+ fastify.route({
407
+ method: 'GET',
408
+ url: '/',
409
+ constraints: { version: '1.0.0' },
410
+ handler: (req, reply) => {
411
+ reply.send({ hello: 'from any other domain' })
412
+ }
413
+ })
414
+
415
+ t.equal(fastify.hasConstraintStrategy('version'), true)
416
+ t.equal(fastify.hasConstraintStrategy('host'), true)
417
+ })
418
+
419
+ test('The hasConstraintStrategy should return true if there already exist a custom constraint with the same name', t => {
420
+ t.plan(2)
421
+
422
+ const constraint = {
423
+ name: 'secret',
424
+ storage: function () {
425
+ const secrets = {}
426
+ return {
427
+ get: (secret) => { return secrets[secret] || null },
428
+ set: (secret, store) => { secrets[secret] = store }
429
+ }
430
+ },
431
+ deriveConstraint: (req, ctx) => {
432
+ return req.headers['x-secret']
433
+ },
434
+ validate () { return true }
435
+ }
436
+
437
+ const fastify = Fastify()
438
+
439
+ t.equal(fastify.hasConstraintStrategy('secret'), false)
440
+ fastify.addConstraintStrategy(constraint)
441
+ t.equal(fastify.hasConstraintStrategy('secret'), true)
442
+ })
443
+
186
444
  test('Should allow registering an unconstrained route after a constrained route', t => {
187
445
  t.plan(6)
188
446
  const fastify = Fastify()
@@ -3146,6 +3146,64 @@ test('reply.send should throw if undefined error is thrown', t => {
3146
3146
  })
3147
3147
  })
3148
3148
 
3149
+ test('reply.send should throw if undefined error is thrown at preParsing hook', t => {
3150
+ /* eslint prefer-promise-reject-errors: ["error", {"allowEmptyReject": true}] */
3151
+
3152
+ t.plan(3)
3153
+ const fastify = Fastify()
3154
+
3155
+ fastify.addHook('preParsing', function (req, reply, done) {
3156
+ return Promise.reject()
3157
+ })
3158
+
3159
+ fastify.get('/', (req, reply) => {
3160
+ reply.send('hello')
3161
+ })
3162
+
3163
+ fastify.inject({
3164
+ method: 'GET',
3165
+ url: '/'
3166
+ }, (err, res) => {
3167
+ t.error(err)
3168
+ t.equal(res.statusCode, 500)
3169
+ t.same(JSON.parse(res.payload), {
3170
+ error: 'Internal Server Error',
3171
+ code: 'FST_ERR_SEND_UNDEFINED_ERR',
3172
+ message: 'Undefined error has occurred',
3173
+ statusCode: 500
3174
+ })
3175
+ })
3176
+ })
3177
+
3178
+ test('reply.send should throw if undefined error is thrown at onSend hook', t => {
3179
+ /* eslint prefer-promise-reject-errors: ["error", {"allowEmptyReject": true}] */
3180
+
3181
+ t.plan(3)
3182
+ const fastify = Fastify()
3183
+
3184
+ fastify.addHook('onSend', function (req, reply, done) {
3185
+ return Promise.reject()
3186
+ })
3187
+
3188
+ fastify.get('/', (req, reply) => {
3189
+ reply.send('hello')
3190
+ })
3191
+
3192
+ fastify.inject({
3193
+ method: 'GET',
3194
+ url: '/'
3195
+ }, (err, res) => {
3196
+ t.error(err)
3197
+ t.equal(res.statusCode, 500)
3198
+ t.same(JSON.parse(res.payload), {
3199
+ error: 'Internal Server Error',
3200
+ code: 'FST_ERR_SEND_UNDEFINED_ERR',
3201
+ message: 'Undefined error has occurred',
3202
+ statusCode: 500
3203
+ })
3204
+ })
3205
+ })
3206
+
3149
3207
  test('onTimeout should be triggered', t => {
3150
3208
  t.plan(6)
3151
3209
  const fastify = Fastify({ connectionTimeout: 500 })
@@ -68,12 +68,10 @@ test('Fastify.initialConfig should expose all options', t => {
68
68
  const versionStrategy = {
69
69
  name: 'version',
70
70
  storage: function () {
71
- let versions = {}
71
+ const versions = {}
72
72
  return {
73
73
  get: (version) => { return versions[version] || null },
74
- set: (version, store) => { versions[version] = store },
75
- del: (version) => { delete versions[version] },
76
- empty: () => { versions = {} }
74
+ set: (version, store) => { versions[version] = store }
77
75
  }
78
76
  },
79
77
  deriveConstraint: (req, ctx) => {
@@ -362,12 +360,10 @@ test('Fastify.initialConfig should accept the deprecated versioning option', t =
362
360
 
363
361
  const versioning = {
364
362
  storage: function () {
365
- let versions = {}
363
+ const versions = {}
366
364
  return {
367
365
  get: (version) => { return versions[version] || null },
368
- set: (version, store) => { versions[version] = store },
369
- del: (version) => { delete versions[version] },
370
- empty: () => { versions = {} }
366
+ set: (version, store) => { versions[version] = store }
371
367
  }
372
368
  },
373
369
  deriveVersion: (req, ctx) => {
@@ -4,7 +4,7 @@ const { test } = require('tap')
4
4
  const proxyquire = require('proxyquire')
5
5
 
6
6
  const Fastify = require('../../fastify')
7
- const createServer = require('../../lib/server')
7
+ const { createServer } = require('../../lib/server')
8
8
 
9
9
  const handler = (req, res) => {
10
10
  res.writeHead(200, { 'Content-Type': 'application/json' })
@@ -19,7 +19,7 @@ test('start listening', async t => {
19
19
  })
20
20
 
21
21
  test('DNS errors does not stop the main server on localhost - promise interface', async t => {
22
- const createServer = proxyquire('../../lib/server', {
22
+ const { createServer } = proxyquire('../../lib/server', {
23
23
  dns: {
24
24
  lookup: (hostname, options, cb) => {
25
25
  cb(new Error('DNS error'))
@@ -34,7 +34,7 @@ test('DNS errors does not stop the main server on localhost - promise interface'
34
34
 
35
35
  test('DNS errors does not stop the main server on localhost - callback interface', t => {
36
36
  t.plan(2)
37
- const createServer = proxyquire('../../lib/server', {
37
+ const { createServer } = proxyquire('../../lib/server', {
38
38
  dns: {
39
39
  lookup: (hostname, options, cb) => {
40
40
  cb(new Error('DNS error'))
@@ -51,7 +51,7 @@ test('DNS errors does not stop the main server on localhost - callback interface
51
51
 
52
52
  test('DNS returns empty binding', t => {
53
53
  t.plan(2)
54
- const createServer = proxyquire('../../lib/server', {
54
+ const { createServer } = proxyquire('../../lib/server', {
55
55
  dns: {
56
56
  lookup: (hostname, options, cb) => {
57
57
  cb(null, [])
@@ -68,7 +68,7 @@ test('DNS returns empty binding', t => {
68
68
 
69
69
  test('DNS returns more than two binding', t => {
70
70
  t.plan(2)
71
- const createServer = proxyquire('../../lib/server', {
71
+ const { createServer } = proxyquire('../../lib/server', {
72
72
  dns: {
73
73
  lookup: (hostname, options, cb) => {
74
74
  cb(null, [
@@ -7,7 +7,6 @@ const { test, before } = require('tap')
7
7
  const dns = require('dns').promises
8
8
  const dnsCb = require('dns')
9
9
  const sget = require('simple-get').concat
10
- const semver = require('semver')
11
10
  const Fastify = require('..')
12
11
 
13
12
  let localhost
@@ -372,12 +371,19 @@ test('addresses getter', async t => {
372
371
  const localAddresses = await dns.lookup('localhost', { all: true })
373
372
  for (const address of localAddresses) {
374
373
  address.port = port
375
- if (semver.lt(process.versions.node, '18.0.0')) {
374
+ if (typeof address.family === 'number') {
375
+ address.family = 'IPv' + address.family
376
+ }
377
+ }
378
+ const appAddresses = app.addresses()
379
+ for (const address of appAddresses) {
380
+ if (typeof address.family === 'number') {
376
381
  address.family = 'IPv' + address.family
377
382
  }
378
383
  }
379
384
  localAddresses.sort((a, b) => a.address.localeCompare(b.address))
380
- t.same(app.addresses().sort((a, b) => a.address.localeCompare(b.address)), localAddresses, 'after listen')
385
+ appAddresses.sort((a, b) => a.address.localeCompare(b.address))
386
+ t.same(appAddresses, localAddresses, 'after listen')
381
387
 
382
388
  await app.close()
383
389
  t.same(app.addresses(), [], 'after close')
@@ -10,6 +10,7 @@ import fastify, {
10
10
  import { HookHandlerDoneFunction } from '../../types/hooks'
11
11
  import { FastifyReply } from '../../types/reply'
12
12
  import { FastifyRequest } from '../../types/request'
13
+ import { DefaultRoute } from '../../types/route'
13
14
  import { FastifySchemaControllerOptions } from '../../types/schema'
14
15
 
15
16
  const server = fastify()
@@ -307,3 +308,19 @@ expectError(server.decorate<string>('test', true))
307
308
  expectError(server.decorate<(myNumber: number) => number>('test', function (myNumber: number): string {
308
309
  return ''
309
310
  }))
311
+
312
+ const versionConstraintStrategy = {
313
+ name: 'version',
314
+ storage: () => ({
315
+ get: () => () => {},
316
+ set: () => { },
317
+ del: () => { },
318
+ empty: () => { }
319
+ }),
320
+ validate () {},
321
+ deriveConstraint: () => 'foo'
322
+ }
323
+ expectType<void>(server.addConstraintStrategy(versionConstraintStrategy))
324
+ expectType<boolean>(server.hasConstraintStrategy(versionConstraintStrategy.name))
325
+
326
+ expectAssignable<DefaultRoute<RawRequestDefaultExpression, RawReplyDefaultExpression>>(server.getDefaultRoute())
@@ -0,0 +1,57 @@
1
+ 'use strict'
2
+
3
+ const net = require('net')
4
+ const t = require('tap')
5
+ const Fastify = require('../fastify')
6
+
7
+ t.test('Will return 505 HTTP error if HTTP version (default) is not supported', t => {
8
+ const fastify = Fastify()
9
+
10
+ t.teardown(fastify.close.bind(fastify))
11
+
12
+ fastify.get('/', (req, reply) => {
13
+ reply.send({ hello: 'world' })
14
+ })
15
+
16
+ fastify.listen({ port: 0 }, err => {
17
+ t.error(err)
18
+
19
+ const port = fastify.server.address().port
20
+ const client = net.createConnection({ port }, () => {
21
+ client.write('GET / HTTP/5.1\r\n\r\n')
22
+
23
+ client.once('data', data => {
24
+ t.match(data.toString(), /505 HTTP Version Not Supported/i)
25
+ client.end(() => {
26
+ t.end()
27
+ })
28
+ })
29
+ })
30
+ })
31
+ })
32
+
33
+ t.test('Will return 505 HTTP error if HTTP version (2.0 when server is 1.1) is not supported', t => {
34
+ const fastify = Fastify()
35
+
36
+ t.teardown(fastify.close.bind(fastify))
37
+
38
+ fastify.get('/', (req, reply) => {
39
+ reply.send({ hello: 'world' })
40
+ })
41
+
42
+ fastify.listen({ port: 0 }, err => {
43
+ t.error(err)
44
+
45
+ const port = fastify.server.address().port
46
+ const client = net.createConnection({ port }, () => {
47
+ client.write('GET / HTTP/2.0\r\n\r\n')
48
+
49
+ client.once('data', data => {
50
+ t.match(data.toString(), /505 HTTP Version Not Supported/i)
51
+ client.end(() => {
52
+ t.end()
53
+ })
54
+ })
55
+ })
56
+ })
57
+ })
@@ -480,12 +480,10 @@ test('Should register a versioned route with custom versioning strategy', t => {
480
480
  const customVersioning = {
481
481
  name: 'version',
482
482
  storage: function () {
483
- let versions = {}
483
+ const versions = {}
484
484
  return {
485
485
  get: (version) => { return versions[version] || null },
486
- set: (version, store) => { versions[version] = store },
487
- del: (version) => { delete versions[version] },
488
- empty: () => { versions = {} }
486
+ set: (version, store) => { versions[version] = store }
489
487
  }
490
488
  },
491
489
  deriveConstraint: (req, ctx) => {
@@ -561,12 +559,10 @@ test('Should get error using an invalid a versioned route, using default validat
561
559
  const fastify = Fastify({
562
560
  versioning: {
563
561
  storage: function () {
564
- let versions = {}
562
+ const versions = {}
565
563
  return {
566
564
  get: (version) => { return versions[version] || null },
567
- set: (version, store) => { versions[version] = store },
568
- del: (version) => { delete versions[version] },
569
- empty: () => { versions = {} }
565
+ set: (version, store) => { versions[version] = store }
570
566
  }
571
567
  },
572
568
  deriveVersion: (req, ctx) => {
@@ -1,4 +1,6 @@
1
+ import * as http from 'http'
1
2
  import { FastifyError } from '@fastify/error'
3
+ import { ConstraintStrategy, HTTPVersion } from 'find-my-way'
2
4
  import { CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse } from 'light-my-request'
3
5
  import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction, removeAllContentTypeParsers, removeContentTypeParser } from './content-type-parser'
4
6
  import { onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler } from './hooks'
@@ -29,6 +31,7 @@ export interface PrintRoutesOptions {
29
31
  }
30
32
 
31
33
  type NotInInterface<Key, _Interface> = Key extends keyof _Interface ? never : Key
34
+ type FindMyWayVersion<RawServer extends RawServerBase> = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2
32
35
 
33
36
  /**
34
37
  * Fastify server instance. Returned by the core `fastify()` method.
@@ -83,6 +86,9 @@ export interface FastifyInstance<
83
86
  hasRequestDecorator(decorator: string | symbol): boolean;
84
87
  hasReplyDecorator(decorator: string | symbol): boolean;
85
88
 
89
+ addConstraintStrategy(strategy: ConstraintStrategy<FindMyWayVersion<RawServer>, unknown>): void;
90
+ hasConstraintStrategy(strategyName: string): boolean;
91
+
86
92
  inject(opts: InjectOptions | string, cb: LightMyRequestCallback): void;
87
93
  inject(opts: InjectOptions | string): Promise<LightMyRequestResponse>;
88
94
  inject(): LightMyRequestChain;
@@ -206,7 +212,7 @@ export interface FastifyInstance<
206
212
  register: FastifyRegister<FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider> & PromiseLike<undefined>>;
207
213
 
208
214
  routing(req: RawRequest, res: RawReply): void;
209
- getDefaultRoute: DefaultRoute<RawRequest, RawReply>;
215
+ getDefaultRoute(): DefaultRoute<RawRequest, RawReply>;
210
216
  setDefaultRoute(defaultRoute: DefaultRoute<RawRequest, RawReply>): void;
211
217
 
212
218
  route<