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.
- package/.taprc +3 -0
- package/README.md +7 -7
- package/build/build-error-serializer.js +27 -0
- package/build/build-validation.js +47 -35
- package/docs/Guides/Database.md +320 -0
- package/docs/Guides/Ecosystem.md +9 -0
- package/docs/Guides/Getting-Started.md +7 -7
- package/docs/Guides/Plugins-Guide.md +1 -1
- package/docs/Guides/Serverless.md +3 -3
- package/docs/Guides/Testing.md +2 -2
- package/docs/Migration-Guide-V4.md +12 -0
- package/docs/Reference/ContentTypeParser.md +4 -0
- package/docs/Reference/Decorators.md +2 -2
- package/docs/Reference/Encapsulation.md +2 -2
- package/docs/Reference/Errors.md +51 -6
- package/docs/Reference/HTTP2.md +3 -3
- package/docs/Reference/Hooks.md +4 -7
- package/docs/Reference/LTS.md +5 -4
- package/docs/Reference/Plugins.md +3 -3
- package/docs/Reference/Reply.md +73 -22
- package/docs/Reference/Request.md +1 -3
- package/docs/Reference/Routes.md +22 -15
- package/docs/Reference/Server.md +69 -119
- package/docs/Reference/TypeScript.md +20 -22
- package/docs/Reference/Validation-and-Serialization.md +30 -55
- package/docs/Type-Providers.md +257 -0
- package/examples/asyncawait.js +1 -1
- package/examples/benchmark/hooks-benchmark-async-await.js +1 -1
- package/examples/benchmark/hooks-benchmark.js +1 -1
- package/examples/benchmark/simple.js +1 -1
- package/examples/hooks.js +2 -2
- package/examples/http2.js +1 -1
- package/examples/https.js +1 -1
- package/examples/parser.js +13 -3
- package/examples/route-prefix.js +1 -1
- package/examples/shared-schema.js +1 -1
- package/examples/simple-stream.js +18 -0
- package/examples/simple.js +1 -1
- package/examples/simple.mjs +1 -1
- package/examples/typescript-server.ts +1 -1
- package/examples/use-plugin.js +1 -1
- package/fastify.d.ts +34 -22
- package/fastify.js +40 -36
- package/lib/configValidator.js +902 -1023
- package/lib/contentTypeParser.js +6 -16
- package/lib/context.js +36 -10
- package/lib/decorate.js +3 -1
- package/lib/error-handler.js +158 -0
- package/lib/error-serializer.js +257 -0
- package/lib/errors.js +51 -9
- package/lib/fourOhFour.js +31 -20
- package/lib/handleRequest.js +10 -13
- package/lib/hooks.js +14 -9
- package/lib/pluginOverride.js +0 -3
- package/lib/pluginUtils.js +3 -2
- package/lib/reply.js +121 -175
- package/lib/request.js +13 -10
- package/lib/route.js +131 -138
- package/lib/schema-controller.js +2 -2
- package/lib/schemas.js +27 -1
- package/lib/server.js +242 -116
- package/lib/symbols.js +5 -3
- package/lib/validation.js +11 -9
- package/lib/warnings.js +4 -12
- package/lib/wrapThenable.js +4 -11
- package/package.json +37 -39
- package/test/404s.test.js +258 -125
- package/test/500s.test.js +3 -3
- package/test/als.test.js +1 -1
- package/test/async-await.test.js +20 -76
- package/test/bodyLimit.test.js +1 -1
- package/test/build-certificate.js +6 -7
- package/test/case-insensitive.test.js +4 -4
- package/test/close-pipelining.test.js +2 -2
- package/test/close.test.js +11 -11
- package/test/content-parser.test.js +32 -0
- package/test/context-config.test.js +52 -0
- package/test/custom-http-server.test.js +14 -7
- package/test/custom-parser-async.test.js +1 -66
- package/test/custom-parser.test.js +92 -159
- package/test/custom-querystring-parser.test.js +3 -3
- package/test/decorator.test.js +11 -13
- package/test/delete.test.js +6 -6
- package/test/encapsulated-error-handler.test.js +50 -0
- package/test/esm/index.test.js +0 -14
- package/test/fastify-instance.test.js +4 -4
- package/test/fluent-schema.test.js +4 -4
- package/test/genReqId.test.js +1 -1
- package/test/get.test.js +4 -4
- package/test/handler-context.test.js +2 -2
- package/test/head.test.js +1 -1
- package/test/helper.js +19 -4
- package/test/hooks-async.test.js +15 -48
- package/test/hooks.on-ready.test.js +10 -5
- package/test/hooks.test.js +78 -119
- package/test/http2/closing.test.js +10 -16
- package/test/http2/constraint.test.js +1 -1
- package/test/http2/head.test.js +1 -1
- package/test/http2/plain.test.js +1 -1
- package/test/http2/secure-with-fallback.test.js +1 -1
- package/test/http2/secure.test.js +1 -1
- package/test/http2/unknown-http-method.test.js +4 -10
- package/test/https/custom-https-server.test.js +12 -6
- package/test/https/https.test.js +1 -1
- package/test/input-validation.js +3 -3
- package/test/internals/handleRequest.test.js +6 -43
- package/test/internals/initialConfig.test.js +41 -12
- package/test/internals/logger.test.js +2 -2
- package/test/internals/reply.test.js +317 -48
- package/test/internals/request.test.js +13 -7
- package/test/internals/server.test.js +88 -0
- package/test/listen.deprecated.test.js +202 -0
- package/test/listen.test.js +140 -145
- package/test/logger.test.js +82 -42
- package/test/maxRequestsPerSocket.test.js +8 -6
- package/test/middleware.test.js +2 -25
- package/test/nullable-validation.test.js +53 -16
- package/test/output-validation.test.js +1 -1
- package/test/plugin.test.js +47 -21
- package/test/pretty-print.test.js +22 -10
- package/test/promises.test.js +1 -1
- package/test/proto-poisoning.test.js +6 -6
- package/test/register.test.js +3 -3
- package/test/reply-error.test.js +126 -15
- package/test/reply-trailers.test.js +270 -0
- package/test/request-error.test.js +3 -6
- package/test/route-hooks.test.js +18 -18
- package/test/route-prefix.test.js +2 -1
- package/test/route.test.js +206 -22
- package/test/router-options.test.js +2 -2
- package/test/schema-examples.test.js +11 -5
- package/test/schema-feature.test.js +25 -20
- package/test/schema-serialization.test.js +9 -9
- package/test/schema-special-usage.test.js +5 -153
- package/test/schema-validation.test.js +9 -9
- package/test/skip-reply-send.test.js +2 -2
- package/test/stream.test.js +82 -23
- package/test/throw.test.js +8 -5
- package/test/trust-proxy.test.js +6 -6
- package/test/type-provider.test.js +20 -0
- package/test/types/fastify.test-d.ts +10 -18
- package/test/types/hooks.test-d.ts +61 -5
- package/test/types/import.js +2 -0
- package/test/types/import.ts +1 -0
- package/test/types/instance.test-d.ts +68 -17
- package/test/types/logger.test-d.ts +44 -15
- package/test/types/reply.test-d.ts +2 -1
- package/test/types/request.test-d.ts +71 -1
- package/test/types/route.test-d.ts +8 -2
- package/test/types/schema.test-d.ts +2 -39
- package/test/types/type-provider.test-d.ts +424 -0
- package/test/url-rewriting.test.js +3 -3
- package/test/validation-error-handling.test.js +8 -8
- package/test/versioned-routes.test.js +30 -18
- package/test/wrapThenable.test.js +7 -6
- package/types/content-type-parser.d.ts +17 -8
- package/types/hooks.d.ts +182 -85
- package/types/instance.d.ts +286 -118
- package/types/logger.d.ts +18 -104
- package/types/plugin.d.ts +10 -4
- package/types/reply.d.ts +18 -12
- package/types/request.d.ts +13 -8
- package/types/route.d.ts +62 -34
- package/types/schema.d.ts +1 -1
- package/types/type-provider.d.ts +99 -0
- package/types/utils.d.ts +1 -1
- package/lib/schema-compilers.js +0 -12
- 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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
66
|
+
server: {
|
|
76
67
|
get () {
|
|
77
|
-
|
|
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
|
-
|
|
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
|
|
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[
|
|
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[
|
|
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
|
|
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 (
|
|
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
|
|
165
|
+
// If user doesn't set charset, we will set charset to utf-8
|
|
171
166
|
if (contentType.indexOf('charset') === -1) {
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
|
|
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
|
-
|
|
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
|
|
420
|
-
|
|
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(
|
|
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[
|
|
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[
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
124
|
+
server: {
|
|
117
125
|
get () {
|
|
118
|
-
|
|
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
|
|