fastify 3.27.3 → 4.0.0-alpha.2

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 (164) 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/Getting-Started.md +7 -7
  7. package/docs/Guides/Plugins-Guide.md +1 -1
  8. package/docs/Guides/Serverless.md +3 -3
  9. package/docs/Guides/Testing.md +2 -2
  10. package/docs/Migration-Guide-V4.md +12 -0
  11. package/docs/Reference/ContentTypeParser.md +4 -0
  12. package/docs/Reference/Decorators.md +2 -2
  13. package/docs/Reference/Encapsulation.md +2 -2
  14. package/docs/Reference/Errors.md +51 -6
  15. package/docs/Reference/HTTP2.md +3 -3
  16. package/docs/Reference/Hooks.md +4 -7
  17. package/docs/Reference/LTS.md +5 -4
  18. package/docs/Reference/Plugins.md +3 -3
  19. package/docs/Reference/Reply.md +23 -22
  20. package/docs/Reference/Request.md +1 -3
  21. package/docs/Reference/Routes.md +22 -15
  22. package/docs/Reference/Server.md +69 -119
  23. package/docs/Reference/TypeScript.md +20 -22
  24. package/docs/Reference/Validation-and-Serialization.md +30 -55
  25. package/docs/Type-Providers.md +257 -0
  26. package/examples/asyncawait.js +1 -1
  27. package/examples/benchmark/hooks-benchmark-async-await.js +1 -1
  28. package/examples/benchmark/hooks-benchmark.js +1 -1
  29. package/examples/benchmark/simple.js +1 -1
  30. package/examples/hooks.js +2 -2
  31. package/examples/http2.js +1 -1
  32. package/examples/https.js +1 -1
  33. package/examples/parser.js +1 -1
  34. package/examples/route-prefix.js +1 -1
  35. package/examples/shared-schema.js +1 -1
  36. package/examples/simple-stream.js +18 -0
  37. package/examples/simple.js +1 -1
  38. package/examples/simple.mjs +1 -1
  39. package/examples/typescript-server.ts +1 -1
  40. package/examples/use-plugin.js +1 -1
  41. package/fastify.d.ts +34 -22
  42. package/fastify.js +40 -36
  43. package/lib/configValidator.js +902 -1023
  44. package/lib/contentTypeParser.js +6 -16
  45. package/lib/context.js +36 -10
  46. package/lib/decorate.js +3 -1
  47. package/lib/error-handler.js +158 -0
  48. package/lib/error-serializer.js +257 -0
  49. package/lib/errors.js +43 -9
  50. package/lib/fourOhFour.js +31 -20
  51. package/lib/handleRequest.js +10 -13
  52. package/lib/hooks.js +14 -9
  53. package/lib/pluginOverride.js +0 -3
  54. package/lib/pluginUtils.js +3 -2
  55. package/lib/reply.js +29 -158
  56. package/lib/request.js +13 -10
  57. package/lib/route.js +131 -138
  58. package/lib/schema-controller.js +2 -2
  59. package/lib/schemas.js +27 -1
  60. package/lib/server.js +241 -116
  61. package/lib/symbols.js +4 -3
  62. package/lib/validation.js +2 -1
  63. package/lib/warnings.js +4 -12
  64. package/lib/wrapThenable.js +4 -11
  65. package/package.json +37 -39
  66. package/test/404s.test.js +258 -125
  67. package/test/500s.test.js +3 -3
  68. package/test/als.test.js +1 -1
  69. package/test/async-await.test.js +20 -76
  70. package/test/bodyLimit.test.js +1 -1
  71. package/test/build-certificate.js +6 -7
  72. package/test/case-insensitive.test.js +4 -4
  73. package/test/close-pipelining.test.js +2 -2
  74. package/test/close.test.js +11 -11
  75. package/test/content-parser.test.js +32 -0
  76. package/test/context-config.test.js +52 -0
  77. package/test/custom-http-server.test.js +14 -7
  78. package/test/custom-parser-async.test.js +1 -66
  79. package/test/custom-parser.test.js +92 -159
  80. package/test/custom-querystring-parser.test.js +3 -3
  81. package/test/decorator.test.js +11 -13
  82. package/test/delete.test.js +6 -6
  83. package/test/encapsulated-error-handler.test.js +50 -0
  84. package/test/esm/index.test.js +0 -14
  85. package/test/fastify-instance.test.js +4 -4
  86. package/test/fluent-schema.test.js +4 -4
  87. package/test/genReqId.test.js +1 -1
  88. package/test/get.test.js +4 -4
  89. package/test/handler-context.test.js +2 -2
  90. package/test/head.test.js +1 -1
  91. package/test/helper.js +19 -4
  92. package/test/hooks-async.test.js +15 -48
  93. package/test/hooks.on-ready.test.js +10 -5
  94. package/test/hooks.test.js +78 -119
  95. package/test/http2/closing.test.js +10 -16
  96. package/test/http2/constraint.test.js +1 -1
  97. package/test/http2/head.test.js +1 -1
  98. package/test/http2/plain.test.js +1 -1
  99. package/test/http2/secure-with-fallback.test.js +1 -1
  100. package/test/http2/secure.test.js +1 -1
  101. package/test/http2/unknown-http-method.test.js +4 -10
  102. package/test/https/custom-https-server.test.js +12 -6
  103. package/test/https/https.test.js +1 -1
  104. package/test/input-validation.js +3 -3
  105. package/test/internals/handleRequest.test.js +6 -43
  106. package/test/internals/initialConfig.test.js +41 -12
  107. package/test/internals/logger.test.js +2 -2
  108. package/test/internals/reply.test.js +281 -40
  109. package/test/internals/request.test.js +13 -7
  110. package/test/internals/server.test.js +88 -0
  111. package/test/listen.deprecated.test.js +202 -0
  112. package/test/listen.test.js +118 -150
  113. package/test/logger.test.js +82 -42
  114. package/test/maxRequestsPerSocket.test.js +8 -6
  115. package/test/middleware.test.js +2 -25
  116. package/test/nullable-validation.test.js +53 -16
  117. package/test/output-validation.test.js +1 -1
  118. package/test/plugin.test.js +47 -21
  119. package/test/pretty-print.test.js +22 -10
  120. package/test/promises.test.js +1 -1
  121. package/test/proto-poisoning.test.js +6 -6
  122. package/test/register.test.js +3 -3
  123. package/test/reply-error.test.js +126 -15
  124. package/test/request-error.test.js +3 -6
  125. package/test/route-hooks.test.js +18 -18
  126. package/test/route-prefix.test.js +2 -1
  127. package/test/route.test.js +206 -22
  128. package/test/router-options.test.js +2 -2
  129. package/test/schema-examples.test.js +11 -5
  130. package/test/schema-feature.test.js +25 -20
  131. package/test/schema-serialization.test.js +9 -9
  132. package/test/schema-special-usage.test.js +5 -153
  133. package/test/schema-validation.test.js +9 -9
  134. package/test/skip-reply-send.test.js +2 -2
  135. package/test/stream.test.js +82 -23
  136. package/test/throw.test.js +8 -5
  137. package/test/trust-proxy.test.js +6 -6
  138. package/test/type-provider.test.js +20 -0
  139. package/test/types/fastify.test-d.ts +10 -18
  140. package/test/types/import.js +2 -0
  141. package/test/types/import.ts +1 -0
  142. package/test/types/instance.test-d.ts +68 -17
  143. package/test/types/logger.test-d.ts +44 -15
  144. package/test/types/reply.test-d.ts +2 -1
  145. package/test/types/route.test-d.ts +8 -2
  146. package/test/types/schema.test-d.ts +2 -39
  147. package/test/types/type-provider.test-d.ts +417 -0
  148. package/test/url-rewriting.test.js +3 -3
  149. package/test/validation-error-handling.test.js +8 -8
  150. package/test/versioned-routes.test.js +30 -18
  151. package/test/wrapThenable.test.js +7 -6
  152. package/types/content-type-parser.d.ts +17 -8
  153. package/types/hooks.d.ts +102 -59
  154. package/types/instance.d.ts +244 -118
  155. package/types/logger.d.ts +18 -104
  156. package/types/plugin.d.ts +10 -4
  157. package/types/reply.d.ts +18 -12
  158. package/types/request.d.ts +10 -5
  159. package/types/route.d.ts +42 -31
  160. package/types/schema.d.ts +1 -1
  161. package/types/type-provider.d.ts +99 -0
  162. package/types/utils.d.ts +1 -1
  163. package/lib/schema-compilers.js +0 -12
  164. 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
 
@@ -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
  }
@@ -379,8 +370,6 @@ function preserializeHookEnd (err, request, reply, payload) {
379
370
  return
380
371
  }
381
372
 
382
- flatstr(payload)
383
-
384
373
  onSendHook(reply, payload)
385
374
  }
386
375
 
@@ -390,7 +379,6 @@ function wrapSeralizationError (error, reply) {
390
379
 
391
380
  function onSendHook (reply, payload) {
392
381
  if (reply.context.onSend !== null) {
393
- reply[kReplySent] = true
394
382
  onSendHookRunner(
395
383
  reply.context.onSend,
396
384
  reply.request,
@@ -417,8 +405,6 @@ function onSendEnd (reply, payload) {
417
405
  const statusCode = res.statusCode
418
406
 
419
407
  if (payload === undefined || payload === null) {
420
- reply[kReplySent] = true
421
-
422
408
  // according to https://tools.ietf.org/html/rfc7230#section-3.3.2
423
409
  // we cannot send a content-length for 304 and 204, and all status code
424
410
  // < 200.
@@ -434,8 +420,6 @@ function onSendEnd (reply, payload) {
434
420
  }
435
421
 
436
422
  if (typeof payload.pipe === 'function') {
437
- reply[kReplySent] = true
438
-
439
423
  sendStream(payload, res, reply)
440
424
  return
441
425
  }
@@ -450,8 +434,6 @@ function onSendEnd (reply, payload) {
450
434
  reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
451
435
  }
452
436
 
453
- reply[kReplySent] = true
454
-
455
437
  res.writeHead(statusCode, reply[kReplyHeaders])
456
438
 
457
439
  // avoid ArgumentsAdaptorTrampoline from V8
@@ -475,7 +457,7 @@ function sendStream (payload, res, reply) {
475
457
  eos(payload, { readable: true, writable: false }, function (err) {
476
458
  sourceOpen = false
477
459
  if (err != null) {
478
- if (res.headersSent) {
460
+ if (res.headersSent || reply.request.raw.aborted === true) {
479
461
  if (!errorLogged) {
480
462
  errorLogged = true
481
463
  logStreamError(reply.log, err, res)
@@ -521,8 +503,7 @@ function sendStream (payload, res, reply) {
521
503
  }
522
504
 
523
505
  function onErrorHook (reply, error, cb) {
524
- reply[kReplySent] = true
525
- if (reply.context.onError !== null && reply[kReplyErrorHandlerCalled] === true) {
506
+ if (reply.context.onError !== null && !reply[kReplyNextErrorHandler]) {
526
507
  reply[kReplyIsRunningOnErrorHook] = true
527
508
  onSendHookRunner(
528
509
  reply.context.onError,
@@ -536,89 +517,6 @@ function onErrorHook (reply, error, cb) {
536
517
  }
537
518
  }
538
519
 
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
520
  function setupResponseListeners (reply) {
623
521
  reply[kReplyStartTime] = now()
624
522
 
@@ -679,8 +577,7 @@ function buildReply (R) {
679
577
  this.raw = res
680
578
  this[kReplyIsError] = false
681
579
  this[kReplyErrorHandlerCalled] = false
682
- this[kReplySent] = false
683
- this[kReplySentOverwritten] = false
580
+ this[kReplyHijacked] = false
684
581
  this[kReplySerializer] = null
685
582
  this.request = request
686
583
  this[kReplyHeaders] = {}
@@ -696,15 +593,14 @@ function buildReply (R) {
696
593
  this[prop.key] = prop.value
697
594
  }
698
595
  }
699
- _Reply.prototype = new R()
596
+ Object.setPrototypeOf(_Reply.prototype, R.prototype)
597
+ Object.setPrototypeOf(_Reply, R)
598
+ _Reply.parent = R
700
599
  _Reply.props = props
701
600
  return _Reply
702
601
  }
703
602
 
704
603
  function notFound (reply) {
705
- reply[kReplySent] = false
706
- reply[kReplyIsError] = false
707
-
708
604
  if (reply.context[kFourOhFourContext] === null) {
709
605
  reply.log.warn('Trying to send a NotFound error inside a 404 handler. Sending basic 404 response.')
710
606
  reply.code(404).send('404 Not Found')
@@ -745,31 +641,6 @@ function serialize (context, data, statusCode) {
745
641
  return JSON.stringify(data)
746
642
  }
747
643
 
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
644
  function noop () { }
774
645
 
775
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