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 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
@@ -286,6 +286,9 @@ function fastify (options) {
286
286
  initialConfig
287
287
  }
288
288
 
289
+ fastify[kReply].prototype.server = fastify
290
+ fastify[kRequest].prototype.server = fastify
291
+
289
292
  Object.defineProperties(fastify, {
290
293
  pluginName: {
291
294
  get () {
@@ -75,7 +75,9 @@ function handler (request, reply) {
75
75
  }
76
76
 
77
77
  function preValidationCallback (err, request, reply) {
78
- if (reply.sent === true) return
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) return
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)
@@ -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
- // We are checking whether reply was manually marked as sent or the
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
- // We throw only if sent was overwritten from Fastify
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[kReplySentOverwritten] = true
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.sent) {
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
@@ -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/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'),
@@ -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.sent === false)) {
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.sent === false) {
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.16.2",
3
+ "version": "3.17.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -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
- res.writableEnded = true
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.headersSent = true
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 = new Reply({}, {}, {})
18
+ const reply = { res: {} }
20
19
  reply[kReplySentOverwritten] = true
21
20
  reply.log = {
22
21
  error: ({ err }) => {