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.
- 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/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 +23 -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 +1 -1
- 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 +43 -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 +29 -158
- 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 +241 -116
- package/lib/symbols.js +4 -3
- package/lib/validation.js +2 -1
- 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 +281 -40
- 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 +118 -150
- 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/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/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/route.test-d.ts +8 -2
- package/test/types/schema.test-d.ts +2 -39
- package/test/types/type-provider.test-d.ts +417 -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 +102 -59
- package/types/instance.d.ts +244 -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 +10 -5
- package/types/route.d.ts +42 -31
- 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/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
|
-
|
|
125
|
-
this
|
|
126
|
-
|
|
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]
|
package/lib/handleRequest.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
package/lib/pluginOverride.js
CHANGED
|
@@ -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])
|
package/lib/pluginUtils.js
CHANGED
|
@@ -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))
|
|
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
|
-
|
|
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,
|
|
@@ -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
|
|
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
|
-
|
|
62
|
+
server: {
|
|
76
63
|
get () {
|
|
77
|
-
|
|
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
|
-
|
|
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
|
|
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[
|
|
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[
|
|
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
|
|
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 (
|
|
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[
|
|
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[
|
|
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
|
|
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 =
|
|
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
|
|