fastify 3.26.0 → 4.0.0-alpha.1

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 (129) hide show
  1. package/README.md +5 -4
  2. package/build/build-error-serializer.js +27 -0
  3. package/build/build-validation.js +49 -35
  4. package/docs/Guides/Ecosystem.md +2 -1
  5. package/docs/Guides/Prototype-Poisoning.md +3 -3
  6. package/docs/Migration-Guide-V4.md +12 -0
  7. package/docs/Reference/ContentTypeParser.md +8 -1
  8. package/docs/Reference/Errors.md +51 -6
  9. package/docs/Reference/Hooks.md +4 -7
  10. package/docs/Reference/LTS.md +5 -4
  11. package/docs/Reference/Reply.md +23 -22
  12. package/docs/Reference/Request.md +1 -3
  13. package/docs/Reference/Routes.md +17 -10
  14. package/docs/Reference/Server.md +98 -63
  15. package/docs/Reference/TypeScript.md +11 -13
  16. package/docs/Reference/Validation-and-Serialization.md +32 -54
  17. package/docs/Type-Providers.md +257 -0
  18. package/examples/hooks.js +1 -1
  19. package/examples/simple-stream.js +18 -0
  20. package/fastify.d.ts +36 -22
  21. package/fastify.js +72 -53
  22. package/lib/configValidator.js +902 -1023
  23. package/lib/contentTypeParser.js +6 -16
  24. package/lib/context.js +36 -10
  25. package/lib/decorate.js +5 -3
  26. package/lib/error-handler.js +158 -0
  27. package/lib/error-serializer.js +257 -0
  28. package/lib/errors.js +49 -10
  29. package/lib/fourOhFour.js +31 -20
  30. package/lib/handleRequest.js +10 -13
  31. package/lib/hooks.js +14 -9
  32. package/lib/noop-set.js +10 -0
  33. package/lib/pluginOverride.js +0 -3
  34. package/lib/pluginUtils.js +3 -2
  35. package/lib/reply.js +44 -163
  36. package/lib/request.js +13 -10
  37. package/lib/route.js +158 -139
  38. package/lib/schema-controller.js +3 -3
  39. package/lib/schemas.js +27 -1
  40. package/lib/server.js +219 -116
  41. package/lib/symbols.js +6 -4
  42. package/lib/validation.js +2 -1
  43. package/lib/warnings.js +2 -12
  44. package/lib/wrapThenable.js +4 -11
  45. package/package.json +40 -45
  46. package/test/404s.test.js +265 -108
  47. package/test/500s.test.js +2 -2
  48. package/test/async-await.test.js +15 -71
  49. package/test/close.test.js +39 -1
  50. package/test/content-parser.test.js +32 -0
  51. package/test/context-config.test.js +56 -4
  52. package/test/custom-http-server.test.js +14 -7
  53. package/test/custom-parser-async.test.js +0 -65
  54. package/test/custom-parser.test.js +54 -121
  55. package/test/decorator.test.js +1 -3
  56. package/test/delete.test.js +5 -5
  57. package/test/encapsulated-error-handler.test.js +50 -0
  58. package/test/esm/index.test.js +0 -14
  59. package/test/fastify-instance.test.js +4 -4
  60. package/test/fluent-schema.test.js +4 -4
  61. package/test/get.test.js +3 -3
  62. package/test/helper.js +18 -3
  63. package/test/hooks-async.test.js +14 -47
  64. package/test/hooks.on-ready.test.js +9 -4
  65. package/test/hooks.test.js +58 -99
  66. package/test/http2/closing.test.js +5 -11
  67. package/test/http2/unknown-http-method.test.js +3 -9
  68. package/test/https/custom-https-server.test.js +12 -6
  69. package/test/inject.test.js +1 -1
  70. package/test/input-validation.js +2 -2
  71. package/test/internals/all.test.js +2 -2
  72. package/test/internals/contentTypeParser.test.js +4 -4
  73. package/test/internals/handleRequest.test.js +9 -46
  74. package/test/internals/initialConfig.test.js +33 -12
  75. package/test/internals/logger.test.js +1 -1
  76. package/test/internals/reply.test.js +245 -3
  77. package/test/internals/request.test.js +13 -7
  78. package/test/internals/server.test.js +88 -0
  79. package/test/listen.test.js +84 -1
  80. package/test/logger.test.js +98 -58
  81. package/test/maxRequestsPerSocket.test.js +8 -6
  82. package/test/middleware.test.js +2 -25
  83. package/test/noop-set.test.js +19 -0
  84. package/test/nullable-validation.test.js +51 -14
  85. package/test/plugin.test.js +31 -5
  86. package/test/pretty-print.test.js +22 -10
  87. package/test/reply-error.test.js +123 -12
  88. package/test/request-error.test.js +2 -5
  89. package/test/route-hooks.test.js +17 -17
  90. package/test/route-prefix.test.js +2 -1
  91. package/test/route.test.js +216 -20
  92. package/test/router-options.test.js +1 -1
  93. package/test/schema-examples.test.js +11 -5
  94. package/test/schema-feature.test.js +24 -19
  95. package/test/schema-serialization.test.js +50 -9
  96. package/test/schema-special-usage.test.js +14 -81
  97. package/test/schema-validation.test.js +9 -9
  98. package/test/skip-reply-send.test.js +8 -8
  99. package/test/stream.test.js +23 -12
  100. package/test/throw.test.js +8 -5
  101. package/test/trust-proxy.test.js +1 -1
  102. package/test/type-provider.test.js +20 -0
  103. package/test/types/fastify.test-d.ts +12 -18
  104. package/test/types/hooks.test-d.ts +7 -3
  105. package/test/types/import.js +2 -0
  106. package/test/types/import.ts +1 -0
  107. package/test/types/instance.test-d.ts +61 -15
  108. package/test/types/logger.test-d.ts +44 -15
  109. package/test/types/route.test-d.ts +8 -2
  110. package/test/types/schema.test-d.ts +2 -39
  111. package/test/types/type-provider.test-d.ts +417 -0
  112. package/test/validation-error-handling.test.js +9 -9
  113. package/test/versioned-routes.test.js +29 -17
  114. package/test/wrapThenable.test.js +7 -6
  115. package/types/.eslintrc.json +1 -1
  116. package/types/content-type-parser.d.ts +17 -8
  117. package/types/hooks.d.ts +107 -60
  118. package/types/instance.d.ts +137 -105
  119. package/types/logger.d.ts +18 -104
  120. package/types/plugin.d.ts +10 -4
  121. package/types/register.d.ts +1 -1
  122. package/types/reply.d.ts +16 -11
  123. package/types/request.d.ts +10 -5
  124. package/types/route.d.ts +42 -31
  125. package/types/schema.d.ts +15 -1
  126. package/types/type-provider.d.ts +99 -0
  127. package/types/utils.d.ts +1 -1
  128. package/lib/schema-compilers.js +0 -12
  129. package/test/emit-warning.test.js +0 -166
package/lib/fourOhFour.js CHANGED
@@ -9,21 +9,17 @@ const {
9
9
  kRoutePrefix,
10
10
  kCanSetNotFoundHandler,
11
11
  kFourOhFourLevelInstance,
12
- kReply,
13
- kRequest,
14
- kContentTypeParser,
15
- kBodyLimit,
16
- kLogLevel,
17
12
  kFourOhFourContext,
18
- kHooks,
19
- kErrorHandler
13
+ kHooks
20
14
  } = require('./symbols.js')
21
15
  const { lifecycleHooks } = require('./hooks')
16
+ const { buildErrorHandler } = require('./error-handler.js')
22
17
  const fourOhFourContext = {
23
18
  config: {
24
19
  },
25
20
  onSend: [],
26
- onError: []
21
+ onError: [],
22
+ errorHandler: buildErrorHandler()
27
23
  }
28
24
 
29
25
  /**
@@ -34,10 +30,10 @@ const fourOhFourContext = {
34
30
  * kFourOhFourContext: the context in the reply object where the handler will be executed
35
31
  */
36
32
  function fourOhFour (options) {
37
- const { logger, genReqId } = options
33
+ const { logger, genReqId, disableRequestLogging } = options
38
34
 
39
35
  // 404 router, used for handling encapsulated 404 handlers
40
- const router = FindMyWay({ defaultRoute: fourOhFourFallBack })
36
+ const router = FindMyWay({ onBadUrl, defaultRoute: fourOhFourFallBack })
41
37
 
42
38
  return { router, setNotFoundHandler, setContext, arrange404 }
43
39
 
@@ -58,6 +54,26 @@ function fourOhFour (options) {
58
54
  })
59
55
  }
60
56
 
57
+ function onBadUrl (path, req, res) {
58
+ const { url, method } = req
59
+ const message = `Route ${method}:${url} not found`
60
+ const body = `{"error":"Not Found","message":"${message}","statusCode":404}`
61
+
62
+ // simulate normal route logging
63
+ if (!disableRequestLogging) {
64
+ const id = genReqId(req)
65
+ const childLogger = logger.child({ reqId: id })
66
+ childLogger.info({ req }, 'incoming request')
67
+ childLogger.info({ req }, message)
68
+ }
69
+
70
+ res.writeHead(404, {
71
+ 'Content-Type': 'application/json',
72
+ 'Content-Length': body.length
73
+ })
74
+ res.end(body)
75
+ }
76
+
61
77
  function setContext (instance, context) {
62
78
  const _404Context = Object.assign({}, instance[kFourOhFourContext])
63
79
  _404Context.onSend = context.onSend
@@ -118,17 +134,12 @@ function fourOhFour (options) {
118
134
  }
119
135
 
120
136
  function _setNotFoundHandler (prefix, opts, handler, avvio, routeHandler) {
121
- const context = new Context(
122
- opts.schema,
137
+ const context = new Context({
138
+ schema: opts.schema,
123
139
  handler,
124
- this[kReply],
125
- this[kRequest],
126
- this[kContentTypeParser],
127
- opts.config || {},
128
- this[kErrorHandler],
129
- this[kBodyLimit],
130
- this[kLogLevel]
131
- )
140
+ config: opts.config || {},
141
+ server: this
142
+ })
132
143
 
133
144
  avvio.once('preReady', () => {
134
145
  const context = this[kFourOhFourContext]
@@ -3,12 +3,14 @@
3
3
  const { validate: validateSchema } = require('./validation')
4
4
  const { hookRunner, hookIterator } = require('./hooks')
5
5
  const wrapThenable = require('./wrapThenable')
6
-
7
- const { kReplyIsError } = require('./symbols')
6
+ const {
7
+ kReplyIsError
8
+ } = require('./symbols')
8
9
 
9
10
  function handleRequest (err, request, reply) {
10
11
  if (reply.sent === true) return
11
12
  if (err != null) {
13
+ reply[kReplyIsError] = true
12
14
  reply.send(err)
13
15
  return
14
16
  }
@@ -55,7 +57,7 @@ function handleRequest (err, request, reply) {
55
57
  }
56
58
 
57
59
  // Return 404 instead of 405 see https://github.com/fastify/fastify/pull/862 for discussion
58
- reply.code(404).send(new Error('Not Found'))
60
+ handler(request, reply)
59
61
  }
60
62
 
61
63
  function handler (request, reply) {
@@ -77,11 +79,10 @@ function handler (request, reply) {
77
79
  }
78
80
 
79
81
  function preValidationCallback (err, request, reply) {
80
- if (reply.sent === true ||
81
- reply.raw.writableEnded === true ||
82
- reply.raw.writable === false) return
82
+ if (reply.sent === true) return
83
83
 
84
84
  if (err != null) {
85
+ reply[kReplyIsError] = true
85
86
  reply.send(err)
86
87
  return
87
88
  }
@@ -111,11 +112,10 @@ function preValidationCallback (err, request, reply) {
111
112
  }
112
113
 
113
114
  function preHandlerCallback (err, request, reply) {
114
- if (reply.sent ||
115
- reply.raw.writableEnded === true ||
116
- reply.raw.writable === false) return
115
+ if (reply.sent) return
117
116
 
118
117
  if (err != null) {
118
+ reply[kReplyIsError] = true
119
119
  reply.send(err)
120
120
  return
121
121
  }
@@ -125,10 +125,7 @@ function preHandlerCallback (err, request, reply) {
125
125
  try {
126
126
  result = reply.context.handler(request, reply)
127
127
  } catch (err) {
128
- if (!(err instanceof Error)) {
129
- reply[kReplyIsError] = true
130
- }
131
-
128
+ reply[kReplyIsError] = true
132
129
  reply.send(err)
133
130
  return
134
131
  }
package/lib/hooks.js CHANGED
@@ -21,11 +21,13 @@ const supportedHooks = lifecycleHooks.concat(applicationHooks)
21
21
  const {
22
22
  FST_ERR_HOOK_INVALID_TYPE,
23
23
  FST_ERR_HOOK_INVALID_HANDLER,
24
- FST_ERR_SEND_UNDEFINED_ERR
24
+ FST_ERR_SEND_UNDEFINED_ERR,
25
+ FST_ERR_HOOK_TIMEOUT,
26
+ AVVIO_ERRORS_MAP,
27
+ appendStackTrace
25
28
  } = require('./errors')
26
29
 
27
30
  const {
28
- kReplyIsError,
29
31
  kChildren,
30
32
  kHooks
31
33
  } = require('./symbols')
@@ -84,6 +86,14 @@ function hookRunnerApplication (hookName, boot, server, cb) {
84
86
 
85
87
  function exit (err) {
86
88
  if (err) {
89
+ if (err.code === 'AVV_ERR_READY_TIMEOUT') {
90
+ err = appendStackTrace(err, new FST_ERR_HOOK_TIMEOUT(hookName))
91
+ } else {
92
+ err = AVVIO_ERRORS_MAP[err.code] != null
93
+ ? appendStackTrace(err, new AVVIO_ERRORS_MAP[err.code](err.message))
94
+ : err
95
+ }
96
+
87
97
  cb(err)
88
98
  return
89
99
  }
@@ -178,9 +188,8 @@ function hookRunner (functions, runner, request, reply, cb) {
178
188
  function handleReject (err) {
179
189
  if (!err) {
180
190
  err = new FST_ERR_SEND_UNDEFINED_ERR()
181
- } else if (!(err instanceof Error)) {
182
- reply[kReplyIsError] = true
183
191
  }
192
+
184
193
  cb(err, request, reply)
185
194
  }
186
195
 
@@ -201,11 +210,7 @@ function onSendHookRunner (functions, request, reply, payload, cb) {
201
210
  }
202
211
 
203
212
  if (i === functions.length) {
204
- try {
205
- cb(null, request, reply, payload)
206
- } catch (err) {
207
- handleReject(err)
208
- }
213
+ cb(null, request, reply, payload)
209
214
  return
210
215
  }
211
216
 
@@ -0,0 +1,10 @@
1
+ 'use strict'
2
+
3
+ module.exports = function noopSet () {
4
+ return {
5
+ [Symbol.iterator]: function * () {},
6
+ add () {},
7
+ delete () {},
8
+ has () { return true }
9
+ }
10
+ }
@@ -39,10 +39,7 @@ module.exports = function override (old, fn, opts) {
39
39
  instance[kChildren] = []
40
40
 
41
41
  instance[kReply] = Reply.buildReply(instance[kReply])
42
- instance[kReply].prototype.server = instance
43
-
44
42
  instance[kRequest] = Request.buildRequest(instance[kRequest])
45
- instance[kRequest].prototype.server = instance
46
43
 
47
44
  instance[kContentTypeParser] = ContentTypeParser.helpers.buildContentTypeParser(instance[kContentTypeParser])
48
45
  instance[kHooks] = buildHooks(instance[kHooks])
@@ -102,9 +102,10 @@ function checkVersion (fn) {
102
102
  if (!meta) return
103
103
 
104
104
  const requiredVersion = meta.fastify
105
- if (!requiredVersion) return
106
105
 
107
- if (!semver.satisfies(this.version, requiredVersion)) throw new FST_ERR_PLUGIN_VERSION_MISMATCH(meta.name, requiredVersion, this.version)
106
+ if (requiredVersion && !semver.satisfies(this.version, requiredVersion)) {
107
+ throw new FST_ERR_PLUGIN_VERSION_MISMATCH(meta.name, requiredVersion, this.version)
108
+ }
108
109
  }
109
110
 
110
111
  function registerPluginName (fn) {
package/lib/reply.js CHANGED
@@ -1,15 +1,11 @@
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,
@@ -18,6 +14,7 @@ const {
18
14
  kReplyHeaders,
19
15
  kReplyHasStatusCode,
20
16
  kReplyIsRunningOnErrorHook,
17
+ kReplyNextErrorHandler,
21
18
  kDisableRequestLogging
22
19
  } = require('./symbols.js')
23
20
  const { hookRunner, hookIterator, onSendHookRunner } = require('./hooks')
@@ -25,17 +22,8 @@ const { hookRunner, hookIterator, onSendHookRunner } = require('./hooks')
25
22
  const internals = require('./handleRequest')[Symbol.for('internals')]
26
23
  const loggerUtils = require('./logger')
27
24
  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
- })
25
+ const { handleError } = require('./error-handler')
26
+ const { getSchemaSerializer } = require('./schemas')
39
27
 
40
28
  const CONTENT_TYPE = {
41
29
  JSON: 'application/json; charset=utf-8',
@@ -53,7 +41,6 @@ const warning = require('./warnings')
53
41
 
54
42
  function Reply (res, request, log) {
55
43
  this.raw = res
56
- this[kReplySent] = false
57
44
  this[kReplySerializer] = null
58
45
  this[kReplyErrorHandlerCalled] = false
59
46
  this[kReplyIsError] = false
@@ -72,28 +59,30 @@ Object.defineProperties(Reply.prototype, {
72
59
  return this.request.context
73
60
  }
74
61
  },
75
- res: {
62
+ server: {
76
63
  get () {
77
- warning.emit('FSTDEP002')
78
- return this.raw
64
+ return this.request.context.server
79
65
  }
80
66
  },
81
67
  sent: {
82
68
  enumerable: true,
83
69
  get () {
84
- return this[kReplySent]
70
+ // We are checking whether reply was hijacked or the response has ended.
71
+ return (this[kReplyHijacked] || this.raw.writableEnded) === true
85
72
  },
86
73
  set (value) {
74
+ warning.emit('FSTDEP010')
75
+
87
76
  if (value !== true) {
88
77
  throw new FST_ERR_REP_SENT_VALUE()
89
78
  }
90
79
 
91
- if (this[kReplySent]) {
80
+ // We throw only if sent was overwritten from Fastify
81
+ if (this.sent && this[kReplyHijacked]) {
92
82
  throw new FST_ERR_REP_ALREADY_SENT()
93
83
  }
94
84
 
95
- this[kReplySentOverwritten] = true
96
- this[kReplySent] = true
85
+ this[kReplyHijacked] = true
97
86
  }
98
87
  },
99
88
  statusCode: {
@@ -103,15 +92,11 @@ Object.defineProperties(Reply.prototype, {
103
92
  set (value) {
104
93
  this.code(value)
105
94
  }
106
- },
107
- server: {
108
- value: null,
109
- writable: true
110
95
  }
111
96
  })
112
97
 
113
98
  Reply.prototype.hijack = function () {
114
- this[kReplySent] = true
99
+ this[kReplyHijacked] = true
115
100
  return this
116
101
  }
117
102
 
@@ -120,12 +105,13 @@ Reply.prototype.send = function (payload) {
120
105
  throw new FST_ERR_SEND_INSIDE_ONERR()
121
106
  }
122
107
 
123
- if (this[kReplySent]) {
108
+ if (this.sent) {
124
109
  this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT() }, 'Reply already sent')
125
110
  return this
126
111
  }
127
112
 
128
113
  if (payload instanceof Error || this[kReplyIsError] === true) {
114
+ this[kReplyIsError] = false
129
115
  onErrorHook(this, payload, onSendHook)
130
116
  return this
131
117
  }
@@ -139,7 +125,12 @@ Reply.prototype.send = function (payload) {
139
125
  const hasContentType = contentType !== undefined
140
126
 
141
127
  if (payload !== null) {
142
- if (Buffer.isBuffer(payload) || typeof payload.pipe === 'function') {
128
+ if (typeof payload.pipe === 'function') {
129
+ onSendHook(this, payload)
130
+ return this
131
+ }
132
+
133
+ if (Buffer.isBuffer(payload)) {
143
134
  if (hasContentType === false) {
144
135
  this[kReplyHeaders]['content-type'] = CONTENT_TYPE.OCTET
145
136
  }
@@ -365,22 +356,29 @@ function preserializeHookEnd (err, request, reply, payload) {
365
356
  return
366
357
  }
367
358
 
368
- if (reply[kReplySerializer] !== null) {
369
- payload = reply[kReplySerializer](payload)
370
- } else if (reply.context && reply.context[kReplySerializerDefault]) {
371
- payload = reply.context[kReplySerializerDefault](payload, reply.raw.statusCode)
372
- } else {
373
- payload = serialize(reply.context, payload, reply.raw.statusCode)
359
+ try {
360
+ if (reply[kReplySerializer] !== null) {
361
+ payload = reply[kReplySerializer](payload)
362
+ } else if (reply.context && reply.context[kReplySerializerDefault]) {
363
+ payload = reply.context[kReplySerializerDefault](payload, reply.raw.statusCode)
364
+ } else {
365
+ payload = serialize(reply.context, payload, reply.raw.statusCode)
366
+ }
367
+ } catch (e) {
368
+ wrapSeralizationError(e, reply)
369
+ onErrorHook(reply, e)
370
+ return
374
371
  }
375
372
 
376
- flatstr(payload)
377
-
378
373
  onSendHook(reply, payload)
379
374
  }
380
375
 
376
+ function wrapSeralizationError (error, reply) {
377
+ error.serialization = reply.context.config
378
+ }
379
+
381
380
  function onSendHook (reply, payload) {
382
381
  if (reply.context.onSend !== null) {
383
- reply[kReplySent] = true
384
382
  onSendHookRunner(
385
383
  reply.context.onSend,
386
384
  reply.request,
@@ -407,8 +405,6 @@ function onSendEnd (reply, payload) {
407
405
  const statusCode = res.statusCode
408
406
 
409
407
  if (payload === undefined || payload === null) {
410
- reply[kReplySent] = true
411
-
412
408
  // according to https://tools.ietf.org/html/rfc7230#section-3.3.2
413
409
  // we cannot send a content-length for 304 and 204, and all status code
414
410
  // < 200.
@@ -424,8 +420,6 @@ function onSendEnd (reply, payload) {
424
420
  }
425
421
 
426
422
  if (typeof payload.pipe === 'function') {
427
- reply[kReplySent] = true
428
-
429
423
  sendStream(payload, res, reply)
430
424
  return
431
425
  }
@@ -440,8 +434,6 @@ function onSendEnd (reply, payload) {
440
434
  reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
441
435
  }
442
436
 
443
- reply[kReplySent] = true
444
-
445
437
  res.writeHead(statusCode, reply[kReplyHeaders])
446
438
 
447
439
  // avoid ArgumentsAdaptorTrampoline from V8
@@ -511,8 +503,7 @@ function sendStream (payload, res, reply) {
511
503
  }
512
504
 
513
505
  function onErrorHook (reply, error, cb) {
514
- reply[kReplySent] = true
515
- if (reply.context.onError !== null && reply[kReplyErrorHandlerCalled] === true) {
506
+ if (reply.context.onError !== null && !reply[kReplyNextErrorHandler]) {
516
507
  reply[kReplyIsRunningOnErrorHook] = true
517
508
  onSendHookRunner(
518
509
  reply.context.onError,
@@ -526,89 +517,6 @@ function onErrorHook (reply, error, cb) {
526
517
  }
527
518
  }
528
519
 
529
- function handleError (reply, error, cb) {
530
- reply[kReplyIsRunningOnErrorHook] = false
531
- const res = reply.raw
532
- let statusCode = res.statusCode
533
- statusCode = (statusCode >= 400) ? statusCode : 500
534
- // treat undefined and null as same
535
- if (error != null) {
536
- if (error.headers !== undefined) {
537
- reply.headers(error.headers)
538
- }
539
- if (error.status >= 400) {
540
- statusCode = error.status
541
- } else if (error.statusCode >= 400) {
542
- statusCode = error.statusCode
543
- }
544
- }
545
-
546
- res.statusCode = statusCode
547
-
548
- const errorHandler = reply.context.errorHandler
549
- if (errorHandler && reply[kReplyErrorHandlerCalled] === false) {
550
- reply[kReplySent] = false
551
- reply[kReplyIsError] = false
552
- reply[kReplyErrorHandlerCalled] = true
553
- // remove header is needed in here, because when we pipe to a stream
554
- // `undefined` value header will directly passed to node response
555
- reply.removeHeader('content-length')
556
- const result = errorHandler(error, reply.request, reply)
557
- if (result !== undefined) {
558
- if (result !== null && typeof result.then === 'function') {
559
- wrapThenable(result, reply)
560
- } else {
561
- reply.send(result)
562
- }
563
- }
564
- return
565
- }
566
-
567
- let payload
568
- try {
569
- const serializerFn = getSchemaSerializer(reply.context, statusCode)
570
- payload = (serializerFn === false)
571
- ? serializeError({
572
- error: statusCodes[statusCode + ''],
573
- code: error.code,
574
- message: error.message || '',
575
- statusCode: statusCode
576
- })
577
- : serializerFn(Object.create(error, {
578
- error: { value: statusCodes[statusCode + ''] },
579
- message: { value: error.message || '' },
580
- statusCode: { value: statusCode }
581
- }))
582
-
583
- if (serializerFn !== false && typeof payload !== 'string') {
584
- throw new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload)
585
- }
586
- } catch (err) {
587
- // error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization
588
- reply.log.error({ err, statusCode: res.statusCode }, 'The serializer for the given status code failed')
589
- res.statusCode = 500
590
- payload = serializeError({
591
- error: statusCodes['500'],
592
- code: err.code,
593
- message: err.message,
594
- statusCode: 500
595
- })
596
- }
597
-
598
- flatstr(payload)
599
- reply[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON
600
- reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
601
-
602
- if (cb) {
603
- cb(reply, payload)
604
- return
605
- }
606
-
607
- reply[kReplySent] = true
608
- res.writeHead(res.statusCode, reply[kReplyHeaders])
609
- res.end(payload)
610
- }
611
-
612
520
  function setupResponseListeners (reply) {
613
521
  reply[kReplyStartTime] = now()
614
522
 
@@ -669,8 +577,7 @@ function buildReply (R) {
669
577
  this.raw = res
670
578
  this[kReplyIsError] = false
671
579
  this[kReplyErrorHandlerCalled] = false
672
- this[kReplySent] = false
673
- this[kReplySentOverwritten] = false
580
+ this[kReplyHijacked] = false
674
581
  this[kReplySerializer] = null
675
582
  this.request = request
676
583
  this[kReplyHeaders] = {}
@@ -686,15 +593,14 @@ function buildReply (R) {
686
593
  this[prop.key] = prop.value
687
594
  }
688
595
  }
689
- _Reply.prototype = new R()
596
+ Object.setPrototypeOf(_Reply.prototype, R.prototype)
597
+ Object.setPrototypeOf(_Reply, R)
598
+ _Reply.parent = R
690
599
  _Reply.props = props
691
600
  return _Reply
692
601
  }
693
602
 
694
603
  function notFound (reply) {
695
- reply[kReplySent] = false
696
- reply[kReplyIsError] = false
697
-
698
604
  if (reply.context[kFourOhFourContext] === null) {
699
605
  reply.log.warn('Trying to send a NotFound error inside a 404 handler. Sending basic 404 response.')
700
606
  reply.code(404).send('404 Not Found')
@@ -735,31 +641,6 @@ function serialize (context, data, statusCode) {
735
641
  return JSON.stringify(data)
736
642
  }
737
643
 
738
- /**
739
- * Search for the right JSON schema compiled function in the request context
740
- * setup by the route configuration `schema.response`.
741
- * It will look for the exact match (eg 200) or generic (eg 2xx)
742
- *
743
- * @param {object} context the request context
744
- * @param {number} statusCode the http status code
745
- * @returns {function|boolean} the right JSON Schema function to serialize
746
- * the reply or false if it is not set
747
- */
748
- function getSchemaSerializer (context, statusCode) {
749
- const responseSchemaDef = context[kSchemaResponse]
750
- if (!responseSchemaDef) {
751
- return false
752
- }
753
- if (responseSchemaDef[statusCode]) {
754
- return responseSchemaDef[statusCode]
755
- }
756
- const fallbackStatusCode = (statusCode + '')[0] + 'xx'
757
- if (responseSchemaDef[fallbackStatusCode]) {
758
- return responseSchemaDef[fallbackStatusCode]
759
- }
760
- return false
761
- }
762
-
763
644
  function noop () { }
764
645
 
765
646
  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