fastify 3.21.1 → 3.21.5
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/.taprc +1 -0
- package/README.md +3 -1
- package/docs/Recommendations.md +1 -1
- package/docs/Reply.md +2 -2
- package/docs/Request.md +6 -6
- package/docs/Server.md +28 -15
- package/lib/decorate.js +33 -9
- package/lib/pluginUtils.js +14 -10
- package/lib/reply.js +11 -0
- package/lib/request.js +8 -0
- package/package.json +3 -2
- package/test/decorator.test.js +149 -0
- package/test/internals/decorator.test.js +1 -1
- package/test/internals/plugin.test.js +4 -4
- package/test/same-shape.test.js +124 -0
- package/test/stream.test.js +37 -1
package/.taprc
CHANGED
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<
|
|
2
|
+
<a href="https://fastify.io/">
|
|
3
|
+
<img src="https://github.com/fastify/graphics/raw/HEAD/fastify-landscape-outlined.svg" width="650" height="auto"/>
|
|
4
|
+
</a>
|
|
3
5
|
</div>
|
|
4
6
|
|
|
5
7
|
<div align="center">
|
package/docs/Recommendations.md
CHANGED
package/docs/Reply.md
CHANGED
|
@@ -335,7 +335,7 @@ If you pass to *send* an object that is an instance of *Error*, Fastify will aut
|
|
|
335
335
|
}
|
|
336
336
|
```
|
|
337
337
|
|
|
338
|
-
You can add
|
|
338
|
+
You can add custom properties to the Error object, such as `headers`, that will be used to enhance the HTTP response.<br>
|
|
339
339
|
*Note: If you are passing an error to `send` and the statusCode is less than 400, Fastify will automatically set it at 500.*
|
|
340
340
|
|
|
341
341
|
Tip: you can simplify errors by using the [`http-errors`](https://npm.im/http-errors) module or [`fastify-sensible`](https://github.com/fastify/fastify-sensible) plugin to generate errors:
|
|
@@ -376,7 +376,7 @@ fastify.get('/', {
|
|
|
376
376
|
})
|
|
377
377
|
```
|
|
378
378
|
|
|
379
|
-
If you want to
|
|
379
|
+
If you want to customize error handling, check out [`setErrorHandler`](Server.md#seterrorhandler) API.<br>
|
|
380
380
|
*Note: you are responsible for logging when customizing the error handler*
|
|
381
381
|
|
|
382
382
|
API:
|
package/docs/Request.md
CHANGED
|
@@ -3,32 +3,32 @@
|
|
|
3
3
|
## Request
|
|
4
4
|
The first parameter of the handler function is `Request`.<br>
|
|
5
5
|
Request is a core Fastify object containing the following fields:
|
|
6
|
-
- `query` - the parsed querystring
|
|
6
|
+
- `query` - the parsed querystring, its format is specified by [`querystringParser`](Server.md#querystringParser)
|
|
7
7
|
- `body` - the body
|
|
8
8
|
- `params` - the params matching the URL
|
|
9
9
|
- [`headers`](#headers) - the headers getter and setter
|
|
10
10
|
- `raw` - the incoming HTTP request from Node core
|
|
11
11
|
- `req` *(deprecated, use `.raw` instead)* - the incoming HTTP request from Node core
|
|
12
12
|
- `server` - The Fastify server instance, scoped to the current [encapsulation context](Encapsulation.md)
|
|
13
|
-
- `id` - the request
|
|
13
|
+
- `id` - the request ID
|
|
14
14
|
- `log` - the logger instance of the incoming request
|
|
15
15
|
- `ip` - the IP address of the incoming request
|
|
16
16
|
- `ips` - an array of the IP addresses, ordered from closest to furthest, in the `X-Forwarded-For` header of the incoming request (only when the [`trustProxy`](Server.md#factory-trust-proxy) option is enabled)
|
|
17
|
-
- `hostname` - the
|
|
17
|
+
- `hostname` - the host of the incoming request (derived from `X-Forwarded-Host` header when the [`trustProxy`](Server.md#factory-trust-proxy) option is enabled). For HTTP/2 compatibility it returns `:authority` if no host header exists.
|
|
18
18
|
- `protocol` - the protocol of the incoming request (`https` or `http`)
|
|
19
19
|
- `method` - the method of the incoming request
|
|
20
|
-
- `url` - the
|
|
20
|
+
- `url` - the URL of the incoming request
|
|
21
21
|
- `routerMethod` - the method defined for the router that is handling the request
|
|
22
22
|
- `routerPath` - the path pattern defined for the router that is handling the request
|
|
23
23
|
- `is404` - true if request is being handled by 404 handler, false if it is not
|
|
24
24
|
- `connection` - Deprecated, use `socket` instead. The underlying connection of the incoming request.
|
|
25
25
|
- `socket` - the underlying connection of the incoming request
|
|
26
|
-
- `context` - A Fastify internal object. You should not use it directly or modify it. It is
|
|
26
|
+
- `context` - A Fastify internal object. You should not use it directly or modify it. It is useful to access one special key:
|
|
27
27
|
- `context.config` - The route [`config`](Routes.md#routes-config) object.
|
|
28
28
|
|
|
29
29
|
### Headers
|
|
30
30
|
|
|
31
|
-
The `request.headers` is a getter that
|
|
31
|
+
The `request.headers` is a getter that returns an Object with the headers of the incoming request.
|
|
32
32
|
You can set custom headers like this:
|
|
33
33
|
|
|
34
34
|
```js
|
package/docs/Server.md
CHANGED
|
@@ -110,7 +110,7 @@ fastify.get('/bar', function (req, reply) {
|
|
|
110
110
|
|
|
111
111
|
<a name="factory-max-param-length"></a>
|
|
112
112
|
### `maxParamLength`
|
|
113
|
-
You can set a custom length for parameters in parametric (standard, regex, and multi) routes by using `maxParamLength` option
|
|
113
|
+
You can set a custom length for parameters in parametric (standard, regex, and multi) routes by using `maxParamLength` option; the default value is 100 characters.<br>
|
|
114
114
|
This can be useful especially if you have some regex based route, protecting you against [DoS attacks](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS).<br>
|
|
115
115
|
*If the maximum length limit is reached, the not found route will be invoked.*
|
|
116
116
|
|
|
@@ -167,7 +167,7 @@ are not present on the object, they will be added accordingly:
|
|
|
167
167
|
* `level`: the minimum logging level. If not set, it will be set to `'info'`.
|
|
168
168
|
* `serializers`: a hash of serialization functions. By default, serializers
|
|
169
169
|
are added for `req` (incoming request objects), `res` (outgoing response
|
|
170
|
-
|
|
170
|
+
objects), and `err` (standard `Error` objects). When a log method receives
|
|
171
171
|
an object with any of these properties then the respective serializer will
|
|
172
172
|
be used for that property. For example:
|
|
173
173
|
```js
|
|
@@ -228,7 +228,7 @@ Please note that this setting will also disable an error log written by the defa
|
|
|
228
228
|
<a name="custom-http-server"></a>
|
|
229
229
|
### `serverFactory`
|
|
230
230
|
You can pass a custom HTTP server to Fastify by using the `serverFactory` option.<br/>
|
|
231
|
-
`serverFactory` is a function that takes
|
|
231
|
+
`serverFactory` is a function that takes a `handler` parameter, which takes the `request` and `response` objects as parameters, and an options object, which is the same you have passed to Fastify.
|
|
232
232
|
|
|
233
233
|
```js
|
|
234
234
|
const serverFactory = (handler, opts) => {
|
|
@@ -255,7 +255,7 @@ Internally Fastify uses the API of Node core HTTP server, so if you are using a
|
|
|
255
255
|
|
|
256
256
|
+ Default: `true`
|
|
257
257
|
|
|
258
|
-
Internally, and by default, Fastify will automatically infer the root properties of JSON Schemas if it
|
|
258
|
+
Internally, and by default, Fastify will automatically infer the root properties of JSON Schemas if it does not find valid root properties according to the JSON Schema spec. If you wish to implement your own schema validation compiler, for example: to parse schemas as JTD instead of JSON Schema, then you can explicitly set this option to `false` to make sure the schemas you receive are unmodified and are not being treated internally as JSON Schema.
|
|
259
259
|
|
|
260
260
|
```js
|
|
261
261
|
const AjvJTD = require('ajv/dist/jtd'/* only valid for AJV v7+ */)
|
|
@@ -298,6 +298,8 @@ fastify.get('/user/:username', (request, reply) => {
|
|
|
298
298
|
Please note that setting this option to `false` goes against
|
|
299
299
|
[RFC3986](https://tools.ietf.org/html/rfc3986#section-6.2.2.1).
|
|
300
300
|
|
|
301
|
+
Also note, this setting will not affect query strings. If you want to change the way query strings are handled take a look at [`querystringParser`](./Server.md#querystringParser).
|
|
302
|
+
|
|
301
303
|
<a name="factory-request-id-header"></a>
|
|
302
304
|
### `requestIdHeader`
|
|
303
305
|
|
|
@@ -333,7 +335,7 @@ const fastify = require('fastify')({
|
|
|
333
335
|
<a name="factory-trust-proxy"></a>
|
|
334
336
|
### `trustProxy`
|
|
335
337
|
|
|
336
|
-
By enabling the `trustProxy` option, Fastify will
|
|
338
|
+
By enabling the `trustProxy` option, Fastify will know that it is sitting behind a proxy and that the `X-Forwarded-*` header fields may be trusted, which otherwise may be easily spoofed.
|
|
337
339
|
|
|
338
340
|
```js
|
|
339
341
|
const fastify = Fastify({ trustProxy: true })
|
|
@@ -388,6 +390,17 @@ const fastify = require('fastify')({
|
|
|
388
390
|
})
|
|
389
391
|
```
|
|
390
392
|
|
|
393
|
+
You can also use Fastify's default parser but change some handling behaviour, like the example below for case insensitive keys and values:
|
|
394
|
+
|
|
395
|
+
```js
|
|
396
|
+
const querystring = require('querystring')
|
|
397
|
+
const fastify = require('fastify')({
|
|
398
|
+
querystringParser: str => querystring.parse(str.toLowerCase())
|
|
399
|
+
})
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Note, if you only want the keys (and not the values) to be case insensitive we recommend using a custom parser to convert only the keys to lowercase.
|
|
403
|
+
|
|
391
404
|
<a name="exposeHeadRoutes"></a>
|
|
392
405
|
### `exposeHeadRoutes`
|
|
393
406
|
|
|
@@ -497,7 +510,7 @@ increased to fit the use case. Node core defaults this to `0`. `
|
|
|
497
510
|
+ Default: `null`
|
|
498
511
|
|
|
499
512
|
Fastify provides default error handlers for the most common use cases.
|
|
500
|
-
|
|
513
|
+
It is possible to override one or more of those handlers with custom code using this option.
|
|
501
514
|
|
|
502
515
|
*Note: Only `FST_ERR_BAD_URL` is implemented at the moment.*
|
|
503
516
|
|
|
@@ -519,7 +532,7 @@ const fastify = require('fastify')({
|
|
|
519
532
|
|
|
520
533
|
Set a [clientErrorHandler](https://nodejs.org/api/http.html#http_event_clienterror) that listens to `error` events emitted by client connections and responds with a `400`.
|
|
521
534
|
|
|
522
|
-
|
|
535
|
+
It is possible to override the default `clientErrorHandler` using this option.
|
|
523
536
|
|
|
524
537
|
+ Default:
|
|
525
538
|
```js
|
|
@@ -649,7 +662,7 @@ fastify.ready().then(() => {
|
|
|
649
662
|
|
|
650
663
|
<a name="listen"></a>
|
|
651
664
|
#### listen
|
|
652
|
-
Starts the server on the given port after all the plugins are loaded, internally waits for the `.ready()` event. The callback is the same as the Node core. By default, the server will listen on the address resolved by `localhost` when no specific address is provided (`127.0.0.1` or `::1` depending on the operating system). If listening on any available interface is desired, then specifying `0.0.0.0` for the address will listen on all IPv4
|
|
665
|
+
Starts the server on the given port after all the plugins are loaded, internally waits for the `.ready()` event. The callback is the same as the Node core. By default, the server will listen on the address resolved by `localhost` when no specific address is provided (`127.0.0.1` or `::1` depending on the operating system). If listening on any available interface is desired, then specifying `0.0.0.0` for the address will listen on all IPv4 addresses. Using `::` for the address will listen on all IPv6 addresses and, depending on OS, may also listen on all IPv4 addresses. Be careful when deciding to listen on all interfaces; it comes with inherent [security risks](https://web.archive.org/web/20170831174611/https://snyk.io/blog/mongodb-hack-and-secure-defaults/).
|
|
653
666
|
|
|
654
667
|
```js
|
|
655
668
|
fastify.listen(3000, (err, address) => {
|
|
@@ -962,15 +975,15 @@ const fastify = Fastify({
|
|
|
962
975
|
},
|
|
963
976
|
|
|
964
977
|
/**
|
|
965
|
-
* The compilers factory let you
|
|
978
|
+
* The compilers factory let you fully control the validator and serializer
|
|
966
979
|
* in the Fastify's lifecycle, providing the encapsulation to your compilers.
|
|
967
980
|
*/
|
|
968
981
|
compilersFactory: {
|
|
969
982
|
/**
|
|
970
983
|
* This factory is called whenever a new validator instance is needed.
|
|
971
|
-
* It may be called whenever `fastify.register()` is called only if new schemas
|
|
984
|
+
* It may be called whenever `fastify.register()` is called only if new schemas have been added to the
|
|
972
985
|
* encapsulation context.
|
|
973
|
-
* It may receive as input the schemas of the parent context if some schemas
|
|
986
|
+
* It may receive as input the schemas of the parent context if some schemas have been added.
|
|
974
987
|
* @param {object} externalSchemas these schemas will be returned by the `bucket.getSchemas()`. Needed to resolve the external references $ref.
|
|
975
988
|
* @param {object} ajvServerOption the server `ajv` options to build your compilers accordingly
|
|
976
989
|
*/
|
|
@@ -985,9 +998,9 @@ const fastify = Fastify({
|
|
|
985
998
|
|
|
986
999
|
/**
|
|
987
1000
|
* This factory is called whenever a new serializer instance is needed.
|
|
988
|
-
* It may be called whenever `fastify.register()` is called only if new schemas
|
|
1001
|
+
* It may be called whenever `fastify.register()` is called only if new schemas have been added to the
|
|
989
1002
|
* encapsulation context.
|
|
990
|
-
* It may receive as input the schemas of the parent context if some schemas
|
|
1003
|
+
* It may receive as input the schemas of the parent context if some schemas have been added.
|
|
991
1004
|
* @param {object} externalSchemas these schemas will be returned by the `bucket.getSchemas()`. Needed to resolve the external references $ref.
|
|
992
1005
|
* @param {object} serializerOptsServerOption the server `serializerOpts` options to build your compilers accordingly
|
|
993
1006
|
*/
|
|
@@ -1006,7 +1019,7 @@ const fastify = Fastify({
|
|
|
1006
1019
|
##### Ajv 8 as default schema validator
|
|
1007
1020
|
|
|
1008
1021
|
Ajv 8 is the evolution of Ajv 6, and it has a lot of improvements and new features.
|
|
1009
|
-
To use the new Ajv 8 features such as JTD or the Standalone mode,
|
|
1022
|
+
To use the new Ajv 8 features such as JTD or the Standalone mode, refer to the [`@fastify/ajv-compiler` documentation](https://github.com/fastify/ajv-compiler#usage).
|
|
1010
1023
|
|
|
1011
1024
|
To use Ajv 8 as default schema validator, you can use the following code:
|
|
1012
1025
|
|
|
@@ -1031,7 +1044,7 @@ const app = fastify({
|
|
|
1031
1044
|
}
|
|
1032
1045
|
})
|
|
1033
1046
|
|
|
1034
|
-
// Done! You can now use Ajv 8 options and keywords
|
|
1047
|
+
// Done! You can now use Ajv 8 options and keywords in your schemas!
|
|
1035
1048
|
```
|
|
1036
1049
|
|
|
1037
1050
|
<a name="set-not-found-handler"></a>
|
package/lib/decorate.js
CHANGED
|
@@ -22,21 +22,35 @@ function decorate (instance, name, fn, dependencies) {
|
|
|
22
22
|
throw new FST_ERR_DEC_ALREADY_PRESENT(name)
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
if (!Array.isArray(dependencies)) {
|
|
27
|
-
throw new FST_ERR_DEC_DEPENDENCY_INVALID_TYPE(name)
|
|
28
|
-
}
|
|
25
|
+
checkDependencies(instance, name, dependencies)
|
|
29
26
|
|
|
30
|
-
|
|
27
|
+
if (fn && (typeof fn.getter === 'function' || typeof fn.setter === 'function')) {
|
|
28
|
+
Object.defineProperty(instance, name, {
|
|
29
|
+
get: fn.getter,
|
|
30
|
+
set: fn.setter
|
|
31
|
+
})
|
|
32
|
+
} else {
|
|
33
|
+
instance[name] = fn
|
|
31
34
|
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function decorateConstructor (konstructor, name, fn, dependencies) {
|
|
38
|
+
const instance = konstructor.prototype
|
|
39
|
+
if (instance.hasOwnProperty(name) || konstructor.props.includes(name)) {
|
|
40
|
+
throw new FST_ERR_DEC_ALREADY_PRESENT(name)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
checkDependencies(instance, name, dependencies)
|
|
32
44
|
|
|
33
45
|
if (fn && (typeof fn.getter === 'function' || typeof fn.setter === 'function')) {
|
|
34
46
|
Object.defineProperty(instance, name, {
|
|
35
47
|
get: fn.getter,
|
|
36
48
|
set: fn.setter
|
|
37
49
|
})
|
|
38
|
-
} else {
|
|
50
|
+
} else if (fn !== undefined && fn !== null) {
|
|
39
51
|
instance[name] = fn
|
|
52
|
+
} else {
|
|
53
|
+
konstructor.props.push(name)
|
|
40
54
|
}
|
|
41
55
|
}
|
|
42
56
|
|
|
@@ -61,14 +75,24 @@ function checkExistence (instance, name) {
|
|
|
61
75
|
}
|
|
62
76
|
|
|
63
77
|
function checkRequestExistence (name) {
|
|
78
|
+
if (name && this[kRequest].props.includes(name)) return true
|
|
64
79
|
return checkExistence(this[kRequest].prototype, name)
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
function checkReplyExistence (name) {
|
|
83
|
+
if (name && this[kReply].props.includes(name)) return true
|
|
68
84
|
return checkExistence(this[kReply].prototype, name)
|
|
69
85
|
}
|
|
70
86
|
|
|
71
|
-
function checkDependencies (instance, deps) {
|
|
87
|
+
function checkDependencies (instance, name, deps) {
|
|
88
|
+
if (deps === undefined || deps === null) {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!Array.isArray(deps)) {
|
|
93
|
+
throw new FST_ERR_DEC_DEPENDENCY_INVALID_TYPE(name)
|
|
94
|
+
}
|
|
95
|
+
|
|
72
96
|
// eslint-disable-next-line no-var
|
|
73
97
|
for (var i = 0; i !== deps.length; ++i) {
|
|
74
98
|
if (!checkExistence(instance, deps[i])) {
|
|
@@ -80,14 +104,14 @@ function checkDependencies (instance, deps) {
|
|
|
80
104
|
function decorateReply (name, fn, dependencies) {
|
|
81
105
|
assertNotStarted(this, name)
|
|
82
106
|
checkReferenceType(name, fn)
|
|
83
|
-
|
|
107
|
+
decorateConstructor(this[kReply], name, fn, dependencies)
|
|
84
108
|
return this
|
|
85
109
|
}
|
|
86
110
|
|
|
87
111
|
function decorateRequest (name, fn, dependencies) {
|
|
88
112
|
assertNotStarted(this, name)
|
|
89
113
|
checkReferenceType(name, fn)
|
|
90
|
-
|
|
114
|
+
decorateConstructor(this[kRequest], name, fn, dependencies)
|
|
91
115
|
return this
|
|
92
116
|
}
|
|
93
117
|
|
package/lib/pluginUtils.js
CHANGED
|
@@ -4,10 +4,9 @@ const semver = require('semver')
|
|
|
4
4
|
const assert = require('assert')
|
|
5
5
|
const registeredPlugins = Symbol.for('registered-plugin')
|
|
6
6
|
const {
|
|
7
|
-
kReply,
|
|
8
|
-
kRequest,
|
|
9
7
|
kTestInternals
|
|
10
8
|
} = require('./symbols.js')
|
|
9
|
+
const { exist, existReply, existRequest } = require('./decorate')
|
|
11
10
|
const { FST_ERR_PLUGIN_VERSION_MISMATCH } = require('./errors')
|
|
12
11
|
|
|
13
12
|
function getMeta (fn) {
|
|
@@ -71,20 +70,25 @@ function checkDecorators (fn) {
|
|
|
71
70
|
const { decorators, name } = meta
|
|
72
71
|
if (!decorators) return
|
|
73
72
|
|
|
74
|
-
if (decorators.fastify) _checkDecorators
|
|
75
|
-
if (decorators.reply) _checkDecorators
|
|
76
|
-
if (decorators.request) _checkDecorators
|
|
73
|
+
if (decorators.fastify) _checkDecorators(this, 'Fastify', decorators.fastify, name)
|
|
74
|
+
if (decorators.reply) _checkDecorators(this, 'Reply', decorators.reply, name)
|
|
75
|
+
if (decorators.request) _checkDecorators(this, 'Request', decorators.request, name)
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
|
|
78
|
+
const checks = {
|
|
79
|
+
Fastify: exist,
|
|
80
|
+
Request: existRequest,
|
|
81
|
+
Reply: existReply
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function _checkDecorators (that, instance, decorators, name) {
|
|
80
85
|
assert(Array.isArray(decorators), 'The decorators should be an array of strings')
|
|
81
86
|
|
|
82
87
|
decorators.forEach(decorator => {
|
|
83
88
|
const withPluginName = typeof name === 'string' ? ` required by '${name}'` : ''
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
)
|
|
89
|
+
if (!checks[instance].call(that, decorator)) {
|
|
90
|
+
throw new Error(`The decorator '${decorator}'${withPluginName} is not present in ${instance}`)
|
|
91
|
+
}
|
|
88
92
|
})
|
|
89
93
|
}
|
|
90
94
|
|
package/lib/reply.js
CHANGED
|
@@ -63,6 +63,7 @@ function Reply (res, request, log) {
|
|
|
63
63
|
this[kReplyStartTime] = undefined
|
|
64
64
|
this.log = log
|
|
65
65
|
}
|
|
66
|
+
Reply.props = []
|
|
66
67
|
|
|
67
68
|
Object.defineProperties(Reply.prototype, {
|
|
68
69
|
context: {
|
|
@@ -422,6 +423,8 @@ function onSendEnd (reply, payload) {
|
|
|
422
423
|
}
|
|
423
424
|
|
|
424
425
|
if (typeof payload.pipe === 'function') {
|
|
426
|
+
reply[kReplySent] = true
|
|
427
|
+
|
|
425
428
|
sendStream(payload, res, reply)
|
|
426
429
|
return
|
|
427
430
|
}
|
|
@@ -656,6 +659,8 @@ function onResponseCallback (err, request, reply) {
|
|
|
656
659
|
}
|
|
657
660
|
|
|
658
661
|
function buildReply (R) {
|
|
662
|
+
const props = [...R.props]
|
|
663
|
+
|
|
659
664
|
function _Reply (res, request, log) {
|
|
660
665
|
this.raw = res
|
|
661
666
|
this[kReplyIsError] = false
|
|
@@ -667,8 +672,14 @@ function buildReply (R) {
|
|
|
667
672
|
this[kReplyHeaders] = {}
|
|
668
673
|
this[kReplyStartTime] = undefined
|
|
669
674
|
this.log = log
|
|
675
|
+
|
|
676
|
+
// eslint-disable-next-line no-var
|
|
677
|
+
for (var i = 0; i < props.length; i++) {
|
|
678
|
+
this[props[i]] = null
|
|
679
|
+
}
|
|
670
680
|
}
|
|
671
681
|
_Reply.prototype = new R()
|
|
682
|
+
_Reply.props = props
|
|
672
683
|
return _Reply
|
|
673
684
|
}
|
|
674
685
|
|
package/lib/request.js
CHANGED
|
@@ -13,6 +13,7 @@ function Request (id, params, req, query, log, context) {
|
|
|
13
13
|
this.log = log
|
|
14
14
|
this.body = null
|
|
15
15
|
}
|
|
16
|
+
Request.props = []
|
|
16
17
|
|
|
17
18
|
function getTrustProxyFn (tp) {
|
|
18
19
|
if (typeof tp === 'function') {
|
|
@@ -43,6 +44,7 @@ function buildRequest (R, trustProxy) {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
function buildRegularRequest (R) {
|
|
47
|
+
const props = [...R.props]
|
|
46
48
|
function _Request (id, params, req, query, log, context) {
|
|
47
49
|
this.id = id
|
|
48
50
|
this.context = context
|
|
@@ -51,8 +53,14 @@ function buildRegularRequest (R) {
|
|
|
51
53
|
this.query = query
|
|
52
54
|
this.log = log
|
|
53
55
|
this.body = null
|
|
56
|
+
|
|
57
|
+
// eslint-disable-next-line no-var
|
|
58
|
+
for (var i = 0; i < props.length; i++) {
|
|
59
|
+
this[props[i]] = null
|
|
60
|
+
}
|
|
54
61
|
}
|
|
55
62
|
_Request.prototype = new R()
|
|
63
|
+
_Request.props = props
|
|
56
64
|
|
|
57
65
|
return _Request
|
|
58
66
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "3.21.
|
|
3
|
+
"version": "3.21.5",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -194,7 +194,8 @@
|
|
|
194
194
|
"lib/configValidator.js",
|
|
195
195
|
"fastify.d.ts",
|
|
196
196
|
"types/*",
|
|
197
|
-
"test/types/*"
|
|
197
|
+
"test/types/*",
|
|
198
|
+
"test/same-shape.test.js"
|
|
198
199
|
]
|
|
199
200
|
},
|
|
200
201
|
"tsd": {
|
package/test/decorator.test.js
CHANGED
|
@@ -17,6 +17,15 @@ test('server methods should exist', t => {
|
|
|
17
17
|
t.ok(fastify.hasDecorator)
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
+
test('should check if the given decoration already exist when null', t => {
|
|
21
|
+
t.plan(1)
|
|
22
|
+
const fastify = Fastify()
|
|
23
|
+
fastify.decorate('null', null)
|
|
24
|
+
fastify.ready(() => {
|
|
25
|
+
t.ok(fastify.hasDecorator('null'))
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
20
29
|
test('server methods should be encapsulated via .register', t => {
|
|
21
30
|
t.plan(2)
|
|
22
31
|
const fastify = Fastify()
|
|
@@ -425,6 +434,15 @@ test('hasRequestDecorator', t => {
|
|
|
425
434
|
t.ok(fastify.hasRequestDecorator(requestDecoratorName))
|
|
426
435
|
})
|
|
427
436
|
|
|
437
|
+
t.test('should check if the given request decoration already exist when null', t => {
|
|
438
|
+
t.plan(2)
|
|
439
|
+
const fastify = Fastify()
|
|
440
|
+
|
|
441
|
+
t.notOk(fastify.hasRequestDecorator(requestDecoratorName))
|
|
442
|
+
fastify.decorateRequest(requestDecoratorName, null)
|
|
443
|
+
t.ok(fastify.hasRequestDecorator(requestDecoratorName))
|
|
444
|
+
})
|
|
445
|
+
|
|
428
446
|
t.test('should be plugin encapsulable', t => {
|
|
429
447
|
t.plan(4)
|
|
430
448
|
const fastify = Fastify()
|
|
@@ -481,6 +499,15 @@ test('hasReplyDecorator', t => {
|
|
|
481
499
|
t.ok(fastify.hasReplyDecorator(replyDecoratorName))
|
|
482
500
|
})
|
|
483
501
|
|
|
502
|
+
t.test('should check if the given reply decoration already exist when null', t => {
|
|
503
|
+
t.plan(2)
|
|
504
|
+
const fastify = Fastify()
|
|
505
|
+
|
|
506
|
+
t.notOk(fastify.hasReplyDecorator(replyDecoratorName))
|
|
507
|
+
fastify.decorateReply(replyDecoratorName, null)
|
|
508
|
+
t.ok(fastify.hasReplyDecorator(replyDecoratorName))
|
|
509
|
+
})
|
|
510
|
+
|
|
484
511
|
t.test('should be plugin encapsulable', t => {
|
|
485
512
|
t.plan(4)
|
|
486
513
|
const fastify = Fastify()
|
|
@@ -877,3 +904,125 @@ test('Request/reply decorators should be able to access the server instance', as
|
|
|
877
904
|
t.equal(this.server.foo, 'bar')
|
|
878
905
|
}
|
|
879
906
|
})
|
|
907
|
+
|
|
908
|
+
test('plugin required decorators', async t => {
|
|
909
|
+
const plugin1 = fp(
|
|
910
|
+
async (instance) => {
|
|
911
|
+
instance.decorateRequest('someThing', null)
|
|
912
|
+
|
|
913
|
+
instance.addHook('onRequest', async (request, reply) => {
|
|
914
|
+
request.someThing = 'hello'
|
|
915
|
+
})
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
name: 'custom-plugin-one',
|
|
919
|
+
fastify: '3.x'
|
|
920
|
+
}
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
const plugin2 = fp(
|
|
924
|
+
async () => {
|
|
925
|
+
// nothing
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
name: 'custom-plugin-two',
|
|
929
|
+
fastify: '3.x',
|
|
930
|
+
dependencies: ['custom-plugin-one'],
|
|
931
|
+
decorators: {
|
|
932
|
+
request: ['someThing']
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
)
|
|
936
|
+
|
|
937
|
+
const app = Fastify()
|
|
938
|
+
app.register(plugin1)
|
|
939
|
+
app.register(plugin2)
|
|
940
|
+
await app.ready()
|
|
941
|
+
})
|
|
942
|
+
|
|
943
|
+
test('decorateRequest/decorateReply empty string', t => {
|
|
944
|
+
t.plan(7)
|
|
945
|
+
const fastify = Fastify()
|
|
946
|
+
|
|
947
|
+
fastify.decorateRequest('test', '')
|
|
948
|
+
fastify.decorateReply('test2', '')
|
|
949
|
+
fastify.get('/yes', (req, reply) => {
|
|
950
|
+
t.equal(req.test, '')
|
|
951
|
+
t.equal(reply.test2, '')
|
|
952
|
+
reply.send({ hello: 'world' })
|
|
953
|
+
})
|
|
954
|
+
t.teardown(fastify.close.bind(fastify))
|
|
955
|
+
|
|
956
|
+
fastify.listen(0, err => {
|
|
957
|
+
t.error(err)
|
|
958
|
+
fastify.server.unref()
|
|
959
|
+
|
|
960
|
+
sget({
|
|
961
|
+
method: 'GET',
|
|
962
|
+
url: 'http://localhost:' + fastify.server.address().port + '/yes'
|
|
963
|
+
}, (err, response, body) => {
|
|
964
|
+
t.error(err)
|
|
965
|
+
t.equal(response.statusCode, 200)
|
|
966
|
+
t.equal(response.headers['content-length'], '' + body.length)
|
|
967
|
+
t.same(JSON.parse(body), { hello: 'world' })
|
|
968
|
+
})
|
|
969
|
+
})
|
|
970
|
+
})
|
|
971
|
+
|
|
972
|
+
test('decorateRequest/decorateReply is undefined', t => {
|
|
973
|
+
t.plan(7)
|
|
974
|
+
const fastify = Fastify()
|
|
975
|
+
|
|
976
|
+
fastify.decorateRequest('test', undefined)
|
|
977
|
+
fastify.decorateReply('test2', undefined)
|
|
978
|
+
fastify.get('/yes', (req, reply) => {
|
|
979
|
+
t.equal(req.test, null)
|
|
980
|
+
t.equal(reply.test2, null)
|
|
981
|
+
reply.send({ hello: 'world' })
|
|
982
|
+
})
|
|
983
|
+
t.teardown(fastify.close.bind(fastify))
|
|
984
|
+
|
|
985
|
+
fastify.listen(0, err => {
|
|
986
|
+
t.error(err)
|
|
987
|
+
fastify.server.unref()
|
|
988
|
+
|
|
989
|
+
sget({
|
|
990
|
+
method: 'GET',
|
|
991
|
+
url: 'http://localhost:' + fastify.server.address().port + '/yes'
|
|
992
|
+
}, (err, response, body) => {
|
|
993
|
+
t.error(err)
|
|
994
|
+
t.equal(response.statusCode, 200)
|
|
995
|
+
t.equal(response.headers['content-length'], '' + body.length)
|
|
996
|
+
t.same(JSON.parse(body), { hello: 'world' })
|
|
997
|
+
})
|
|
998
|
+
})
|
|
999
|
+
})
|
|
1000
|
+
|
|
1001
|
+
test('decorateRequest/decorateReply is not set to a value', t => {
|
|
1002
|
+
t.plan(7)
|
|
1003
|
+
const fastify = Fastify()
|
|
1004
|
+
|
|
1005
|
+
fastify.decorateRequest('test')
|
|
1006
|
+
fastify.decorateReply('test2')
|
|
1007
|
+
fastify.get('/yes', (req, reply) => {
|
|
1008
|
+
t.equal(req.test, null)
|
|
1009
|
+
t.equal(reply.test2, null)
|
|
1010
|
+
reply.send({ hello: 'world' })
|
|
1011
|
+
})
|
|
1012
|
+
t.teardown(fastify.close.bind(fastify))
|
|
1013
|
+
|
|
1014
|
+
fastify.listen(0, err => {
|
|
1015
|
+
t.error(err)
|
|
1016
|
+
fastify.server.unref()
|
|
1017
|
+
|
|
1018
|
+
sget({
|
|
1019
|
+
method: 'GET',
|
|
1020
|
+
url: 'http://localhost:' + fastify.server.address().port + '/yes'
|
|
1021
|
+
}, (err, response, body) => {
|
|
1022
|
+
t.error(err)
|
|
1023
|
+
t.equal(response.statusCode, 200)
|
|
1024
|
+
t.equal(response.headers['content-length'], '' + body.length)
|
|
1025
|
+
t.same(JSON.parse(body), { hello: 'world' })
|
|
1026
|
+
})
|
|
1027
|
+
})
|
|
1028
|
+
})
|
|
@@ -89,7 +89,7 @@ test('checkDependencies should throw if a dependency is not present', t => {
|
|
|
89
89
|
t.plan(2)
|
|
90
90
|
const instance = {}
|
|
91
91
|
try {
|
|
92
|
-
decorator.dependencies(instance, ['test'])
|
|
92
|
+
decorator.dependencies(instance, 'foo', ['test'])
|
|
93
93
|
t.fail()
|
|
94
94
|
} catch (e) {
|
|
95
95
|
t.equal(e.code, 'FST_ERR_DEC_MISSING_DEPENDENCY')
|
|
@@ -53,8 +53,8 @@ test('checkDecorators should check if the given decorator is present in the inst
|
|
|
53
53
|
|
|
54
54
|
function context () {}
|
|
55
55
|
context.plugin = true
|
|
56
|
-
context[symbols.kReply] = { prototype: { plugin: true } }
|
|
57
|
-
context[symbols.kRequest] = { prototype: { plugin: true } }
|
|
56
|
+
context[symbols.kReply] = { prototype: { plugin: true }, props: [] }
|
|
57
|
+
context[symbols.kRequest] = { prototype: { plugin: true }, props: [] }
|
|
58
58
|
|
|
59
59
|
try {
|
|
60
60
|
pluginUtils.checkDecorators.call(context, fn)
|
|
@@ -79,8 +79,8 @@ test('checkDecorators should check if the given decorator is present in the inst
|
|
|
79
79
|
|
|
80
80
|
function context () {}
|
|
81
81
|
context.plugin = true
|
|
82
|
-
context[symbols.kReply] = { prototype: { plugin: true } }
|
|
83
|
-
context[symbols.kRequest] = { prototype: {} }
|
|
82
|
+
context[symbols.kReply] = { prototype: { plugin: true }, props: [] }
|
|
83
|
+
context[symbols.kRequest] = { prototype: {}, props: [] }
|
|
84
84
|
|
|
85
85
|
try {
|
|
86
86
|
pluginUtils.checkDecorators.call(context, fn)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('tap')
|
|
4
|
+
const fastify = require('..')
|
|
5
|
+
|
|
6
|
+
test('same shape on Request', async (t) => {
|
|
7
|
+
t.plan(1)
|
|
8
|
+
|
|
9
|
+
const app = fastify()
|
|
10
|
+
|
|
11
|
+
let request
|
|
12
|
+
|
|
13
|
+
app.decorateRequest('user')
|
|
14
|
+
|
|
15
|
+
app.addHook('preHandler', (req, reply, done) => {
|
|
16
|
+
if (request) {
|
|
17
|
+
req.user = 'User'
|
|
18
|
+
}
|
|
19
|
+
done()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
app.get('/', (req, reply) => {
|
|
23
|
+
if (request) {
|
|
24
|
+
t.equal(%HaveSameMap(request, req), true)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
request = req
|
|
28
|
+
|
|
29
|
+
return 'hello world'
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
await app.inject('/')
|
|
33
|
+
await app.inject('/')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('same shape on Request when object', async (t) => {
|
|
37
|
+
t.plan(1)
|
|
38
|
+
|
|
39
|
+
const app = fastify()
|
|
40
|
+
|
|
41
|
+
let request
|
|
42
|
+
|
|
43
|
+
app.decorateRequest('object', null)
|
|
44
|
+
|
|
45
|
+
app.addHook('preHandler', (req, reply, done) => {
|
|
46
|
+
if (request) {
|
|
47
|
+
req.object = {}
|
|
48
|
+
}
|
|
49
|
+
done()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
app.get('/', (req, reply) => {
|
|
53
|
+
if (request) {
|
|
54
|
+
t.equal(%HaveSameMap(request, req), true)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
request = req
|
|
58
|
+
|
|
59
|
+
return 'hello world'
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
await app.inject('/')
|
|
63
|
+
await app.inject('/')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('same shape on Reply', async (t) => {
|
|
67
|
+
t.plan(1)
|
|
68
|
+
|
|
69
|
+
const app = fastify()
|
|
70
|
+
|
|
71
|
+
let _reply
|
|
72
|
+
|
|
73
|
+
app.decorateReply('user')
|
|
74
|
+
|
|
75
|
+
app.addHook('preHandler', (req, reply, done) => {
|
|
76
|
+
if (_reply) {
|
|
77
|
+
reply.user = 'User'
|
|
78
|
+
}
|
|
79
|
+
done()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
app.get('/', (req, reply) => {
|
|
83
|
+
if (_reply) {
|
|
84
|
+
t.equal(%HaveSameMap(_reply, reply), true)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_reply = reply
|
|
88
|
+
|
|
89
|
+
return 'hello world'
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
await app.inject('/')
|
|
93
|
+
await app.inject('/')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('same shape on Reply when object', async (t) => {
|
|
97
|
+
t.plan(1)
|
|
98
|
+
|
|
99
|
+
const app = fastify()
|
|
100
|
+
|
|
101
|
+
let _reply
|
|
102
|
+
|
|
103
|
+
app.decorateReply('object', null)
|
|
104
|
+
|
|
105
|
+
app.addHook('preHandler', (req, reply, done) => {
|
|
106
|
+
if (_reply) {
|
|
107
|
+
reply.object = {}
|
|
108
|
+
}
|
|
109
|
+
done()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
app.get('/', (req, reply) => {
|
|
113
|
+
if (_reply) {
|
|
114
|
+
t.equal(%HaveSameMap(_reply, reply), true)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_reply = reply
|
|
118
|
+
|
|
119
|
+
return 'hello world'
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
await app.inject('/')
|
|
123
|
+
await app.inject('/')
|
|
124
|
+
})
|
package/test/stream.test.js
CHANGED
|
@@ -14,7 +14,7 @@ const JSONStream = require('JSONStream')
|
|
|
14
14
|
const send = require('send')
|
|
15
15
|
const Readable = require('stream').Readable
|
|
16
16
|
const split = require('split2')
|
|
17
|
-
const { kDisableRequestLogging } = require('../lib/symbols.js')
|
|
17
|
+
const { kDisableRequestLogging, kReplySent } = require('../lib/symbols.js')
|
|
18
18
|
|
|
19
19
|
test('should respond with a stream', t => {
|
|
20
20
|
t.plan(8)
|
|
@@ -636,3 +636,39 @@ test('should destroy stream when response is ended', t => {
|
|
|
636
636
|
})
|
|
637
637
|
})
|
|
638
638
|
})
|
|
639
|
+
|
|
640
|
+
test('should mark reply as sent before pumping the payload stream into response for async route handler', t => {
|
|
641
|
+
t.plan(3)
|
|
642
|
+
|
|
643
|
+
const handleRequest = proxyquire('../lib/handleRequest', {
|
|
644
|
+
'./wrapThenable': (thenable, reply) => {
|
|
645
|
+
thenable.then(function (payload) {
|
|
646
|
+
t.equal(reply[kReplySent], true)
|
|
647
|
+
})
|
|
648
|
+
}
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
const route = proxyquire('../lib/route', {
|
|
652
|
+
'./handleRequest': handleRequest
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
const Fastify = proxyquire('..', {
|
|
656
|
+
'./lib/route': route
|
|
657
|
+
})
|
|
658
|
+
|
|
659
|
+
const fastify = Fastify()
|
|
660
|
+
|
|
661
|
+
fastify.get('/', async function (req, reply) {
|
|
662
|
+
const stream = fs.createReadStream(__filename, 'utf8')
|
|
663
|
+
reply.code(200).send(stream)
|
|
664
|
+
})
|
|
665
|
+
|
|
666
|
+
fastify.inject({
|
|
667
|
+
url: '/',
|
|
668
|
+
method: 'GET'
|
|
669
|
+
}, (err, res) => {
|
|
670
|
+
t.error(err)
|
|
671
|
+
t.equal(res.payload, fs.readFileSync(__filename, 'utf8'))
|
|
672
|
+
fastify.close()
|
|
673
|
+
})
|
|
674
|
+
})
|