fastify 3.16.0 → 3.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GOVERNANCE.md +1 -1
- package/docs/Ecosystem.md +1 -0
- package/docs/Reply.md +16 -0
- package/docs/Request.md +2 -0
- package/docs/Server.md +31 -4
- package/docs/TypeScript.md +9 -1
- package/fastify.d.ts +1 -1
- package/fastify.js +27 -22
- package/lib/handleRequest.js +6 -2
- package/lib/hooks.js +2 -1
- package/lib/pluginOverride.js +5 -0
- package/lib/pluginUtils.js +1 -3
- package/lib/reply.js +21 -12
- package/lib/request.js +5 -1
- package/lib/route.js +1 -1
- package/lib/symbols.js +1 -0
- package/lib/wrapThenable.js +6 -3
- package/package.json +2 -2
- package/test/decorator.test.js +39 -0
- package/test/internals/handleRequest.test.js +8 -3
- package/test/internals/reply.test.js +0 -16
- package/test/pretty-print.test.js +89 -0
- package/test/types/fastify.test-d.ts +13 -2
- package/test/versioned-routes.test.js +1 -1
- package/test/wrapThenable.test.js +1 -2
- package/flamegraph.html +0 -88256
- package/test/internals/version.test.js +0 -43
package/GOVERNANCE.md
CHANGED
|
@@ -80,7 +80,7 @@ The consensus to grant a new candidate Collaborator status is reached when:
|
|
|
80
80
|
- at least one of the Lead Maintainers approve
|
|
81
81
|
- at least two of the Team Members approve
|
|
82
82
|
|
|
83
|
-
After these conditions are satisfied, the [onboarding process](#onboarding-collaborators) may start.
|
|
83
|
+
After these conditions are satisfied, the [onboarding process](CONTRIBUTING.md#onboarding-collaborators) may start.
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
## Lead Maintainers nominations
|
package/docs/Ecosystem.md
CHANGED
|
@@ -63,6 +63,7 @@ Plugins maintained by the Fastify team are listed under [Core](#core) while plug
|
|
|
63
63
|
- [`@mgcrea/fastify-session-sodium-crypto`](https://github.com/mgcrea/fastify-session-sodium-crypto) Fast sodium-based crypto for @mgcrea/fastify-session
|
|
64
64
|
- [`@mgcrea/fastify-session`](https://github.com/mgcrea/fastify-session) Session plugin for Fastify that supports both stateless and stateful sessions
|
|
65
65
|
- [`@mgcrea/pino-pretty-compact`](https://github.com/mgcrea/pino-pretty-compact) A custom compact pino-base prettifier
|
|
66
|
+
- [`@trubavuong/fastify-seaweedfs`](https://github.com/trubavuong/fastify-seaweedfs) SeaweedFS for Fastify
|
|
66
67
|
- [`apollo-server-fastify`](https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-fastify) Run an [Apollo Server](https://github.com/apollographql/apollo-server) to serve GraphQL with Fastify.
|
|
67
68
|
- [`arecibo`](https://github.com/nucleode/arecibo) Fastify ping responder for Kubernetes Liveness and Readiness Probes.
|
|
68
69
|
- [`cls-rtracer`](https://github.com/puzpuzpuz/cls-rtracer) Fastify middleware for CLS-based request ID generation. An out-of-the-box solution for adding request IDs into your logs.
|
package/docs/Reply.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
- [Introduction](#introduction)
|
|
6
6
|
- [.code(statusCode)](#codestatuscode)
|
|
7
7
|
- [.statusCode](#statusCode)
|
|
8
|
+
- [.server](#server)
|
|
8
9
|
- [.header(key, value)](#headerkey-value)
|
|
9
10
|
- [.headers(object)](#headersobject)
|
|
10
11
|
- [.getHeader(key)](#getheaderkey)
|
|
@@ -38,6 +39,7 @@ and properties:
|
|
|
38
39
|
- `.code(statusCode)` - Sets the status code.
|
|
39
40
|
- `.status(statusCode)` - An alias for `.code(statusCode)`.
|
|
40
41
|
- `.statusCode` - Read and set the HTTP status code.
|
|
42
|
+
- `.server` - A reference to the fastify instance object.
|
|
41
43
|
- `.header(name, value)` - Sets a response header.
|
|
42
44
|
- `.headers(object)` - Sets all the keys of the object as response headers.
|
|
43
45
|
- `.getHeader(name)` - Retrieve value of already set header.
|
|
@@ -87,6 +89,20 @@ if (reply.statusCode >= 299) {
|
|
|
87
89
|
}
|
|
88
90
|
```
|
|
89
91
|
|
|
92
|
+
<a name="server"></a>
|
|
93
|
+
### .server
|
|
94
|
+
The Fastify server instance, scoped to the current [encapsulation context](Encapsulation.md).
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
fastify.decorate('util', function util () {
|
|
98
|
+
return 'foo'
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
fastify.get('/', async function (req, rep) {
|
|
102
|
+
return rep.server.util() // foo
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
90
106
|
<a name="header"></a>
|
|
91
107
|
### .header(key, value)
|
|
92
108
|
Sets a response header. If the value is omitted or undefined, it is coerced
|
package/docs/Request.md
CHANGED
|
@@ -9,6 +9,7 @@ Request is a core Fastify object containing the following fields:
|
|
|
9
9
|
- `headers` - the headers
|
|
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
|
+
- `server` - The Fastify server instance, scoped to the current [encapsulation context](Encapsulation.md)
|
|
12
13
|
- `id` - the request id
|
|
13
14
|
- `log` - the logger instance of the incoming request
|
|
14
15
|
- `ip` - the IP address of the incoming request
|
|
@@ -31,6 +32,7 @@ fastify.post('/:params', options, function (request, reply) {
|
|
|
31
32
|
console.log(request.params)
|
|
32
33
|
console.log(request.headers)
|
|
33
34
|
console.log(request.raw)
|
|
35
|
+
console.log(request.server)
|
|
34
36
|
console.log(request.id)
|
|
35
37
|
console.log(request.ip)
|
|
36
38
|
console.log(request.ips)
|
package/docs/Server.md
CHANGED
|
@@ -288,7 +288,7 @@ const fastify = Fastify({ trustProxy: true })
|
|
|
288
288
|
}
|
|
289
289
|
```
|
|
290
290
|
|
|
291
|
-
For more examples, refer to the [
|
|
291
|
+
For more examples, refer to the [`proxy-addr`](https://www.npmjs.com/package/proxy-addr) package.
|
|
292
292
|
|
|
293
293
|
You may access the `ip`, `ips`, `hostname` and `protocol` values on the [`request`](Request.md) object.
|
|
294
294
|
|
|
@@ -967,6 +967,8 @@ fastify.register(function (instance, options, done) {
|
|
|
967
967
|
}, { prefix: '/v1' })
|
|
968
968
|
```
|
|
969
969
|
|
|
970
|
+
Fastify calls setNotFoundHandler to add a default 404 handler at startup before plugins are registered. If you would like to augment the behavior of the default 404 handler, for example with plugins, you can call setNotFoundHandler with no arguments `fastify.setNotFoundHandler()` within the context of these registered plugins.
|
|
971
|
+
|
|
970
972
|
<a name="set-error-handler"></a>
|
|
971
973
|
#### setErrorHandler
|
|
972
974
|
|
|
@@ -1026,6 +1028,31 @@ fastify.ready(() => {
|
|
|
1026
1028
|
})
|
|
1027
1029
|
```
|
|
1028
1030
|
|
|
1031
|
+
`fastify.printRoutes({ includeMeta: (true | []) })` will display properties from the `route.store` object for each displayed route. This can be an `array` of keys (e.g. `['onRequest', Symbol('key')]`), or `true` to display all properties. A shorthand option, `fastify.printRoutes({ includeHooks: true })` will include all [hooks](https://www.fastify.io/docs/latest/Hooks/).
|
|
1032
|
+
|
|
1033
|
+
```js
|
|
1034
|
+
console.log(fastify.printRoutes({ includeHooks: true, includeMeta: ['metaProperty'] }))
|
|
1035
|
+
// └── /
|
|
1036
|
+
// ├── test (GET)
|
|
1037
|
+
// │ • (onRequest) ["anonymous()","namedFunction()"]
|
|
1038
|
+
// │ • (metaProperty) "value"
|
|
1039
|
+
// │ └── /hello (GET)
|
|
1040
|
+
// └── hel
|
|
1041
|
+
// ├── lo/world (GET)
|
|
1042
|
+
// │ • (onTimeout) ["anonymous()"]
|
|
1043
|
+
// └── licopter (GET)
|
|
1044
|
+
|
|
1045
|
+
console.log(fastify.printRoutes({ includeHooks: true }))
|
|
1046
|
+
// └── /
|
|
1047
|
+
// ├── test (GET)
|
|
1048
|
+
// │ • (onRequest) ["anonymous()","namedFunction()"]
|
|
1049
|
+
// │ └── /hello (GET)
|
|
1050
|
+
// └── hel
|
|
1051
|
+
// ├── lo/world (GET)
|
|
1052
|
+
// │ • (onTimeout) ["anonymous()"]
|
|
1053
|
+
// └── licopter (GET)
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1029
1056
|
<a name="print-plugins"></a>
|
|
1030
1057
|
#### printPlugins
|
|
1031
1058
|
|
|
@@ -1042,9 +1069,9 @@ fastify.ready(() => {
|
|
|
1042
1069
|
console.error(fastify.printPlugins())
|
|
1043
1070
|
// will output the following to stderr:
|
|
1044
1071
|
// └── root
|
|
1045
|
-
//
|
|
1046
|
-
//
|
|
1047
|
-
//
|
|
1072
|
+
// ├── foo
|
|
1073
|
+
// │ └── bar
|
|
1074
|
+
// └── baz
|
|
1048
1075
|
})
|
|
1049
1076
|
```
|
|
1050
1077
|
|
package/docs/TypeScript.md
CHANGED
|
@@ -265,6 +265,11 @@ In the last example we used interfaces to define the types for the request query
|
|
|
265
265
|
const { username, password } = request.query
|
|
266
266
|
done(username !== 'admin' ? new Error('Must be admin') : undefined)
|
|
267
267
|
}
|
|
268
|
+
// or if using async
|
|
269
|
+
// preValidation: async (request, reply) => {
|
|
270
|
+
// const { username, password } = request.query
|
|
271
|
+
// return username !== "admin" ? new Error("Must be admin") : undefined;
|
|
272
|
+
// }
|
|
268
273
|
}, async (request, reply) => {
|
|
269
274
|
const customerHeader = request.headers['h-Custom']
|
|
270
275
|
// do something with request data
|
|
@@ -281,13 +286,15 @@ In the last example we used interfaces to define the types for the request query
|
|
|
281
286
|
querystring: QuerystringSchema,
|
|
282
287
|
headers: HeadersSchema
|
|
283
288
|
},
|
|
284
|
-
preHandler: (request, reply) => {
|
|
289
|
+
preHandler: (request, reply, done) => {
|
|
285
290
|
const { username, password } = request.query
|
|
286
291
|
const customerHeader = request.headers['h-Custom']
|
|
292
|
+
done()
|
|
287
293
|
},
|
|
288
294
|
handler: (request, reply) => {
|
|
289
295
|
const { username, password } = request.query
|
|
290
296
|
const customerHeader = request.headers['h-Custom']
|
|
297
|
+
reply.status(200).send({username});
|
|
291
298
|
}
|
|
292
299
|
})
|
|
293
300
|
|
|
@@ -331,6 +338,7 @@ const todo = {
|
|
|
331
338
|
With the provided type `FromSchema` you can build a type from your schema and use it in your handler.
|
|
332
339
|
|
|
333
340
|
```typescript
|
|
341
|
+
import { FromSchema } from "json-schema-to-ts";
|
|
334
342
|
fastify.post<{ Body: FromSchema<typeof todo> }>(
|
|
335
343
|
'/todo',
|
|
336
344
|
{
|
package/fastify.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as http from 'http'
|
|
2
2
|
import * as http2 from 'http2'
|
|
3
3
|
import * as https from 'https'
|
|
4
|
-
import * as LightMyRequest from 'light-my-request'
|
|
5
4
|
import { ConstraintStrategy, HTTPVersion } from 'find-my-way'
|
|
6
5
|
|
|
7
6
|
import { FastifyRequest, RequestGenericInterface } from './types/request'
|
|
@@ -159,6 +158,7 @@ export interface ValidationResult {
|
|
|
159
158
|
}
|
|
160
159
|
|
|
161
160
|
/* Export all additional types */
|
|
161
|
+
export type { Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse, CallbackFunc as LightMyRequestCallback } from 'light-my-request'
|
|
162
162
|
export { FastifyRequest, RequestGenericInterface } from './types/request'
|
|
163
163
|
export { FastifyReply } from './types/reply'
|
|
164
164
|
export { FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin } from './types/plugin'
|
package/fastify.js
CHANGED
|
@@ -4,9 +4,8 @@ const Avvio = require('avvio')
|
|
|
4
4
|
const http = require('http')
|
|
5
5
|
const querystring = require('querystring')
|
|
6
6
|
let lightMyRequest
|
|
7
|
-
let version
|
|
8
|
-
let versionLoaded = false
|
|
9
7
|
|
|
8
|
+
const { version } = require('./package.json')
|
|
10
9
|
const {
|
|
11
10
|
kAvvioBoot,
|
|
12
11
|
kChildren,
|
|
@@ -35,7 +34,7 @@ const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTI
|
|
|
35
34
|
const decorator = require('./lib/decorate')
|
|
36
35
|
const ContentTypeParser = require('./lib/contentTypeParser')
|
|
37
36
|
const SchemaController = require('./lib/schema-controller')
|
|
38
|
-
const { Hooks, hookRunnerApplication } = require('./lib/hooks')
|
|
37
|
+
const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks')
|
|
39
38
|
const { createLogger } = require('./lib/logger')
|
|
40
39
|
const pluginUtils = require('./lib/pluginUtils')
|
|
41
40
|
const reqIdGenFactory = require('./lib/reqIdGenFactory')
|
|
@@ -58,6 +57,19 @@ const onBadUrlContext = {
|
|
|
58
57
|
onError: []
|
|
59
58
|
}
|
|
60
59
|
|
|
60
|
+
function defaultBuildPrettyMeta (route) {
|
|
61
|
+
// return a shallow copy of route's sanitized context
|
|
62
|
+
|
|
63
|
+
const cleanKeys = {}
|
|
64
|
+
const allowedProps = ['errorHandler', 'logLevel', 'logSerializers']
|
|
65
|
+
|
|
66
|
+
allowedProps.concat(supportedHooks).forEach(k => {
|
|
67
|
+
cleanKeys[k] = route.store[k]
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
return Object.assign({}, cleanKeys)
|
|
71
|
+
}
|
|
72
|
+
|
|
61
73
|
function defaultErrorHandler (error, request, reply) {
|
|
62
74
|
if (reply.statusCode < 500) {
|
|
63
75
|
reply.log.info(
|
|
@@ -158,7 +170,8 @@ function fastify (options) {
|
|
|
158
170
|
constraints: constraints,
|
|
159
171
|
ignoreTrailingSlash: options.ignoreTrailingSlash || defaultInitOptions.ignoreTrailingSlash,
|
|
160
172
|
maxParamLength: options.maxParamLength || defaultInitOptions.maxParamLength,
|
|
161
|
-
caseSensitive: options.caseSensitive
|
|
173
|
+
caseSensitive: options.caseSensitive,
|
|
174
|
+
buildPrettyMeta: defaultBuildPrettyMeta
|
|
162
175
|
}
|
|
163
176
|
})
|
|
164
177
|
|
|
@@ -278,7 +291,7 @@ function fastify (options) {
|
|
|
278
291
|
// fake http injection
|
|
279
292
|
inject: inject,
|
|
280
293
|
// pretty print of the registered routes
|
|
281
|
-
printRoutes
|
|
294
|
+
printRoutes,
|
|
282
295
|
// custom error handling
|
|
283
296
|
setNotFoundHandler: setNotFoundHandler,
|
|
284
297
|
setErrorHandler: setErrorHandler,
|
|
@@ -286,6 +299,9 @@ function fastify (options) {
|
|
|
286
299
|
initialConfig
|
|
287
300
|
}
|
|
288
301
|
|
|
302
|
+
fastify[kReply].prototype.server = fastify
|
|
303
|
+
fastify[kRequest].prototype.server = fastify
|
|
304
|
+
|
|
289
305
|
Object.defineProperties(fastify, {
|
|
290
306
|
pluginName: {
|
|
291
307
|
get () {
|
|
@@ -306,9 +322,6 @@ function fastify (options) {
|
|
|
306
322
|
},
|
|
307
323
|
version: {
|
|
308
324
|
get () {
|
|
309
|
-
if (versionLoaded === false) {
|
|
310
|
-
version = loadVersion()
|
|
311
|
-
}
|
|
312
325
|
return version
|
|
313
326
|
}
|
|
314
327
|
},
|
|
@@ -623,6 +636,12 @@ function fastify (options) {
|
|
|
623
636
|
this[kErrorHandler] = func.bind(this)
|
|
624
637
|
return this
|
|
625
638
|
}
|
|
639
|
+
|
|
640
|
+
function printRoutes (opts = {}) {
|
|
641
|
+
// includeHooks:true - shortcut to include all supported hooks exported by fastify.Hooks
|
|
642
|
+
opts.includeMeta = opts.includeHooks ? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks : opts.includeMeta
|
|
643
|
+
return router.printRoutes(opts)
|
|
644
|
+
}
|
|
626
645
|
}
|
|
627
646
|
|
|
628
647
|
function validateSchemaErrorFormatter (schemaErrorFormatter) {
|
|
@@ -652,20 +671,6 @@ function wrapRouting (httpHandler, { rewriteUrl, logger }) {
|
|
|
652
671
|
}
|
|
653
672
|
}
|
|
654
673
|
|
|
655
|
-
function loadVersion () {
|
|
656
|
-
versionLoaded = true
|
|
657
|
-
const fs = require('fs')
|
|
658
|
-
const path = require('path')
|
|
659
|
-
try {
|
|
660
|
-
const pkgPath = path.join(__dirname, 'package.json')
|
|
661
|
-
fs.accessSync(pkgPath, fs.constants.R_OK)
|
|
662
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath))
|
|
663
|
-
return pkg.name === 'fastify' ? pkg.version : undefined
|
|
664
|
-
} catch (e) {
|
|
665
|
-
return undefined
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
674
|
/**
|
|
670
675
|
* These export configurations enable JS and TS developers
|
|
671
676
|
* to consumer fastify in whatever way best suits their needs.
|
package/lib/handleRequest.js
CHANGED
|
@@ -75,7 +75,9 @@ function handler (request, reply) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
function preValidationCallback (err, request, reply) {
|
|
78
|
-
if (reply.sent === true
|
|
78
|
+
if (reply.sent === true ||
|
|
79
|
+
reply.raw.writableEnded === true ||
|
|
80
|
+
reply.raw.writable === false) return
|
|
79
81
|
|
|
80
82
|
if (err != null) {
|
|
81
83
|
reply.send(err)
|
|
@@ -107,7 +109,9 @@ function preValidationCallback (err, request, reply) {
|
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
function preHandlerCallback (err, request, reply) {
|
|
110
|
-
if (reply.sent
|
|
112
|
+
if (reply.sent ||
|
|
113
|
+
reply.raw.writableEnded === true ||
|
|
114
|
+
reply.raw.writable === false) return
|
|
111
115
|
|
|
112
116
|
if (err != null) {
|
|
113
117
|
reply.send(err)
|
package/lib/hooks.js
CHANGED
package/lib/pluginOverride.js
CHANGED
|
@@ -37,8 +37,13 @@ module.exports = function override (old, fn, opts) {
|
|
|
37
37
|
old[kChildren].push(instance)
|
|
38
38
|
instance.ready = old[kAvvioBoot].bind(instance)
|
|
39
39
|
instance[kChildren] = []
|
|
40
|
+
|
|
40
41
|
instance[kReply] = Reply.buildReply(instance[kReply])
|
|
42
|
+
instance[kReply].prototype.server = instance
|
|
43
|
+
|
|
41
44
|
instance[kRequest] = Request.buildRequest(instance[kRequest])
|
|
45
|
+
instance[kRequest].prototype.server = instance
|
|
46
|
+
|
|
42
47
|
instance[kContentTypeParser] = ContentTypeParser.helpers.buildContentTypeParser(instance[kContentTypeParser])
|
|
43
48
|
instance[kHooks] = buildHooks(instance[kHooks])
|
|
44
49
|
instance[kRoutePrefix] = buildRoutePrefix(instance[kRoutePrefix], opts.prefix)
|
package/lib/pluginUtils.js
CHANGED
|
@@ -109,9 +109,7 @@ function registerPluginName (fn) {
|
|
|
109
109
|
|
|
110
110
|
function registerPlugin (fn) {
|
|
111
111
|
registerPluginName.call(this, fn)
|
|
112
|
-
|
|
113
|
-
checkVersion.call(this, fn)
|
|
114
|
-
}
|
|
112
|
+
checkVersion.call(this, fn)
|
|
115
113
|
checkDecorators.call(this, fn)
|
|
116
114
|
checkDependencies.call(this, fn)
|
|
117
115
|
return shouldSkipOverride(fn)
|
package/lib/reply.js
CHANGED
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
kSchemaResponse,
|
|
9
9
|
kFourOhFourContext,
|
|
10
10
|
kReplyErrorHandlerCalled,
|
|
11
|
+
kReplySent,
|
|
11
12
|
kReplySentOverwritten,
|
|
12
13
|
kReplyStartTime,
|
|
13
14
|
kReplySerializer,
|
|
@@ -51,6 +52,7 @@ const warning = require('./warnings')
|
|
|
51
52
|
|
|
52
53
|
function Reply (res, request, log) {
|
|
53
54
|
this.raw = res
|
|
55
|
+
this[kReplySent] = false
|
|
54
56
|
this[kReplySerializer] = null
|
|
55
57
|
this[kReplyErrorHandlerCalled] = false
|
|
56
58
|
this[kReplyIsError] = false
|
|
@@ -77,26 +79,19 @@ Object.defineProperties(Reply.prototype, {
|
|
|
77
79
|
sent: {
|
|
78
80
|
enumerable: true,
|
|
79
81
|
get () {
|
|
80
|
-
|
|
81
|
-
// response has ended. The response.writableEnded property was added in
|
|
82
|
-
// Node.js v12.9.0. Since fastify supports older Node.js versions as well,
|
|
83
|
-
// we have to take response.finished property in consideration when
|
|
84
|
-
// applicable. The response.finished will be always true when the request
|
|
85
|
-
// method is HEAD and http2 is used, so we have to combine that with
|
|
86
|
-
// response.headersSent.
|
|
87
|
-
// TODO: remove fallback when the lowest supported Node.js version >= v12.9.0
|
|
88
|
-
return (this[kReplySentOverwritten] || (typeof this.raw.writableEnded !== 'undefined' ? this.raw.writableEnded : (this.raw.headersSent && this.raw.finished))) === true
|
|
82
|
+
return this[kReplySent]
|
|
89
83
|
},
|
|
90
84
|
set (value) {
|
|
91
85
|
if (value !== true) {
|
|
92
86
|
throw new FST_ERR_REP_SENT_VALUE()
|
|
93
87
|
}
|
|
94
88
|
|
|
95
|
-
if (this
|
|
89
|
+
if (this[kReplySent]) {
|
|
96
90
|
throw new FST_ERR_REP_ALREADY_SENT()
|
|
97
91
|
}
|
|
98
92
|
|
|
99
93
|
this[kReplySentOverwritten] = true
|
|
94
|
+
this[kReplySent] = true
|
|
100
95
|
}
|
|
101
96
|
},
|
|
102
97
|
statusCode: {
|
|
@@ -106,11 +101,15 @@ Object.defineProperties(Reply.prototype, {
|
|
|
106
101
|
set (value) {
|
|
107
102
|
this.code(value)
|
|
108
103
|
}
|
|
104
|
+
},
|
|
105
|
+
server: {
|
|
106
|
+
value: null,
|
|
107
|
+
writable: true
|
|
109
108
|
}
|
|
110
109
|
})
|
|
111
110
|
|
|
112
111
|
Reply.prototype.hijack = function () {
|
|
113
|
-
this[
|
|
112
|
+
this[kReplySent] = true
|
|
114
113
|
return this
|
|
115
114
|
}
|
|
116
115
|
|
|
@@ -119,7 +118,7 @@ Reply.prototype.send = function (payload) {
|
|
|
119
118
|
throw new FST_ERR_SEND_INSIDE_ONERR()
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
if (this
|
|
121
|
+
if (this[kReplySent]) {
|
|
123
122
|
this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT() }, 'Reply already sent')
|
|
124
123
|
return this
|
|
125
124
|
}
|
|
@@ -378,6 +377,7 @@ function preserializeHookEnd (err, request, reply, payload) {
|
|
|
378
377
|
}
|
|
379
378
|
|
|
380
379
|
function onSendHook (reply, payload) {
|
|
380
|
+
reply[kReplySent] = true
|
|
381
381
|
if (reply.context.onSend !== null) {
|
|
382
382
|
onSendHookRunner(
|
|
383
383
|
reply.context.onSend,
|
|
@@ -405,6 +405,8 @@ function onSendEnd (reply, payload) {
|
|
|
405
405
|
const statusCode = res.statusCode
|
|
406
406
|
|
|
407
407
|
if (payload === undefined || payload === null) {
|
|
408
|
+
reply[kReplySent] = true
|
|
409
|
+
|
|
408
410
|
// according to https://tools.ietf.org/html/rfc7230#section-3.3.2
|
|
409
411
|
// we cannot send a content-length for 304 and 204, and all status code
|
|
410
412
|
// < 200.
|
|
@@ -434,6 +436,8 @@ function onSendEnd (reply, payload) {
|
|
|
434
436
|
reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
|
|
435
437
|
}
|
|
436
438
|
|
|
439
|
+
reply[kReplySent] = true
|
|
440
|
+
|
|
437
441
|
res.writeHead(statusCode, reply[kReplyHeaders])
|
|
438
442
|
|
|
439
443
|
// avoid ArgumentsAdaptorTrampoline from V8
|
|
@@ -503,6 +507,7 @@ function sendStream (payload, res, reply) {
|
|
|
503
507
|
}
|
|
504
508
|
|
|
505
509
|
function onErrorHook (reply, error, cb) {
|
|
510
|
+
reply[kReplySent] = true
|
|
506
511
|
if (reply.context.onError !== null && reply[kReplyErrorHandlerCalled] === true) {
|
|
507
512
|
reply[kReplyIsRunningOnErrorHook] = true
|
|
508
513
|
onSendHookRunner(
|
|
@@ -538,6 +543,7 @@ function handleError (reply, error, cb) {
|
|
|
538
543
|
|
|
539
544
|
const errorHandler = reply.context.errorHandler
|
|
540
545
|
if (errorHandler && reply[kReplyErrorHandlerCalled] === false) {
|
|
546
|
+
reply[kReplySent] = false
|
|
541
547
|
reply[kReplyIsError] = false
|
|
542
548
|
reply[kReplyErrorHandlerCalled] = true
|
|
543
549
|
reply[kReplyHeaders]['content-length'] = undefined
|
|
@@ -583,6 +589,7 @@ function handleError (reply, error, cb) {
|
|
|
583
589
|
return
|
|
584
590
|
}
|
|
585
591
|
|
|
592
|
+
reply[kReplySent] = true
|
|
586
593
|
res.writeHead(res.statusCode, reply[kReplyHeaders])
|
|
587
594
|
res.end(payload)
|
|
588
595
|
}
|
|
@@ -644,6 +651,7 @@ function buildReply (R) {
|
|
|
644
651
|
this.raw = res
|
|
645
652
|
this[kReplyIsError] = false
|
|
646
653
|
this[kReplyErrorHandlerCalled] = false
|
|
654
|
+
this[kReplySent] = false
|
|
647
655
|
this[kReplySentOverwritten] = false
|
|
648
656
|
this[kReplySerializer] = null
|
|
649
657
|
this.request = request
|
|
@@ -656,6 +664,7 @@ function buildReply (R) {
|
|
|
656
664
|
}
|
|
657
665
|
|
|
658
666
|
function notFound (reply) {
|
|
667
|
+
reply[kReplySent] = false
|
|
659
668
|
reply[kReplyIsError] = false
|
|
660
669
|
|
|
661
670
|
if (reply.context[kFourOhFourContext] === null) {
|
package/lib/request.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const proxyAddr = require('
|
|
3
|
+
const proxyAddr = require('proxy-addr')
|
|
4
4
|
const semver = require('semver')
|
|
5
5
|
const warning = require('./warnings')
|
|
6
6
|
|
|
@@ -164,6 +164,10 @@ Object.defineProperties(Request.prototype, {
|
|
|
164
164
|
get () {
|
|
165
165
|
return this.raw.headers
|
|
166
166
|
}
|
|
167
|
+
},
|
|
168
|
+
server: {
|
|
169
|
+
value: null,
|
|
170
|
+
writable: true
|
|
167
171
|
}
|
|
168
172
|
})
|
|
169
173
|
|
package/lib/route.js
CHANGED
package/lib/symbols.js
CHANGED
|
@@ -30,6 +30,7 @@ const keys = {
|
|
|
30
30
|
kReplyIsError: Symbol('fastify.reply.isError'),
|
|
31
31
|
kReplyHeaders: Symbol('fastify.reply.headers'),
|
|
32
32
|
kReplyHasStatusCode: Symbol('fastify.reply.hasStatusCode'),
|
|
33
|
+
kReplySent: Symbol('fastify.reply.sent'),
|
|
33
34
|
kReplySentOverwritten: Symbol('fastify.reply.sentOverwritten'),
|
|
34
35
|
kReplyStartTime: Symbol('fastify.reply.startTime'),
|
|
35
36
|
kReplyErrorHandlerCalled: Symbol('fastify.reply.errorHandlerCalled'),
|
package/lib/wrapThenable.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
kReplyIsError,
|
|
5
|
+
kReplySent,
|
|
5
6
|
kReplySentOverwritten
|
|
6
7
|
} = require('./symbols')
|
|
7
8
|
|
|
@@ -15,24 +16,26 @@ function wrapThenable (thenable, reply) {
|
|
|
15
16
|
|
|
16
17
|
// this is for async functions that
|
|
17
18
|
// are using reply.send directly
|
|
18
|
-
if (payload !== undefined || (reply.raw.statusCode === 204 && reply
|
|
19
|
+
if (payload !== undefined || (reply.raw.statusCode === 204 && reply[kReplySent] === false)) {
|
|
19
20
|
// we use a try-catch internally to avoid adding a catch to another
|
|
20
21
|
// promise, increase promise perf by 10%
|
|
21
22
|
try {
|
|
22
23
|
reply.send(payload)
|
|
23
24
|
} catch (err) {
|
|
25
|
+
reply[kReplySent] = false
|
|
24
26
|
reply[kReplyIsError] = true
|
|
25
27
|
reply.send(err)
|
|
26
28
|
}
|
|
27
|
-
} else if (reply
|
|
29
|
+
} else if (reply[kReplySent] === false) {
|
|
28
30
|
reply.log.error({ err: new FST_ERR_PROMISE_NOT_FULFILLED() }, "Promise may not be fulfilled with 'undefined' when statusCode is not 204")
|
|
29
31
|
}
|
|
30
32
|
}, function (err) {
|
|
31
|
-
if (reply.sent === true) {
|
|
33
|
+
if (reply[kReplySentOverwritten] === true || reply.sent === true) {
|
|
32
34
|
reply.log.error({ err }, 'Promise errored, but reply.sent = true was set')
|
|
33
35
|
return
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
reply[kReplySent] = false
|
|
36
39
|
reply[kReplyIsError] = true
|
|
37
40
|
reply.send(err)
|
|
38
41
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.18.0",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -177,7 +177,6 @@
|
|
|
177
177
|
},
|
|
178
178
|
"dependencies": {
|
|
179
179
|
"@fastify/ajv-compiler": "^1.0.0",
|
|
180
|
-
"@fastify/proxy-addr": "^3.0.0",
|
|
181
180
|
"abstract-logging": "^2.0.0",
|
|
182
181
|
"avvio": "^7.1.2",
|
|
183
182
|
"fast-json-stringify": "^2.5.2",
|
|
@@ -187,6 +186,7 @@
|
|
|
187
186
|
"flatstr": "^1.0.12",
|
|
188
187
|
"light-my-request": "^4.2.0",
|
|
189
188
|
"pino": "^6.2.1",
|
|
189
|
+
"proxy-addr": "^2.0.7",
|
|
190
190
|
"readable-stream": "^3.4.0",
|
|
191
191
|
"rfdc": "^1.1.4",
|
|
192
192
|
"secure-json-parse": "^2.0.0",
|
package/test/decorator.test.js
CHANGED
|
@@ -838,3 +838,42 @@ test('decorate* should not emit warning if string,bool,numbers are passed', t =>
|
|
|
838
838
|
fastify.decorateReply('test_undefined', undefined)
|
|
839
839
|
t.end('Done')
|
|
840
840
|
})
|
|
841
|
+
|
|
842
|
+
test('Request/reply decorators should be able to access the server instance', async t => {
|
|
843
|
+
t.plan(6)
|
|
844
|
+
|
|
845
|
+
const server = require('..')({ logger: false })
|
|
846
|
+
server.decorateRequest('assert', rootAssert)
|
|
847
|
+
server.decorateReply('assert', rootAssert)
|
|
848
|
+
|
|
849
|
+
server.get('/root-assert', async (req, rep) => {
|
|
850
|
+
req.assert()
|
|
851
|
+
rep.assert()
|
|
852
|
+
return 'done'
|
|
853
|
+
})
|
|
854
|
+
|
|
855
|
+
server.register(async instance => {
|
|
856
|
+
instance.decorateRequest('assert', nestedAssert)
|
|
857
|
+
instance.decorateReply('assert', nestedAssert)
|
|
858
|
+
instance.decorate('foo', 'bar')
|
|
859
|
+
|
|
860
|
+
instance.get('/nested-assert', async (req, rep) => {
|
|
861
|
+
req.assert()
|
|
862
|
+
rep.assert()
|
|
863
|
+
return 'done'
|
|
864
|
+
})
|
|
865
|
+
})
|
|
866
|
+
|
|
867
|
+
await server.inject({ method: 'GET', url: '/root-assert' })
|
|
868
|
+
await server.inject({ method: 'GET', url: '/nested-assert' })
|
|
869
|
+
|
|
870
|
+
// ----
|
|
871
|
+
function rootAssert () {
|
|
872
|
+
t.equal(this.server, server)
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function nestedAssert () {
|
|
876
|
+
t.not(this.server, server)
|
|
877
|
+
t.equal(this.server.foo, 'bar')
|
|
878
|
+
}
|
|
879
|
+
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { test } = require('tap')
|
|
4
|
+
const semver = require('semver')
|
|
4
5
|
const handleRequest = require('../../lib/handleRequest')
|
|
5
6
|
const internals = require('../../lib/handleRequest')[Symbol.for('internals')]
|
|
6
7
|
const Request = require('../../lib/request')
|
|
@@ -107,8 +108,12 @@ test('handler function - preValidationCallback with finished response', t => {
|
|
|
107
108
|
t.plan(0)
|
|
108
109
|
const res = {}
|
|
109
110
|
// Be sure to check only `writableEnded` where is available
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
if (semver.gte(process.versions.node, '12.9.0')) {
|
|
112
|
+
res.writableEnded = true
|
|
113
|
+
} else {
|
|
114
|
+
res.writable = false
|
|
115
|
+
res.finished = true
|
|
116
|
+
}
|
|
112
117
|
res.end = () => {
|
|
113
118
|
t.fail()
|
|
114
119
|
}
|
|
@@ -133,7 +138,7 @@ test('handler function - preValidationCallback with finished response (< v12.9.0
|
|
|
133
138
|
t.plan(0)
|
|
134
139
|
const res = {}
|
|
135
140
|
// Be sure to check only `writableEnded` where is available
|
|
136
|
-
res.
|
|
141
|
+
res.writable = false
|
|
137
142
|
res.finished = true
|
|
138
143
|
|
|
139
144
|
res.end = () => {
|