fastify 3.27.4 → 4.0.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/.taprc +3 -0
  2. package/README.md +7 -7
  3. package/build/build-error-serializer.js +27 -0
  4. package/build/build-validation.js +47 -35
  5. package/docs/Guides/Database.md +320 -0
  6. package/docs/Guides/Ecosystem.md +9 -0
  7. package/docs/Guides/Getting-Started.md +7 -7
  8. package/docs/Guides/Plugins-Guide.md +1 -1
  9. package/docs/Guides/Serverless.md +3 -3
  10. package/docs/Guides/Testing.md +2 -2
  11. package/docs/Migration-Guide-V4.md +12 -0
  12. package/docs/Reference/ContentTypeParser.md +4 -0
  13. package/docs/Reference/Decorators.md +2 -2
  14. package/docs/Reference/Encapsulation.md +2 -2
  15. package/docs/Reference/Errors.md +51 -6
  16. package/docs/Reference/HTTP2.md +3 -3
  17. package/docs/Reference/Hooks.md +4 -7
  18. package/docs/Reference/LTS.md +5 -4
  19. package/docs/Reference/Plugins.md +3 -3
  20. package/docs/Reference/Reply.md +73 -22
  21. package/docs/Reference/Request.md +1 -3
  22. package/docs/Reference/Routes.md +22 -15
  23. package/docs/Reference/Server.md +69 -119
  24. package/docs/Reference/TypeScript.md +20 -22
  25. package/docs/Reference/Validation-and-Serialization.md +30 -55
  26. package/docs/Type-Providers.md +257 -0
  27. package/examples/asyncawait.js +1 -1
  28. package/examples/benchmark/hooks-benchmark-async-await.js +1 -1
  29. package/examples/benchmark/hooks-benchmark.js +1 -1
  30. package/examples/benchmark/simple.js +1 -1
  31. package/examples/hooks.js +2 -2
  32. package/examples/http2.js +1 -1
  33. package/examples/https.js +1 -1
  34. package/examples/parser.js +13 -3
  35. package/examples/route-prefix.js +1 -1
  36. package/examples/shared-schema.js +1 -1
  37. package/examples/simple-stream.js +18 -0
  38. package/examples/simple.js +1 -1
  39. package/examples/simple.mjs +1 -1
  40. package/examples/typescript-server.ts +1 -1
  41. package/examples/use-plugin.js +1 -1
  42. package/fastify.d.ts +34 -22
  43. package/fastify.js +40 -36
  44. package/lib/configValidator.js +902 -1023
  45. package/lib/contentTypeParser.js +6 -16
  46. package/lib/context.js +36 -10
  47. package/lib/decorate.js +3 -1
  48. package/lib/error-handler.js +158 -0
  49. package/lib/error-serializer.js +257 -0
  50. package/lib/errors.js +51 -9
  51. package/lib/fourOhFour.js +31 -20
  52. package/lib/handleRequest.js +10 -13
  53. package/lib/hooks.js +14 -9
  54. package/lib/pluginOverride.js +0 -3
  55. package/lib/pluginUtils.js +3 -2
  56. package/lib/reply.js +121 -175
  57. package/lib/request.js +13 -10
  58. package/lib/route.js +131 -138
  59. package/lib/schema-controller.js +2 -2
  60. package/lib/schemas.js +27 -1
  61. package/lib/server.js +242 -116
  62. package/lib/symbols.js +5 -3
  63. package/lib/validation.js +11 -9
  64. package/lib/warnings.js +4 -12
  65. package/lib/wrapThenable.js +4 -11
  66. package/package.json +37 -39
  67. package/test/404s.test.js +258 -125
  68. package/test/500s.test.js +3 -3
  69. package/test/als.test.js +1 -1
  70. package/test/async-await.test.js +20 -76
  71. package/test/bodyLimit.test.js +1 -1
  72. package/test/build-certificate.js +6 -7
  73. package/test/case-insensitive.test.js +4 -4
  74. package/test/close-pipelining.test.js +2 -2
  75. package/test/close.test.js +11 -11
  76. package/test/content-parser.test.js +32 -0
  77. package/test/context-config.test.js +52 -0
  78. package/test/custom-http-server.test.js +14 -7
  79. package/test/custom-parser-async.test.js +1 -66
  80. package/test/custom-parser.test.js +92 -159
  81. package/test/custom-querystring-parser.test.js +3 -3
  82. package/test/decorator.test.js +11 -13
  83. package/test/delete.test.js +6 -6
  84. package/test/encapsulated-error-handler.test.js +50 -0
  85. package/test/esm/index.test.js +0 -14
  86. package/test/fastify-instance.test.js +4 -4
  87. package/test/fluent-schema.test.js +4 -4
  88. package/test/genReqId.test.js +1 -1
  89. package/test/get.test.js +4 -4
  90. package/test/handler-context.test.js +2 -2
  91. package/test/head.test.js +1 -1
  92. package/test/helper.js +19 -4
  93. package/test/hooks-async.test.js +15 -48
  94. package/test/hooks.on-ready.test.js +10 -5
  95. package/test/hooks.test.js +78 -119
  96. package/test/http2/closing.test.js +10 -16
  97. package/test/http2/constraint.test.js +1 -1
  98. package/test/http2/head.test.js +1 -1
  99. package/test/http2/plain.test.js +1 -1
  100. package/test/http2/secure-with-fallback.test.js +1 -1
  101. package/test/http2/secure.test.js +1 -1
  102. package/test/http2/unknown-http-method.test.js +4 -10
  103. package/test/https/custom-https-server.test.js +12 -6
  104. package/test/https/https.test.js +1 -1
  105. package/test/input-validation.js +3 -3
  106. package/test/internals/handleRequest.test.js +6 -43
  107. package/test/internals/initialConfig.test.js +41 -12
  108. package/test/internals/logger.test.js +2 -2
  109. package/test/internals/reply.test.js +317 -48
  110. package/test/internals/request.test.js +13 -7
  111. package/test/internals/server.test.js +88 -0
  112. package/test/listen.deprecated.test.js +202 -0
  113. package/test/listen.test.js +140 -145
  114. package/test/logger.test.js +82 -42
  115. package/test/maxRequestsPerSocket.test.js +8 -6
  116. package/test/middleware.test.js +2 -25
  117. package/test/nullable-validation.test.js +53 -16
  118. package/test/output-validation.test.js +1 -1
  119. package/test/plugin.test.js +47 -21
  120. package/test/pretty-print.test.js +22 -10
  121. package/test/promises.test.js +1 -1
  122. package/test/proto-poisoning.test.js +6 -6
  123. package/test/register.test.js +3 -3
  124. package/test/reply-error.test.js +126 -15
  125. package/test/reply-trailers.test.js +270 -0
  126. package/test/request-error.test.js +3 -6
  127. package/test/route-hooks.test.js +18 -18
  128. package/test/route-prefix.test.js +2 -1
  129. package/test/route.test.js +206 -22
  130. package/test/router-options.test.js +2 -2
  131. package/test/schema-examples.test.js +11 -5
  132. package/test/schema-feature.test.js +25 -20
  133. package/test/schema-serialization.test.js +9 -9
  134. package/test/schema-special-usage.test.js +5 -153
  135. package/test/schema-validation.test.js +9 -9
  136. package/test/skip-reply-send.test.js +2 -2
  137. package/test/stream.test.js +82 -23
  138. package/test/throw.test.js +8 -5
  139. package/test/trust-proxy.test.js +6 -6
  140. package/test/type-provider.test.js +20 -0
  141. package/test/types/fastify.test-d.ts +10 -18
  142. package/test/types/hooks.test-d.ts +61 -5
  143. package/test/types/import.js +2 -0
  144. package/test/types/import.ts +1 -0
  145. package/test/types/instance.test-d.ts +68 -17
  146. package/test/types/logger.test-d.ts +44 -15
  147. package/test/types/reply.test-d.ts +2 -1
  148. package/test/types/request.test-d.ts +71 -1
  149. package/test/types/route.test-d.ts +8 -2
  150. package/test/types/schema.test-d.ts +2 -39
  151. package/test/types/type-provider.test-d.ts +424 -0
  152. package/test/url-rewriting.test.js +3 -3
  153. package/test/validation-error-handling.test.js +8 -8
  154. package/test/versioned-routes.test.js +30 -18
  155. package/test/wrapThenable.test.js +7 -6
  156. package/types/content-type-parser.d.ts +17 -8
  157. package/types/hooks.d.ts +182 -85
  158. package/types/instance.d.ts +286 -118
  159. package/types/logger.d.ts +18 -104
  160. package/types/plugin.d.ts +10 -4
  161. package/types/reply.d.ts +18 -12
  162. package/types/request.d.ts +13 -8
  163. package/types/route.d.ts +62 -34
  164. package/types/schema.d.ts +1 -1
  165. package/types/type-provider.d.ts +99 -0
  166. package/types/utils.d.ts +1 -1
  167. package/lib/schema-compilers.js +0 -12
  168. package/test/emit-warning.test.js +0 -166
package/lib/reply.js CHANGED
@@ -1,23 +1,21 @@
1
1
  'use strict'
2
2
 
3
3
  const eos = require('stream').finished
4
- const statusCodes = require('http').STATUS_CODES
5
- const flatstr = require('flatstr')
6
- const FJS = require('fast-json-stringify')
4
+
7
5
  const {
8
- kSchemaResponse,
9
6
  kFourOhFourContext,
10
7
  kReplyErrorHandlerCalled,
11
- kReplySent,
12
- kReplySentOverwritten,
8
+ kReplyHijacked,
13
9
  kReplyStartTime,
14
10
  kReplyEndTime,
15
11
  kReplySerializer,
16
12
  kReplySerializerDefault,
17
13
  kReplyIsError,
18
14
  kReplyHeaders,
15
+ kReplyTrailers,
19
16
  kReplyHasStatusCode,
20
17
  kReplyIsRunningOnErrorHook,
18
+ kReplyNextErrorHandler,
21
19
  kDisableRequestLogging
22
20
  } = require('./symbols.js')
23
21
  const { hookRunner, hookIterator, onSendHookRunner } = require('./hooks')
@@ -25,17 +23,8 @@ const { hookRunner, hookIterator, onSendHookRunner } = require('./hooks')
25
23
  const internals = require('./handleRequest')[Symbol.for('internals')]
26
24
  const loggerUtils = require('./logger')
27
25
  const now = loggerUtils.now
28
- const wrapThenable = require('./wrapThenable')
29
-
30
- const serializeError = FJS({
31
- type: 'object',
32
- properties: {
33
- statusCode: { type: 'number' },
34
- code: { type: 'string' },
35
- error: { type: 'string' },
36
- message: { type: 'string' }
37
- }
38
- })
26
+ const { handleError } = require('./error-handler')
27
+ const { getSchemaSerializer } = require('./schemas')
39
28
 
40
29
  const CONTENT_TYPE = {
41
30
  JSON: 'application/json; charset=utf-8',
@@ -47,19 +36,21 @@ const {
47
36
  FST_ERR_REP_ALREADY_SENT,
48
37
  FST_ERR_REP_SENT_VALUE,
49
38
  FST_ERR_SEND_INSIDE_ONERR,
50
- FST_ERR_BAD_STATUS_CODE
39
+ FST_ERR_BAD_STATUS_CODE,
40
+ FST_ERR_BAD_TRAILER_NAME,
41
+ FST_ERR_BAD_TRAILER_VALUE
51
42
  } = require('./errors')
52
43
  const warning = require('./warnings')
53
44
 
54
45
  function Reply (res, request, log) {
55
46
  this.raw = res
56
- this[kReplySent] = false
57
47
  this[kReplySerializer] = null
58
48
  this[kReplyErrorHandlerCalled] = false
59
49
  this[kReplyIsError] = false
60
50
  this[kReplyIsRunningOnErrorHook] = false
61
51
  this.request = request
62
52
  this[kReplyHeaders] = {}
53
+ this[kReplyTrailers] = null
63
54
  this[kReplyHasStatusCode] = false
64
55
  this[kReplyStartTime] = undefined
65
56
  this.log = log
@@ -72,28 +63,30 @@ Object.defineProperties(Reply.prototype, {
72
63
  return this.request.context
73
64
  }
74
65
  },
75
- res: {
66
+ server: {
76
67
  get () {
77
- warning.emit('FSTDEP002')
78
- return this.raw
68
+ return this.request.context.server
79
69
  }
80
70
  },
81
71
  sent: {
82
72
  enumerable: true,
83
73
  get () {
84
- return this[kReplySent]
74
+ // We are checking whether reply was hijacked or the response has ended.
75
+ return (this[kReplyHijacked] || this.raw.writableEnded) === true
85
76
  },
86
77
  set (value) {
78
+ warning.emit('FSTDEP010')
79
+
87
80
  if (value !== true) {
88
81
  throw new FST_ERR_REP_SENT_VALUE()
89
82
  }
90
83
 
91
- if (this[kReplySent]) {
84
+ // We throw only if sent was overwritten from Fastify
85
+ if (this.sent && this[kReplyHijacked]) {
92
86
  throw new FST_ERR_REP_ALREADY_SENT()
93
87
  }
94
88
 
95
- this[kReplySentOverwritten] = true
96
- this[kReplySent] = true
89
+ this[kReplyHijacked] = true
97
90
  }
98
91
  },
99
92
  statusCode: {
@@ -103,15 +96,11 @@ Object.defineProperties(Reply.prototype, {
103
96
  set (value) {
104
97
  this.code(value)
105
98
  }
106
- },
107
- server: {
108
- value: null,
109
- writable: true
110
99
  }
111
100
  })
112
101
 
113
102
  Reply.prototype.hijack = function () {
114
- this[kReplySent] = true
103
+ this[kReplyHijacked] = true
115
104
  return this
116
105
  }
117
106
 
@@ -120,12 +109,13 @@ Reply.prototype.send = function (payload) {
120
109
  throw new FST_ERR_SEND_INSIDE_ONERR()
121
110
  }
122
111
 
123
- if (this[kReplySent]) {
112
+ if (this.sent) {
124
113
  this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT() }, 'Reply already sent')
125
114
  return this
126
115
  }
127
116
 
128
117
  if (payload instanceof Error || this[kReplyIsError] === true) {
118
+ this[kReplyIsError] = false
129
119
  onErrorHook(this, payload, onSendHook)
130
120
  return this
131
121
  }
@@ -139,7 +129,12 @@ Reply.prototype.send = function (payload) {
139
129
  const hasContentType = contentType !== undefined
140
130
 
141
131
  if (payload !== null) {
142
- if (Buffer.isBuffer(payload) || typeof payload.pipe === 'function') {
132
+ if (typeof payload.pipe === 'function') {
133
+ onSendHook(this, payload)
134
+ return this
135
+ }
136
+
137
+ if (Buffer.isBuffer(payload)) {
143
138
  if (hasContentType === false) {
144
139
  this[kReplyHeaders]['content-type'] = CONTENT_TYPE.OCTET
145
140
  }
@@ -167,22 +162,14 @@ Reply.prototype.send = function (payload) {
167
162
  if (hasContentType === false) {
168
163
  this[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON
169
164
  } else {
170
- // If hasContentType === true, we have a JSON mimetype
165
+ // If user doesn't set charset, we will set charset to utf-8
171
166
  if (contentType.indexOf('charset') === -1) {
172
- // If we have simply application/json instead of a custom json mimetype
173
- if (contentType.indexOf('/json') > -1) {
174
- this[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON
167
+ const customContentType = contentType.trim()
168
+ if (customContentType.endsWith(';')) {
169
+ // custom content-type is ended with ';'
170
+ this[kReplyHeaders]['content-type'] = `${customContentType} charset=utf-8`
175
171
  } else {
176
- const currContentType = this[kReplyHeaders]['content-type']
177
- // We extract the custom mimetype part (e.g. 'hal+' from 'application/hal+json')
178
- const customJsonType = currContentType.substring(
179
- currContentType.indexOf('/'),
180
- currContentType.indexOf('json') + 4
181
- )
182
-
183
- // We ensure we set the header to the proper JSON content-type if necessary
184
- // (e.g. 'application/hal+json' instead of 'application/json')
185
- this[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON.replace('/json', customJsonType)
172
+ this[kReplyHeaders]['content-type'] = `${customContentType}; charset=utf-8`
186
173
  }
187
174
  }
188
175
  }
@@ -261,6 +248,47 @@ Reply.prototype.headers = function (headers) {
261
248
  return this
262
249
  }
263
250
 
251
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Trailer#directives
252
+ // https://httpwg.org/specs/rfc7230.html#chunked.trailer.part
253
+ const INVALID_TRAILERS = new Set([
254
+ 'transfer-encoding',
255
+ 'content-length',
256
+ 'host',
257
+ 'cache-control',
258
+ 'max-forwards',
259
+ 'te',
260
+ 'authorization',
261
+ 'set-cookie',
262
+ 'content-encoding',
263
+ 'content-type',
264
+ 'content-range',
265
+ 'trailer'
266
+ ])
267
+
268
+ Reply.prototype.trailer = function (key, fn) {
269
+ key = key.toLowerCase()
270
+ if (INVALID_TRAILERS.has(key)) {
271
+ throw new FST_ERR_BAD_TRAILER_NAME(key)
272
+ }
273
+ if (typeof fn !== 'function') {
274
+ throw new FST_ERR_BAD_TRAILER_VALUE(key, typeof fn)
275
+ }
276
+ if (this[kReplyTrailers] === null) this[kReplyTrailers] = {}
277
+ this[kReplyTrailers][key] = fn
278
+ return this
279
+ }
280
+
281
+ Reply.prototype.hasTrailer = function (key) {
282
+ if (this[kReplyTrailers] === null) return false
283
+ return this[kReplyTrailers][key.toLowerCase()] !== undefined
284
+ }
285
+
286
+ Reply.prototype.removeTrailer = function (key) {
287
+ if (this[kReplyTrailers] === null) return this
288
+ this[kReplyTrailers][key.toLowerCase()] = undefined
289
+ return this
290
+ }
291
+
264
292
  Reply.prototype.code = function (code) {
265
293
  const intValue = parseInt(code)
266
294
  if (isNaN(intValue) || intValue < 100 || intValue > 600) {
@@ -379,8 +407,6 @@ function preserializeHookEnd (err, request, reply, payload) {
379
407
  return
380
408
  }
381
409
 
382
- flatstr(payload)
383
-
384
410
  onSendHook(reply, payload)
385
411
  }
386
412
 
@@ -390,7 +416,6 @@ function wrapSeralizationError (error, reply) {
390
416
 
391
417
  function onSendHook (reply, payload) {
392
418
  if (reply.context.onSend !== null) {
393
- reply[kReplySent] = true
394
419
  onSendHookRunner(
395
420
  reply.context.onSend,
396
421
  reply.request,
@@ -416,9 +441,21 @@ function onSendEnd (reply, payload) {
416
441
  const req = reply.request
417
442
  const statusCode = res.statusCode
418
443
 
419
- if (payload === undefined || payload === null) {
420
- reply[kReplySent] = true
444
+ // we check if we need to update the trailers header and set it
445
+ if (reply[kReplyTrailers] !== null) {
446
+ const trailerHeaders = Object.keys(reply[kReplyTrailers])
447
+ let header = ''
448
+ for (const trailerName of trailerHeaders) {
449
+ if (typeof reply[kReplyTrailers][trailerName] !== 'function') continue
450
+ header += ' '
451
+ header += trailerName
452
+ }
453
+ // it must be chunked for trailer to work
454
+ reply.header('Transfer-Encoding', 'chunked')
455
+ reply.header('Trailer', header.trim())
456
+ }
421
457
 
458
+ if (payload === undefined || payload === null) {
422
459
  // according to https://tools.ietf.org/html/rfc7230#section-3.3.2
423
460
  // we cannot send a content-length for 304 and 204, and all status code
424
461
  // < 200.
@@ -428,14 +465,13 @@ function onSendEnd (reply, payload) {
428
465
  }
429
466
 
430
467
  res.writeHead(statusCode, reply[kReplyHeaders])
468
+ sendTrailer(payload, res, reply)
431
469
  // avoid ArgumentsAdaptorTrampoline from V8
432
470
  res.end(null, null, null)
433
471
  return
434
472
  }
435
473
 
436
474
  if (typeof payload.pipe === 'function') {
437
- reply[kReplySent] = true
438
-
439
475
  sendStream(payload, res, reply)
440
476
  return
441
477
  }
@@ -450,12 +486,13 @@ function onSendEnd (reply, payload) {
450
486
  reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
451
487
  }
452
488
 
453
- reply[kReplySent] = true
454
-
455
489
  res.writeHead(statusCode, reply[kReplyHeaders])
456
-
490
+ // write payload first
491
+ res.write(payload)
492
+ // then send trailers
493
+ sendTrailer(payload, res, reply)
457
494
  // avoid ArgumentsAdaptorTrampoline from V8
458
- res.end(payload, null, null)
495
+ res.end(null, null, null)
459
496
  }
460
497
 
461
498
  function logStreamError (logger, err, res) {
@@ -472,10 +509,13 @@ function sendStream (payload, res, reply) {
472
509
  let sourceOpen = true
473
510
  let errorLogged = false
474
511
 
512
+ // set trailer when stream ended
513
+ sendStreamTrailer(payload, res, reply)
514
+
475
515
  eos(payload, { readable: true, writable: false }, function (err) {
476
516
  sourceOpen = false
477
517
  if (err != null) {
478
- if (res.headersSent) {
518
+ if (res.headersSent || reply.request.raw.aborted === true) {
479
519
  if (!errorLogged) {
480
520
  errorLogged = true
481
521
  logStreamError(reply.log, err, res)
@@ -520,9 +560,24 @@ function sendStream (payload, res, reply) {
520
560
  payload.pipe(res)
521
561
  }
522
562
 
563
+ function sendTrailer (payload, res, reply) {
564
+ if (reply[kReplyTrailers] === null) return
565
+ const trailerHeaders = Object.keys(reply[kReplyTrailers])
566
+ const trailers = {}
567
+ for (const trailerName of trailerHeaders) {
568
+ if (typeof reply[kReplyTrailers][trailerName] !== 'function') continue
569
+ trailers[trailerName] = reply[kReplyTrailers][trailerName](reply, payload)
570
+ }
571
+ res.addTrailers(trailers)
572
+ }
573
+
574
+ function sendStreamTrailer (payload, res, reply) {
575
+ if (reply[kReplyTrailers] === null) return
576
+ payload.on('end', () => sendTrailer(null, res, reply))
577
+ }
578
+
523
579
  function onErrorHook (reply, error, cb) {
524
- reply[kReplySent] = true
525
- if (reply.context.onError !== null && reply[kReplyErrorHandlerCalled] === true) {
580
+ if (reply.context.onError !== null && !reply[kReplyNextErrorHandler]) {
526
581
  reply[kReplyIsRunningOnErrorHook] = true
527
582
  onSendHookRunner(
528
583
  reply.context.onError,
@@ -536,89 +591,6 @@ function onErrorHook (reply, error, cb) {
536
591
  }
537
592
  }
538
593
 
539
- function handleError (reply, error, cb) {
540
- reply[kReplyIsRunningOnErrorHook] = false
541
- const res = reply.raw
542
- let statusCode = res.statusCode
543
- statusCode = (statusCode >= 400) ? statusCode : 500
544
- // treat undefined and null as same
545
- if (error != null) {
546
- if (error.headers !== undefined) {
547
- reply.headers(error.headers)
548
- }
549
- if (error.status >= 400) {
550
- statusCode = error.status
551
- } else if (error.statusCode >= 400) {
552
- statusCode = error.statusCode
553
- }
554
- }
555
-
556
- res.statusCode = statusCode
557
-
558
- const errorHandler = reply.context.errorHandler
559
- if (errorHandler && reply[kReplyErrorHandlerCalled] === false) {
560
- reply[kReplySent] = false
561
- reply[kReplyIsError] = false
562
- reply[kReplyErrorHandlerCalled] = true
563
- // remove header is needed in here, because when we pipe to a stream
564
- // `undefined` value header will directly passed to node response
565
- reply.removeHeader('content-length')
566
- const result = errorHandler(error, reply.request, reply)
567
- if (result !== undefined) {
568
- if (result !== null && typeof result.then === 'function') {
569
- wrapThenable(result, reply)
570
- } else {
571
- reply.send(result)
572
- }
573
- }
574
- return
575
- }
576
-
577
- let payload
578
- try {
579
- const serializerFn = getSchemaSerializer(reply.context, statusCode)
580
- payload = (serializerFn === false)
581
- ? serializeError({
582
- error: statusCodes[statusCode + ''],
583
- code: error.code,
584
- message: error.message || '',
585
- statusCode
586
- })
587
- : serializerFn(Object.create(error, {
588
- error: { value: statusCodes[statusCode + ''] },
589
- message: { value: error.message || '' },
590
- statusCode: { value: statusCode }
591
- }))
592
-
593
- if (serializerFn !== false && typeof payload !== 'string') {
594
- throw new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload)
595
- }
596
- } catch (err) {
597
- // error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization
598
- reply.log.error({ err, statusCode: res.statusCode }, 'The serializer for the given status code failed')
599
- res.statusCode = 500
600
- payload = serializeError({
601
- error: statusCodes['500'],
602
- code: err.code,
603
- message: err.message,
604
- statusCode: 500
605
- })
606
- }
607
-
608
- flatstr(payload)
609
- reply[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON
610
- reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
611
-
612
- if (cb) {
613
- cb(reply, payload)
614
- return
615
- }
616
-
617
- reply[kReplySent] = true
618
- res.writeHead(res.statusCode, reply[kReplyHeaders])
619
- res.end(payload)
620
- }
621
-
622
594
  function setupResponseListeners (reply) {
623
595
  reply[kReplyStartTime] = now()
624
596
 
@@ -679,11 +651,11 @@ function buildReply (R) {
679
651
  this.raw = res
680
652
  this[kReplyIsError] = false
681
653
  this[kReplyErrorHandlerCalled] = false
682
- this[kReplySent] = false
683
- this[kReplySentOverwritten] = false
654
+ this[kReplyHijacked] = false
684
655
  this[kReplySerializer] = null
685
656
  this.request = request
686
657
  this[kReplyHeaders] = {}
658
+ this[kReplyTrailers] = null
687
659
  this[kReplyStartTime] = undefined
688
660
  this[kReplyEndTime] = undefined
689
661
  this.log = log
@@ -696,15 +668,14 @@ function buildReply (R) {
696
668
  this[prop.key] = prop.value
697
669
  }
698
670
  }
699
- _Reply.prototype = new R()
671
+ Object.setPrototypeOf(_Reply.prototype, R.prototype)
672
+ Object.setPrototypeOf(_Reply, R)
673
+ _Reply.parent = R
700
674
  _Reply.props = props
701
675
  return _Reply
702
676
  }
703
677
 
704
678
  function notFound (reply) {
705
- reply[kReplySent] = false
706
- reply[kReplyIsError] = false
707
-
708
679
  if (reply.context[kFourOhFourContext] === null) {
709
680
  reply.log.warn('Trying to send a NotFound error inside a 404 handler. Sending basic 404 response.')
710
681
  reply.code(404).send('404 Not Found')
@@ -745,31 +716,6 @@ function serialize (context, data, statusCode) {
745
716
  return JSON.stringify(data)
746
717
  }
747
718
 
748
- /**
749
- * Search for the right JSON schema compiled function in the request context
750
- * setup by the route configuration `schema.response`.
751
- * It will look for the exact match (eg 200) or generic (eg 2xx)
752
- *
753
- * @param {object} context the request context
754
- * @param {number} statusCode the http status code
755
- * @returns {function|boolean} the right JSON Schema function to serialize
756
- * the reply or false if it is not set
757
- */
758
- function getSchemaSerializer (context, statusCode) {
759
- const responseSchemaDef = context[kSchemaResponse]
760
- if (!responseSchemaDef) {
761
- return false
762
- }
763
- if (responseSchemaDef[statusCode]) {
764
- return responseSchemaDef[statusCode]
765
- }
766
- const fallbackStatusCode = (statusCode + '')[0] + 'xx'
767
- if (responseSchemaDef[fallbackStatusCode]) {
768
- return responseSchemaDef[fallbackStatusCode]
769
- }
770
- return false
771
- }
772
-
773
719
  function noop () { }
774
720
 
775
721
  module.exports = Reply
package/lib/request.js CHANGED
@@ -3,6 +3,9 @@
3
3
  const proxyAddr = require('proxy-addr')
4
4
  const semver = require('semver')
5
5
  const warning = require('./warnings')
6
+ const {
7
+ kHasBeenDecorated
8
+ } = require('./symbols')
6
9
 
7
10
  function Request (id, params, req, query, log, context) {
8
11
  this.id = id
@@ -11,7 +14,7 @@ function Request (id, params, req, query, log, context) {
11
14
  this.raw = req
12
15
  this.query = query
13
16
  this.log = log
14
- this.body = null
17
+ this.body = undefined
15
18
  }
16
19
  Request.props = []
17
20
 
@@ -52,7 +55,7 @@ function buildRegularRequest (R) {
52
55
  this.raw = req
53
56
  this.query = query
54
57
  this.log = log
55
- this.body = null
58
+ this.body = undefined
56
59
 
57
60
  // eslint-disable-next-line no-var
58
61
  var prop
@@ -62,8 +65,10 @@ function buildRegularRequest (R) {
62
65
  this[prop.key] = prop.value
63
66
  }
64
67
  }
65
- _Request.prototype = new R()
68
+ Object.setPrototypeOf(_Request.prototype, Request.prototype)
69
+ Object.setPrototypeOf(_Request, Request)
66
70
  _Request.props = props
71
+ _Request.parent = R
67
72
 
68
73
  return _Request
69
74
  }
@@ -78,6 +83,9 @@ function buildRequestWithTrustProxy (R, trustProxy) {
78
83
  const _Request = buildRegularRequest(R)
79
84
  const proxyFn = getTrustProxyFn(trustProxy)
80
85
 
86
+ // This is a more optimized version of decoration
87
+ _Request[kHasBeenDecorated] = true
88
+
81
89
  Object.defineProperties(_Request.prototype, {
82
90
  ip: {
83
91
  get () {
@@ -113,10 +121,9 @@ function buildRequestWithTrustProxy (R, trustProxy) {
113
121
  }
114
122
 
115
123
  Object.defineProperties(Request.prototype, {
116
- req: {
124
+ server: {
117
125
  get () {
118
- warning.emit('FSTDEP001')
119
- return this.raw
126
+ return this.context.server
120
127
  }
121
128
  },
122
129
  url: {
@@ -187,10 +194,6 @@ Object.defineProperties(Request.prototype, {
187
194
  set (headers) {
188
195
  this.additionalHeaders = headers
189
196
  }
190
- },
191
- server: {
192
- value: null,
193
- writable: true
194
197
  }
195
198
  })
196
199