fastify 3.16.2 → 3.17.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/docs/Reply.md +16 -0
- package/docs/Request.md +2 -0
- package/fastify.js +3 -0
- package/lib/handleRequest.js +6 -2
- package/lib/pluginOverride.js +5 -0
- package/lib/reply.js +21 -13
- package/lib/request.js +4 -0
- package/lib/symbols.js +1 -0
- package/lib/wrapThenable.js +6 -3
- package/package.json +1 -1
- package/test/decorator.test.js +39 -0
- package/test/internals/handleRequest.test.js +8 -3
- package/test/internals/reply.test.js +0 -34
- package/test/wrapThenable.test.js +1 -2
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/fastify.js
CHANGED
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/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/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,27 +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
|
-
|
|
96
|
-
if (this.sent && this[kReplySentOverwritten]) {
|
|
89
|
+
if (this[kReplySent]) {
|
|
97
90
|
throw new FST_ERR_REP_ALREADY_SENT()
|
|
98
91
|
}
|
|
99
92
|
|
|
100
93
|
this[kReplySentOverwritten] = true
|
|
94
|
+
this[kReplySent] = true
|
|
101
95
|
}
|
|
102
96
|
},
|
|
103
97
|
statusCode: {
|
|
@@ -107,11 +101,15 @@ Object.defineProperties(Reply.prototype, {
|
|
|
107
101
|
set (value) {
|
|
108
102
|
this.code(value)
|
|
109
103
|
}
|
|
104
|
+
},
|
|
105
|
+
server: {
|
|
106
|
+
value: null,
|
|
107
|
+
writable: true
|
|
110
108
|
}
|
|
111
109
|
})
|
|
112
110
|
|
|
113
111
|
Reply.prototype.hijack = function () {
|
|
114
|
-
this[
|
|
112
|
+
this[kReplySent] = true
|
|
115
113
|
return this
|
|
116
114
|
}
|
|
117
115
|
|
|
@@ -120,7 +118,7 @@ Reply.prototype.send = function (payload) {
|
|
|
120
118
|
throw new FST_ERR_SEND_INSIDE_ONERR()
|
|
121
119
|
}
|
|
122
120
|
|
|
123
|
-
if (this
|
|
121
|
+
if (this[kReplySent]) {
|
|
124
122
|
this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT() }, 'Reply already sent')
|
|
125
123
|
return this
|
|
126
124
|
}
|
|
@@ -379,6 +377,7 @@ function preserializeHookEnd (err, request, reply, payload) {
|
|
|
379
377
|
}
|
|
380
378
|
|
|
381
379
|
function onSendHook (reply, payload) {
|
|
380
|
+
reply[kReplySent] = true
|
|
382
381
|
if (reply.context.onSend !== null) {
|
|
383
382
|
onSendHookRunner(
|
|
384
383
|
reply.context.onSend,
|
|
@@ -406,6 +405,8 @@ function onSendEnd (reply, payload) {
|
|
|
406
405
|
const statusCode = res.statusCode
|
|
407
406
|
|
|
408
407
|
if (payload === undefined || payload === null) {
|
|
408
|
+
reply[kReplySent] = true
|
|
409
|
+
|
|
409
410
|
// according to https://tools.ietf.org/html/rfc7230#section-3.3.2
|
|
410
411
|
// we cannot send a content-length for 304 and 204, and all status code
|
|
411
412
|
// < 200.
|
|
@@ -435,6 +436,8 @@ function onSendEnd (reply, payload) {
|
|
|
435
436
|
reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
|
|
436
437
|
}
|
|
437
438
|
|
|
439
|
+
reply[kReplySent] = true
|
|
440
|
+
|
|
438
441
|
res.writeHead(statusCode, reply[kReplyHeaders])
|
|
439
442
|
|
|
440
443
|
// avoid ArgumentsAdaptorTrampoline from V8
|
|
@@ -504,6 +507,7 @@ function sendStream (payload, res, reply) {
|
|
|
504
507
|
}
|
|
505
508
|
|
|
506
509
|
function onErrorHook (reply, error, cb) {
|
|
510
|
+
reply[kReplySent] = true
|
|
507
511
|
if (reply.context.onError !== null && reply[kReplyErrorHandlerCalled] === true) {
|
|
508
512
|
reply[kReplyIsRunningOnErrorHook] = true
|
|
509
513
|
onSendHookRunner(
|
|
@@ -539,6 +543,7 @@ function handleError (reply, error, cb) {
|
|
|
539
543
|
|
|
540
544
|
const errorHandler = reply.context.errorHandler
|
|
541
545
|
if (errorHandler && reply[kReplyErrorHandlerCalled] === false) {
|
|
546
|
+
reply[kReplySent] = false
|
|
542
547
|
reply[kReplyIsError] = false
|
|
543
548
|
reply[kReplyErrorHandlerCalled] = true
|
|
544
549
|
reply[kReplyHeaders]['content-length'] = undefined
|
|
@@ -584,6 +589,7 @@ function handleError (reply, error, cb) {
|
|
|
584
589
|
return
|
|
585
590
|
}
|
|
586
591
|
|
|
592
|
+
reply[kReplySent] = true
|
|
587
593
|
res.writeHead(res.statusCode, reply[kReplyHeaders])
|
|
588
594
|
res.end(payload)
|
|
589
595
|
}
|
|
@@ -645,6 +651,7 @@ function buildReply (R) {
|
|
|
645
651
|
this.raw = res
|
|
646
652
|
this[kReplyIsError] = false
|
|
647
653
|
this[kReplyErrorHandlerCalled] = false
|
|
654
|
+
this[kReplySent] = false
|
|
648
655
|
this[kReplySentOverwritten] = false
|
|
649
656
|
this[kReplySerializer] = null
|
|
650
657
|
this.request = request
|
|
@@ -657,6 +664,7 @@ function buildReply (R) {
|
|
|
657
664
|
}
|
|
658
665
|
|
|
659
666
|
function notFound (reply) {
|
|
667
|
+
reply[kReplySent] = false
|
|
660
668
|
reply[kReplyIsError] = false
|
|
661
669
|
|
|
662
670
|
if (reply.context[kFourOhFourContext] === null) {
|
package/lib/request.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
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 = () => {
|
|
@@ -1329,7 +1329,6 @@ test('should throw error when attempting to set reply.sent more than once', t =>
|
|
|
1329
1329
|
reply.sent = true
|
|
1330
1330
|
try {
|
|
1331
1331
|
reply.sent = true
|
|
1332
|
-
t.fail('must throw')
|
|
1333
1332
|
} catch (err) {
|
|
1334
1333
|
t.equal(err.code, 'FST_ERR_REP_ALREADY_SENT')
|
|
1335
1334
|
t.equal(err.message, 'Reply was already sent.')
|
|
@@ -1343,23 +1342,6 @@ test('should throw error when attempting to set reply.sent more than once', t =>
|
|
|
1343
1342
|
})
|
|
1344
1343
|
})
|
|
1345
1344
|
|
|
1346
|
-
test('should not throw error when attempting to set reply.sent if the underlining request was sent', t => {
|
|
1347
|
-
t.plan(3)
|
|
1348
|
-
const fastify = require('../..')()
|
|
1349
|
-
|
|
1350
|
-
fastify.get('/', function (req, reply) {
|
|
1351
|
-
reply.raw.end()
|
|
1352
|
-
t.doesNotThrow(() => {
|
|
1353
|
-
reply.sent = true
|
|
1354
|
-
})
|
|
1355
|
-
})
|
|
1356
|
-
|
|
1357
|
-
fastify.inject('/', (err, res) => {
|
|
1358
|
-
t.error(err)
|
|
1359
|
-
t.pass()
|
|
1360
|
-
})
|
|
1361
|
-
})
|
|
1362
|
-
|
|
1363
1345
|
test('reply.getResponseTime() should return 0 before the timer is initialised on the reply by setting up response listeners', t => {
|
|
1364
1346
|
t.plan(1)
|
|
1365
1347
|
const response = { statusCode: 200 }
|
|
@@ -1703,19 +1685,3 @@ test('reply.then', t => {
|
|
|
1703
1685
|
response.destroy(_err)
|
|
1704
1686
|
})
|
|
1705
1687
|
})
|
|
1706
|
-
|
|
1707
|
-
test('reply.sent should read from response.writableEnded if it is defined', t => {
|
|
1708
|
-
t.plan(1)
|
|
1709
|
-
|
|
1710
|
-
const reply = new Reply({ writableEnded: true }, {}, {})
|
|
1711
|
-
|
|
1712
|
-
t.equal(reply.sent, true)
|
|
1713
|
-
})
|
|
1714
|
-
|
|
1715
|
-
test('reply.sent should read from response.headersSent and response.finished if response.writableEnded is not defined', t => {
|
|
1716
|
-
t.plan(1)
|
|
1717
|
-
|
|
1718
|
-
const reply = new Reply({ headersSent: true, finished: true }, {}, {})
|
|
1719
|
-
|
|
1720
|
-
t.equal(reply.sent, true)
|
|
1721
|
-
})
|
|
@@ -4,7 +4,6 @@ const t = require('tap')
|
|
|
4
4
|
const test = t.test
|
|
5
5
|
const { kReplySentOverwritten } = require('../lib/symbols')
|
|
6
6
|
const wrapThenable = require('../lib/wrapThenable')
|
|
7
|
-
const Reply = require('../lib/reply')
|
|
8
7
|
|
|
9
8
|
test('should resolve immediately when reply[kReplySentOverwritten] is true', t => {
|
|
10
9
|
const reply = {}
|
|
@@ -16,7 +15,7 @@ test('should resolve immediately when reply[kReplySentOverwritten] is true', t =
|
|
|
16
15
|
|
|
17
16
|
test('should reject immediately when reply[kReplySentOverwritten] is true', t => {
|
|
18
17
|
t.plan(1)
|
|
19
|
-
const reply =
|
|
18
|
+
const reply = { res: {} }
|
|
20
19
|
reply[kReplySentOverwritten] = true
|
|
21
20
|
reply.log = {
|
|
22
21
|
error: ({ err }) => {
|