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.
- package/README.md +5 -4
- package/build/build-error-serializer.js +27 -0
- package/build/build-validation.js +49 -35
- package/docs/Guides/Ecosystem.md +2 -1
- package/docs/Guides/Prototype-Poisoning.md +3 -3
- package/docs/Migration-Guide-V4.md +12 -0
- package/docs/Reference/ContentTypeParser.md +8 -1
- package/docs/Reference/Errors.md +51 -6
- package/docs/Reference/Hooks.md +4 -7
- package/docs/Reference/LTS.md +5 -4
- package/docs/Reference/Reply.md +23 -22
- package/docs/Reference/Request.md +1 -3
- package/docs/Reference/Routes.md +17 -10
- package/docs/Reference/Server.md +98 -63
- package/docs/Reference/TypeScript.md +11 -13
- package/docs/Reference/Validation-and-Serialization.md +32 -54
- package/docs/Type-Providers.md +257 -0
- package/examples/hooks.js +1 -1
- package/examples/simple-stream.js +18 -0
- package/fastify.d.ts +36 -22
- package/fastify.js +72 -53
- package/lib/configValidator.js +902 -1023
- package/lib/contentTypeParser.js +6 -16
- package/lib/context.js +36 -10
- package/lib/decorate.js +5 -3
- package/lib/error-handler.js +158 -0
- package/lib/error-serializer.js +257 -0
- package/lib/errors.js +49 -10
- package/lib/fourOhFour.js +31 -20
- package/lib/handleRequest.js +10 -13
- package/lib/hooks.js +14 -9
- package/lib/noop-set.js +10 -0
- package/lib/pluginOverride.js +0 -3
- package/lib/pluginUtils.js +3 -2
- package/lib/reply.js +44 -163
- package/lib/request.js +13 -10
- package/lib/route.js +158 -139
- package/lib/schema-controller.js +3 -3
- package/lib/schemas.js +27 -1
- package/lib/server.js +219 -116
- package/lib/symbols.js +6 -4
- package/lib/validation.js +2 -1
- package/lib/warnings.js +2 -12
- package/lib/wrapThenable.js +4 -11
- package/package.json +40 -45
- package/test/404s.test.js +265 -108
- package/test/500s.test.js +2 -2
- package/test/async-await.test.js +15 -71
- package/test/close.test.js +39 -1
- package/test/content-parser.test.js +32 -0
- package/test/context-config.test.js +56 -4
- package/test/custom-http-server.test.js +14 -7
- package/test/custom-parser-async.test.js +0 -65
- package/test/custom-parser.test.js +54 -121
- package/test/decorator.test.js +1 -3
- package/test/delete.test.js +5 -5
- 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/get.test.js +3 -3
- package/test/helper.js +18 -3
- package/test/hooks-async.test.js +14 -47
- package/test/hooks.on-ready.test.js +9 -4
- package/test/hooks.test.js +58 -99
- package/test/http2/closing.test.js +5 -11
- package/test/http2/unknown-http-method.test.js +3 -9
- package/test/https/custom-https-server.test.js +12 -6
- package/test/inject.test.js +1 -1
- package/test/input-validation.js +2 -2
- package/test/internals/all.test.js +2 -2
- package/test/internals/contentTypeParser.test.js +4 -4
- package/test/internals/handleRequest.test.js +9 -46
- package/test/internals/initialConfig.test.js +33 -12
- package/test/internals/logger.test.js +1 -1
- package/test/internals/reply.test.js +245 -3
- package/test/internals/request.test.js +13 -7
- package/test/internals/server.test.js +88 -0
- package/test/listen.test.js +84 -1
- package/test/logger.test.js +98 -58
- package/test/maxRequestsPerSocket.test.js +8 -6
- package/test/middleware.test.js +2 -25
- package/test/noop-set.test.js +19 -0
- package/test/nullable-validation.test.js +51 -14
- package/test/plugin.test.js +31 -5
- package/test/pretty-print.test.js +22 -10
- package/test/reply-error.test.js +123 -12
- package/test/request-error.test.js +2 -5
- package/test/route-hooks.test.js +17 -17
- package/test/route-prefix.test.js +2 -1
- package/test/route.test.js +216 -20
- package/test/router-options.test.js +1 -1
- package/test/schema-examples.test.js +11 -5
- package/test/schema-feature.test.js +24 -19
- package/test/schema-serialization.test.js +50 -9
- package/test/schema-special-usage.test.js +14 -81
- package/test/schema-validation.test.js +9 -9
- package/test/skip-reply-send.test.js +8 -8
- package/test/stream.test.js +23 -12
- package/test/throw.test.js +8 -5
- package/test/trust-proxy.test.js +1 -1
- package/test/type-provider.test.js +20 -0
- package/test/types/fastify.test-d.ts +12 -18
- package/test/types/hooks.test-d.ts +7 -3
- package/test/types/import.js +2 -0
- package/test/types/import.ts +1 -0
- package/test/types/instance.test-d.ts +61 -15
- package/test/types/logger.test-d.ts +44 -15
- 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/validation-error-handling.test.js +9 -9
- package/test/versioned-routes.test.js +29 -17
- package/test/wrapThenable.test.js +7 -6
- package/types/.eslintrc.json +1 -1
- package/types/content-type-parser.d.ts +17 -8
- package/types/hooks.d.ts +107 -60
- package/types/instance.d.ts +137 -105
- package/types/logger.d.ts +18 -104
- package/types/plugin.d.ts +10 -4
- package/types/register.d.ts +1 -1
- package/types/reply.d.ts +16 -11
- package/types/request.d.ts +10 -5
- package/types/route.d.ts +42 -31
- package/types/schema.d.ts +15 -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/noop-set.js
ADDED
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
|
}
|
|
@@ -365,22 +356,29 @@ function preserializeHookEnd (err, request, reply, payload) {
|
|
|
365
356
|
return
|
|
366
357
|
}
|
|
367
358
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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[
|
|
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[
|
|
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
|
|
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 =
|
|
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
|
|