1 | 'use strict'
|
2 |
|
3 | const VERSION = '4.28.0'
|
4 |
|
5 | const Avvio = require('avvio')
|
6 | const http = require('node:http')
|
7 | let lightMyRequest
|
8 |
|
9 | const {
|
10 | kAvvioBoot,
|
11 | kChildren,
|
12 | kServerBindings,
|
13 | kBodyLimit,
|
14 | kRoutePrefix,
|
15 | kLogLevel,
|
16 | kLogSerializers,
|
17 | kHooks,
|
18 | kSchemaController,
|
19 | kRequestAcceptVersion,
|
20 | kReplySerializerDefault,
|
21 | kContentTypeParser,
|
22 | kReply,
|
23 | kRequest,
|
24 | kFourOhFour,
|
25 | kState,
|
26 | kOptions,
|
27 | kPluginNameChain,
|
28 | kSchemaErrorFormatter,
|
29 | kErrorHandler,
|
30 | kKeepAliveConnections,
|
31 | kChildLoggerFactory,
|
32 | kGenReqId
|
33 | } = require('./lib/symbols.js')
|
34 |
|
35 | const { createServer, compileValidateHTTPVersion } = require('./lib/server')
|
36 | const Reply = require('./lib/reply')
|
37 | const Request = require('./lib/request')
|
38 | const Context = require('./lib/context.js')
|
39 | const { supportedMethods } = require('./lib/httpMethods')
|
40 | const decorator = require('./lib/decorate')
|
41 | const ContentTypeParser = require('./lib/contentTypeParser')
|
42 | const SchemaController = require('./lib/schema-controller')
|
43 | const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks')
|
44 | const { createLogger, createChildLogger, defaultChildLoggerFactory } = require('./lib/logger')
|
45 | const pluginUtils = require('./lib/pluginUtils')
|
46 | const { getGenReqId, reqIdGenFactory } = require('./lib/reqIdGenFactory')
|
47 | const { buildRouting, validateBodyLimitOption } = require('./lib/route')
|
48 | const build404 = require('./lib/fourOhFour')
|
49 | const getSecuredInitialConfig = require('./lib/initialConfigValidation')
|
50 | const override = require('./lib/pluginOverride')
|
51 | const { FSTDEP009 } = require('./lib/warnings')
|
52 | const noopSet = require('./lib/noop-set')
|
53 | const {
|
54 | appendStackTrace,
|
55 | AVVIO_ERRORS_MAP,
|
56 | ...errorCodes
|
57 | } = require('./lib/errors')
|
58 |
|
59 | const { defaultInitOptions } = getSecuredInitialConfig
|
60 |
|
61 | const {
|
62 | FST_ERR_ASYNC_CONSTRAINT,
|
63 | FST_ERR_BAD_URL,
|
64 | FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE,
|
65 | FST_ERR_OPTIONS_NOT_OBJ,
|
66 | FST_ERR_QSP_NOT_FN,
|
67 | FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN,
|
68 | FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ,
|
69 | FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR,
|
70 | FST_ERR_VERSION_CONSTRAINT_NOT_STR,
|
71 | FST_ERR_INSTANCE_ALREADY_LISTENING,
|
72 | FST_ERR_REOPENED_CLOSE_SERVER,
|
73 | FST_ERR_ROUTE_REWRITE_NOT_STR,
|
74 | FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN,
|
75 | FST_ERR_ERROR_HANDLER_NOT_FN
|
76 | } = errorCodes
|
77 |
|
78 | const { buildErrorHandler } = require('./lib/error-handler.js')
|
79 |
|
80 | function defaultBuildPrettyMeta (route) {
|
81 |
|
82 |
|
83 | const cleanKeys = {}
|
84 | const allowedProps = ['errorHandler', 'logLevel', 'logSerializers']
|
85 |
|
86 | allowedProps.concat(supportedHooks).forEach(k => {
|
87 | cleanKeys[k] = route.store[k]
|
88 | })
|
89 |
|
90 | return Object.assign({}, cleanKeys)
|
91 | }
|
92 |
|
93 |
|
94 |
|
95 |
|
96 | function fastify (options) {
|
97 |
|
98 | options = options || {}
|
99 |
|
100 | if (typeof options !== 'object') {
|
101 | throw new FST_ERR_OPTIONS_NOT_OBJ()
|
102 | }
|
103 |
|
104 | if (options.querystringParser && typeof options.querystringParser !== 'function') {
|
105 | throw new FST_ERR_QSP_NOT_FN(typeof options.querystringParser)
|
106 | }
|
107 |
|
108 | if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') {
|
109 | throw new FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN(typeof options.schemaController.bucket)
|
110 | }
|
111 |
|
112 | validateBodyLimitOption(options.bodyLimit)
|
113 |
|
114 | const requestIdHeader = (options.requestIdHeader === false) ? false : (options.requestIdHeader || defaultInitOptions.requestIdHeader).toLowerCase()
|
115 | const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId)
|
116 | const requestIdLogLabel = options.requestIdLogLabel || 'reqId'
|
117 | const bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit
|
118 | const disableRequestLogging = options.disableRequestLogging || false
|
119 |
|
120 | const ajvOptions = Object.assign({
|
121 | customOptions: {},
|
122 | plugins: []
|
123 | }, options.ajv)
|
124 | const frameworkErrors = options.frameworkErrors
|
125 |
|
126 |
|
127 | if (!ajvOptions.customOptions || Object.prototype.toString.call(ajvOptions.customOptions) !== '[object Object]') {
|
128 | throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ(typeof ajvOptions.customOptions)
|
129 | }
|
130 | if (!ajvOptions.plugins || !Array.isArray(ajvOptions.plugins)) {
|
131 | throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR(typeof ajvOptions.plugins)
|
132 | }
|
133 |
|
134 |
|
135 | const { logger, hasLogger } = createLogger(options)
|
136 |
|
137 |
|
138 | options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout
|
139 | options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout
|
140 | options.maxRequestsPerSocket = options.maxRequestsPerSocket || defaultInitOptions.maxRequestsPerSocket
|
141 | options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout
|
142 | options.logger = logger
|
143 | options.requestIdHeader = requestIdHeader
|
144 | options.requestIdLogLabel = requestIdLogLabel
|
145 | options.disableRequestLogging = disableRequestLogging
|
146 | options.ajv = ajvOptions
|
147 | options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler
|
148 |
|
149 | const initialConfig = getSecuredInitialConfig(options)
|
150 |
|
151 |
|
152 | options.exposeHeadRoutes = initialConfig.exposeHeadRoutes
|
153 |
|
154 | let constraints = options.constraints
|
155 | if (options.versioning) {
|
156 | FSTDEP009()
|
157 | constraints = {
|
158 | ...constraints,
|
159 | version: {
|
160 | name: 'version',
|
161 | mustMatchWhenDerived: true,
|
162 | storage: options.versioning.storage,
|
163 | deriveConstraint: options.versioning.deriveVersion,
|
164 | validate (value) {
|
165 | if (typeof value !== 'string') {
|
166 | throw new FST_ERR_VERSION_CONSTRAINT_NOT_STR()
|
167 | }
|
168 | }
|
169 | }
|
170 | }
|
171 | }
|
172 |
|
173 |
|
174 | const router = buildRouting({
|
175 | config: {
|
176 | defaultRoute,
|
177 | onBadUrl,
|
178 | constraints,
|
179 | ignoreTrailingSlash: options.ignoreTrailingSlash || defaultInitOptions.ignoreTrailingSlash,
|
180 | ignoreDuplicateSlashes: options.ignoreDuplicateSlashes || defaultInitOptions.ignoreDuplicateSlashes,
|
181 | maxParamLength: options.maxParamLength || defaultInitOptions.maxParamLength,
|
182 | caseSensitive: options.caseSensitive,
|
183 | allowUnsafeRegex: options.allowUnsafeRegex || defaultInitOptions.allowUnsafeRegex,
|
184 | buildPrettyMeta: defaultBuildPrettyMeta,
|
185 | querystringParser: options.querystringParser,
|
186 | useSemicolonDelimiter: options.useSemicolonDelimiter ?? defaultInitOptions.useSemicolonDelimiter
|
187 | }
|
188 | })
|
189 |
|
190 |
|
191 | const fourOhFour = build404(options)
|
192 |
|
193 |
|
194 | const httpHandler = wrapRouting(router, options)
|
195 |
|
196 |
|
197 | options.http2SessionTimeout = initialConfig.http2SessionTimeout
|
198 | const { server, listen } = createServer(options, httpHandler)
|
199 |
|
200 | const serverHasCloseAllConnections = typeof server.closeAllConnections === 'function'
|
201 | const serverHasCloseIdleConnections = typeof server.closeIdleConnections === 'function'
|
202 |
|
203 | let forceCloseConnections = options.forceCloseConnections
|
204 | if (forceCloseConnections === 'idle' && !serverHasCloseIdleConnections) {
|
205 | throw new FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE()
|
206 | } else if (typeof forceCloseConnections !== 'boolean') {
|
207 |
|
208 | forceCloseConnections = serverHasCloseIdleConnections ? 'idle' : false
|
209 | }
|
210 |
|
211 | const keepAliveConnections = !serverHasCloseAllConnections && forceCloseConnections === true ? new Set() : noopSet()
|
212 |
|
213 | const setupResponseListeners = Reply.setupResponseListeners
|
214 | const schemaController = SchemaController.buildSchemaController(null, options.schemaController)
|
215 |
|
216 |
|
217 | const fastify = {
|
218 |
|
219 | [kState]: {
|
220 | listening: false,
|
221 | closing: false,
|
222 | started: false,
|
223 | ready: false,
|
224 | booting: false,
|
225 | readyPromise: null
|
226 | },
|
227 | [kKeepAliveConnections]: keepAliveConnections,
|
228 | [kOptions]: options,
|
229 | [kChildren]: [],
|
230 | [kServerBindings]: [],
|
231 | [kBodyLimit]: bodyLimit,
|
232 | [kRoutePrefix]: '',
|
233 | [kLogLevel]: '',
|
234 | [kLogSerializers]: null,
|
235 | [kHooks]: new Hooks(),
|
236 | [kSchemaController]: schemaController,
|
237 | [kSchemaErrorFormatter]: null,
|
238 | [kErrorHandler]: buildErrorHandler(),
|
239 | [kChildLoggerFactory]: defaultChildLoggerFactory,
|
240 | [kReplySerializerDefault]: null,
|
241 | [kContentTypeParser]: new ContentTypeParser(
|
242 | bodyLimit,
|
243 | (options.onProtoPoisoning || defaultInitOptions.onProtoPoisoning),
|
244 | (options.onConstructorPoisoning || defaultInitOptions.onConstructorPoisoning)
|
245 | ),
|
246 | [kReply]: Reply.buildReply(Reply),
|
247 | [kRequest]: Request.buildRequest(Request, options.trustProxy),
|
248 | [kFourOhFour]: fourOhFour,
|
249 | [pluginUtils.kRegisteredPlugins]: [],
|
250 | [kPluginNameChain]: ['fastify'],
|
251 | [kAvvioBoot]: null,
|
252 | [kGenReqId]: genReqId,
|
253 |
|
254 | routing: httpHandler,
|
255 | getDefaultRoute: router.getDefaultRoute.bind(router),
|
256 | setDefaultRoute: router.setDefaultRoute.bind(router),
|
257 |
|
258 | delete: function _delete (url, options, handler) {
|
259 | return router.prepareRoute.call(this, { method: 'DELETE', url, options, handler })
|
260 | },
|
261 | get: function _get (url, options, handler) {
|
262 | return router.prepareRoute.call(this, { method: 'GET', url, options, handler })
|
263 | },
|
264 | head: function _head (url, options, handler) {
|
265 | return router.prepareRoute.call(this, { method: 'HEAD', url, options, handler })
|
266 | },
|
267 | patch: function _patch (url, options, handler) {
|
268 | return router.prepareRoute.call(this, { method: 'PATCH', url, options, handler })
|
269 | },
|
270 | post: function _post (url, options, handler) {
|
271 | return router.prepareRoute.call(this, { method: 'POST', url, options, handler })
|
272 | },
|
273 | put: function _put (url, options, handler) {
|
274 | return router.prepareRoute.call(this, { method: 'PUT', url, options, handler })
|
275 | },
|
276 | options: function _options (url, options, handler) {
|
277 | return router.prepareRoute.call(this, { method: 'OPTIONS', url, options, handler })
|
278 | },
|
279 | all: function _all (url, options, handler) {
|
280 | return router.prepareRoute.call(this, { method: supportedMethods, url, options, handler })
|
281 | },
|
282 |
|
283 | route: function _route (options) {
|
284 |
|
285 |
|
286 | return router.route.call(this, { options })
|
287 | },
|
288 | hasRoute: function _route (options) {
|
289 | return router.hasRoute.call(this, { options })
|
290 | },
|
291 | findRoute: function _findRoute (options) {
|
292 | return router.findRoute(options)
|
293 | },
|
294 |
|
295 | log: logger,
|
296 |
|
297 | withTypeProvider,
|
298 |
|
299 | addHook,
|
300 |
|
301 | addSchema,
|
302 | getSchema: schemaController.getSchema.bind(schemaController),
|
303 | getSchemas: schemaController.getSchemas.bind(schemaController),
|
304 | setValidatorCompiler,
|
305 | setSerializerCompiler,
|
306 | setSchemaController,
|
307 | setReplySerializer,
|
308 | setSchemaErrorFormatter,
|
309 |
|
310 | setGenReqId,
|
311 |
|
312 | addContentTypeParser: ContentTypeParser.helpers.addContentTypeParser,
|
313 | hasContentTypeParser: ContentTypeParser.helpers.hasContentTypeParser,
|
314 | getDefaultJsonParser: ContentTypeParser.defaultParsers.getDefaultJsonParser,
|
315 | defaultTextParser: ContentTypeParser.defaultParsers.defaultTextParser,
|
316 | removeContentTypeParser: ContentTypeParser.helpers.removeContentTypeParser,
|
317 | removeAllContentTypeParsers: ContentTypeParser.helpers.removeAllContentTypeParsers,
|
318 |
|
319 | register: null,
|
320 | after: null,
|
321 | ready: null,
|
322 | onClose: null,
|
323 | close: null,
|
324 | printPlugins: null,
|
325 | hasPlugin: function (name) {
|
326 | return this[pluginUtils.kRegisteredPlugins].includes(name) || this[kPluginNameChain].includes(name)
|
327 | },
|
328 |
|
329 | listen,
|
330 | server,
|
331 | addresses: function () {
|
332 |
|
333 | const binded = this[kServerBindings].map(b => b.address())
|
334 | binded.push(this.server.address())
|
335 | return binded.filter(adr => adr)
|
336 | },
|
337 |
|
338 | decorate: decorator.add,
|
339 | hasDecorator: decorator.exist,
|
340 | decorateReply: decorator.decorateReply,
|
341 | decorateRequest: decorator.decorateRequest,
|
342 | hasRequestDecorator: decorator.existRequest,
|
343 | hasReplyDecorator: decorator.existReply,
|
344 |
|
345 | inject,
|
346 |
|
347 | printRoutes,
|
348 |
|
349 | setNotFoundHandler,
|
350 | setErrorHandler,
|
351 |
|
352 | setChildLoggerFactory,
|
353 |
|
354 | initialConfig,
|
355 |
|
356 | addConstraintStrategy: router.addConstraintStrategy.bind(router),
|
357 | hasConstraintStrategy: router.hasConstraintStrategy.bind(router)
|
358 | }
|
359 |
|
360 | Object.defineProperties(fastify, {
|
361 | listeningOrigin: {
|
362 | get () {
|
363 | const address = this.addresses().slice(-1).pop()
|
364 |
|
365 |
|
366 | if (typeof address === 'string') {
|
367 | return address
|
368 | }
|
369 | const host = address.family === 'IPv6' ? `[${address.address}]` : address.address
|
370 | return `${this[kOptions].https ? 'https' : 'http'}://${host}:${address.port}`
|
371 | }
|
372 | },
|
373 | pluginName: {
|
374 | configurable: true,
|
375 | get () {
|
376 | if (this[kPluginNameChain].length > 1) {
|
377 | return this[kPluginNameChain].join(' -> ')
|
378 | }
|
379 | return this[kPluginNameChain][0]
|
380 | }
|
381 | },
|
382 | prefix: {
|
383 | configurable: true,
|
384 | get () { return this[kRoutePrefix] }
|
385 | },
|
386 | validatorCompiler: {
|
387 | configurable: true,
|
388 | get () { return this[kSchemaController].getValidatorCompiler() }
|
389 | },
|
390 | serializerCompiler: {
|
391 | configurable: true,
|
392 | get () { return this[kSchemaController].getSerializerCompiler() }
|
393 | },
|
394 | childLoggerFactory: {
|
395 | configurable: true,
|
396 | get () { return this[kChildLoggerFactory] }
|
397 | },
|
398 | version: {
|
399 | configurable: true,
|
400 | get () { return VERSION }
|
401 | },
|
402 | errorHandler: {
|
403 | configurable: true,
|
404 | get () {
|
405 | return this[kErrorHandler].func
|
406 | }
|
407 | },
|
408 | genReqId: {
|
409 | configurable: true,
|
410 | get () { return this[kGenReqId] }
|
411 | }
|
412 | })
|
413 |
|
414 | if (options.schemaErrorFormatter) {
|
415 | validateSchemaErrorFormatter(options.schemaErrorFormatter)
|
416 | fastify[kSchemaErrorFormatter] = options.schemaErrorFormatter.bind(fastify)
|
417 | }
|
418 |
|
419 |
|
420 |
|
421 |
|
422 |
|
423 |
|
424 |
|
425 |
|
426 |
|
427 | const avvioPluginTimeout = Number(options.pluginTimeout)
|
428 | const avvio = Avvio(fastify, {
|
429 | autostart: false,
|
430 | timeout: isNaN(avvioPluginTimeout) === false ? avvioPluginTimeout : defaultInitOptions.pluginTimeout,
|
431 | expose: {
|
432 | use: 'register'
|
433 | }
|
434 | })
|
435 |
|
436 | avvio.override = override
|
437 | avvio.on('start', () => (fastify[kState].started = true))
|
438 | fastify[kAvvioBoot] = fastify.ready
|
439 | fastify.ready = ready
|
440 | fastify.printPlugins = avvio.prettyPrint.bind(avvio)
|
441 |
|
442 |
|
443 | avvio.once('preReady', () => {
|
444 | fastify.onClose((instance, done) => {
|
445 | fastify[kState].closing = true
|
446 | router.closeRoutes()
|
447 |
|
448 | hookRunnerApplication('preClose', fastify[kAvvioBoot], fastify, function () {
|
449 | if (fastify[kState].listening) {
|
450 |
|
451 | if (forceCloseConnections === 'idle') {
|
452 |
|
453 | instance.server.closeIdleConnections()
|
454 |
|
455 | } else if (serverHasCloseAllConnections && forceCloseConnections) {
|
456 | instance.server.closeAllConnections()
|
457 | } else if (forceCloseConnections === true) {
|
458 | for (const conn of fastify[kKeepAliveConnections]) {
|
459 |
|
460 |
|
461 |
|
462 |
|
463 | conn.destroy()
|
464 | fastify[kKeepAliveConnections].delete(conn)
|
465 | }
|
466 | }
|
467 | }
|
468 |
|
469 |
|
470 |
|
471 |
|
472 |
|
473 | if (!options.serverFactory || fastify[kState].listening) {
|
474 | instance.server.close(function (err) {
|
475 |
|
476 | if (err && err.code !== 'ERR_SERVER_NOT_RUNNING') {
|
477 | done(null)
|
478 | } else {
|
479 | done()
|
480 | }
|
481 | })
|
482 | } else {
|
483 | process.nextTick(done, null)
|
484 | }
|
485 | })
|
486 | })
|
487 | })
|
488 |
|
489 |
|
490 | const onBadUrlContext = new Context({
|
491 | server: fastify,
|
492 | config: {}
|
493 | })
|
494 |
|
495 |
|
496 | fastify.setNotFoundHandler()
|
497 | fourOhFour.arrange404(fastify)
|
498 |
|
499 | router.setup(options, {
|
500 | avvio,
|
501 | fourOhFour,
|
502 | logger,
|
503 | hasLogger,
|
504 | setupResponseListeners,
|
505 | throwIfAlreadyStarted,
|
506 | validateHTTPVersion: compileValidateHTTPVersion(options),
|
507 | keepAliveConnections
|
508 | })
|
509 |
|
510 |
|
511 | server.on('clientError', options.clientErrorHandler.bind(fastify))
|
512 |
|
513 | try {
|
514 | const dc = require('node:diagnostics_channel')
|
515 | const initChannel = dc.channel('fastify.initialization')
|
516 | if (initChannel.hasSubscribers) {
|
517 | initChannel.publish({ fastify })
|
518 | }
|
519 | } catch (e) {
|
520 |
|
521 |
|
522 | }
|
523 |
|
524 |
|
525 | if ('asyncDispose' in Symbol) {
|
526 | fastify[Symbol.asyncDispose] = function dispose () {
|
527 | return fastify.close()
|
528 | }
|
529 | }
|
530 |
|
531 | return fastify
|
532 |
|
533 | function throwIfAlreadyStarted (msg) {
|
534 | if (fastify[kState].started) throw new FST_ERR_INSTANCE_ALREADY_LISTENING(msg)
|
535 | }
|
536 |
|
537 |
|
538 |
|
539 |
|
540 | function inject (opts, cb) {
|
541 |
|
542 |
|
543 | if (lightMyRequest === undefined) {
|
544 | lightMyRequest = require('light-my-request')
|
545 | }
|
546 |
|
547 | if (fastify[kState].started) {
|
548 | if (fastify[kState].closing) {
|
549 |
|
550 | const error = new FST_ERR_REOPENED_CLOSE_SERVER()
|
551 | if (cb) {
|
552 | cb(error)
|
553 | return
|
554 | } else {
|
555 | return Promise.reject(error)
|
556 | }
|
557 | }
|
558 | return lightMyRequest(httpHandler, opts, cb)
|
559 | }
|
560 |
|
561 | if (cb) {
|
562 | this.ready(err => {
|
563 | if (err) cb(err, null)
|
564 | else lightMyRequest(httpHandler, opts, cb)
|
565 | })
|
566 | } else {
|
567 | return lightMyRequest((req, res) => {
|
568 | this.ready(function (err) {
|
569 | if (err) {
|
570 | res.emit('error', err)
|
571 | return
|
572 | }
|
573 | httpHandler(req, res)
|
574 | })
|
575 | }, opts)
|
576 | }
|
577 | }
|
578 |
|
579 | function ready (cb) {
|
580 | if (this[kState].readyPromise !== null) {
|
581 | if (cb != null) {
|
582 | this[kState].readyPromise.then(() => cb(null, fastify), cb)
|
583 | return
|
584 | }
|
585 |
|
586 | return this[kState].readyPromise
|
587 | }
|
588 |
|
589 | let resolveReady
|
590 | let rejectReady
|
591 |
|
592 |
|
593 | process.nextTick(runHooks)
|
594 |
|
595 |
|
596 |
|
597 |
|
598 |
|
599 | this[kState].readyPromise = new Promise(function (resolve, reject) {
|
600 | resolveReady = resolve
|
601 | rejectReady = reject
|
602 | })
|
603 |
|
604 | if (!cb) {
|
605 | return this[kState].readyPromise
|
606 | } else {
|
607 | this[kState].readyPromise.then(() => cb(null, fastify), cb)
|
608 | }
|
609 |
|
610 | function runHooks () {
|
611 |
|
612 | fastify[kAvvioBoot]((err, done) => {
|
613 | if (err || fastify[kState].started || fastify[kState].ready || fastify[kState].booting) {
|
614 | manageErr(err)
|
615 | } else {
|
616 | fastify[kState].booting = true
|
617 | hookRunnerApplication('onReady', fastify[kAvvioBoot], fastify, manageErr)
|
618 | }
|
619 | done()
|
620 | })
|
621 | }
|
622 |
|
623 | function manageErr (err) {
|
624 |
|
625 |
|
626 |
|
627 | err = err != null && AVVIO_ERRORS_MAP[err.code] != null
|
628 | ? appendStackTrace(err, new AVVIO_ERRORS_MAP[err.code](err.message))
|
629 | : err
|
630 |
|
631 | if (err) {
|
632 | return rejectReady(err)
|
633 | }
|
634 |
|
635 | resolveReady(fastify)
|
636 | fastify[kState].booting = false
|
637 | fastify[kState].ready = true
|
638 | fastify[kState].promise = null
|
639 | }
|
640 | }
|
641 |
|
642 |
|
643 | function withTypeProvider () {
|
644 | return this
|
645 | }
|
646 |
|
647 |
|
648 | function addHook (name, fn) {
|
649 | throwIfAlreadyStarted('Cannot call "addHook"!')
|
650 |
|
651 | if (fn == null) {
|
652 | throw new errorCodes.FST_ERR_HOOK_INVALID_HANDLER(name, fn)
|
653 | }
|
654 |
|
655 | if (name === 'onSend' || name === 'preSerialization' || name === 'onError' || name === 'preParsing') {
|
656 | if (fn.constructor.name === 'AsyncFunction' && fn.length === 4) {
|
657 | throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
658 | }
|
659 | } else if (name === 'onReady' || name === 'onListen') {
|
660 | if (fn.constructor.name === 'AsyncFunction' && fn.length !== 0) {
|
661 | throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
662 | }
|
663 | } else if (name === 'onRequestAbort') {
|
664 | if (fn.constructor.name === 'AsyncFunction' && fn.length !== 1) {
|
665 | throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
666 | }
|
667 | } else {
|
668 | if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) {
|
669 | throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
670 | }
|
671 | }
|
672 |
|
673 | if (name === 'onClose') {
|
674 | this.onClose(fn)
|
675 | } else if (name === 'onReady' || name === 'onListen' || name === 'onRoute') {
|
676 | this[kHooks].add(name, fn)
|
677 | } else {
|
678 | this.after((err, done) => {
|
679 | _addHook.call(this, name, fn)
|
680 | done(err)
|
681 | })
|
682 | }
|
683 | return this
|
684 |
|
685 | function _addHook (name, fn) {
|
686 | this[kHooks].add(name, fn)
|
687 | this[kChildren].forEach(child => _addHook.call(child, name, fn))
|
688 | }
|
689 | }
|
690 |
|
691 |
|
692 | function addSchema (schema) {
|
693 | throwIfAlreadyStarted('Cannot call "addSchema"!')
|
694 | this[kSchemaController].add(schema)
|
695 | this[kChildren].forEach(child => child.addSchema(schema))
|
696 | return this
|
697 | }
|
698 |
|
699 | function defaultClientErrorHandler (err, socket) {
|
700 |
|
701 |
|
702 | if (err.code === 'ECONNRESET' || socket.destroyed) {
|
703 | return
|
704 | }
|
705 |
|
706 | let body, errorCode, errorStatus, errorLabel
|
707 |
|
708 | if (err.code === 'ERR_HTTP_REQUEST_TIMEOUT') {
|
709 | errorCode = '408'
|
710 | errorStatus = http.STATUS_CODES[errorCode]
|
711 | body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}`
|
712 | errorLabel = 'timeout'
|
713 | } else if (err.code === 'HPE_HEADER_OVERFLOW') {
|
714 | errorCode = '431'
|
715 | errorStatus = http.STATUS_CODES[errorCode]
|
716 | body = `{"error":"${errorStatus}","message":"Exceeded maximum allowed HTTP header size","statusCode":431}`
|
717 | errorLabel = 'header_overflow'
|
718 | } else {
|
719 | errorCode = '400'
|
720 | errorStatus = http.STATUS_CODES[errorCode]
|
721 | body = `{"error":"${errorStatus}","message":"Client Error","statusCode":400}`
|
722 | errorLabel = 'error'
|
723 | }
|
724 |
|
725 |
|
726 |
|
727 |
|
728 | this.log.trace({ err }, `client ${errorLabel}`)
|
729 |
|
730 |
|
731 |
|
732 |
|
733 | if (socket.writable) {
|
734 | socket.write(`HTTP/1.1 ${errorCode} ${errorStatus}\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
|
735 | }
|
736 | socket.destroy(err)
|
737 | }
|
738 |
|
739 |
|
740 |
|
741 | function defaultRoute (req, res) {
|
742 | if (req.headers['accept-version'] !== undefined) {
|
743 |
|
744 |
|
745 |
|
746 | req.headers[kRequestAcceptVersion] = req.headers['accept-version']
|
747 | req.headers['accept-version'] = undefined
|
748 | }
|
749 | fourOhFour.router.lookup(req, res)
|
750 | }
|
751 |
|
752 | function onBadUrl (path, req, res) {
|
753 | if (frameworkErrors) {
|
754 | const id = getGenReqId(onBadUrlContext.server, req)
|
755 | const childLogger = createChildLogger(onBadUrlContext, logger, req, id)
|
756 |
|
757 | const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
|
758 | const reply = new Reply(res, request, childLogger)
|
759 |
|
760 | if (disableRequestLogging === false) {
|
761 | childLogger.info({ req: request }, 'incoming request')
|
762 | }
|
763 |
|
764 | return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
|
765 | }
|
766 | const body = `{"error":"Bad Request","code":"FST_ERR_BAD_URL","message":"'${path}' is not a valid url component","statusCode":400}`
|
767 | res.writeHead(400, {
|
768 | 'Content-Type': 'application/json',
|
769 | 'Content-Length': body.length
|
770 | })
|
771 | res.end(body)
|
772 | }
|
773 |
|
774 | function buildAsyncConstraintCallback (isAsync, req, res) {
|
775 | if (isAsync === false) return undefined
|
776 | return function onAsyncConstraintError (err) {
|
777 | if (err) {
|
778 | if (frameworkErrors) {
|
779 | const id = getGenReqId(onBadUrlContext.server, req)
|
780 | const childLogger = createChildLogger(onBadUrlContext, logger, req, id)
|
781 |
|
782 | const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
|
783 | const reply = new Reply(res, request, childLogger)
|
784 |
|
785 | if (disableRequestLogging === false) {
|
786 | childLogger.info({ req: request }, 'incoming request')
|
787 | }
|
788 |
|
789 | return frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)
|
790 | }
|
791 | const body = '{"error":"Internal Server Error","message":"Unexpected error from async constraint","statusCode":500}'
|
792 | res.writeHead(500, {
|
793 | 'Content-Type': 'application/json',
|
794 | 'Content-Length': body.length
|
795 | })
|
796 | res.end(body)
|
797 | }
|
798 | }
|
799 | }
|
800 |
|
801 | function setNotFoundHandler (opts, handler) {
|
802 | throwIfAlreadyStarted('Cannot call "setNotFoundHandler"!')
|
803 |
|
804 | fourOhFour.setNotFoundHandler.call(this, opts, handler, avvio, router.routeHandler)
|
805 | return this
|
806 | }
|
807 |
|
808 | function setValidatorCompiler (validatorCompiler) {
|
809 | throwIfAlreadyStarted('Cannot call "setValidatorCompiler"!')
|
810 | this[kSchemaController].setValidatorCompiler(validatorCompiler)
|
811 | return this
|
812 | }
|
813 |
|
814 | function setSchemaErrorFormatter (errorFormatter) {
|
815 | throwIfAlreadyStarted('Cannot call "setSchemaErrorFormatter"!')
|
816 | validateSchemaErrorFormatter(errorFormatter)
|
817 | this[kSchemaErrorFormatter] = errorFormatter.bind(this)
|
818 | return this
|
819 | }
|
820 |
|
821 | function setSerializerCompiler (serializerCompiler) {
|
822 | throwIfAlreadyStarted('Cannot call "setSerializerCompiler"!')
|
823 | this[kSchemaController].setSerializerCompiler(serializerCompiler)
|
824 | return this
|
825 | }
|
826 |
|
827 | function setSchemaController (schemaControllerOpts) {
|
828 | throwIfAlreadyStarted('Cannot call "setSchemaController"!')
|
829 | const old = this[kSchemaController]
|
830 | const schemaController = SchemaController.buildSchemaController(old, Object.assign({}, old.opts, schemaControllerOpts))
|
831 | this[kSchemaController] = schemaController
|
832 | this.getSchema = schemaController.getSchema.bind(schemaController)
|
833 | this.getSchemas = schemaController.getSchemas.bind(schemaController)
|
834 | return this
|
835 | }
|
836 |
|
837 | function setReplySerializer (replySerializer) {
|
838 | throwIfAlreadyStarted('Cannot call "setReplySerializer"!')
|
839 |
|
840 | this[kReplySerializerDefault] = replySerializer
|
841 | return this
|
842 | }
|
843 |
|
844 |
|
845 | function setErrorHandler (func) {
|
846 | throwIfAlreadyStarted('Cannot call "setErrorHandler"!')
|
847 |
|
848 | if (typeof func !== 'function') {
|
849 | throw new FST_ERR_ERROR_HANDLER_NOT_FN()
|
850 | }
|
851 |
|
852 | this[kErrorHandler] = buildErrorHandler(this[kErrorHandler], func.bind(this))
|
853 | return this
|
854 | }
|
855 |
|
856 | function setChildLoggerFactory (factory) {
|
857 | throwIfAlreadyStarted('Cannot call "setChildLoggerFactory"!')
|
858 |
|
859 | this[kChildLoggerFactory] = factory
|
860 | return this
|
861 | }
|
862 |
|
863 | function printRoutes (opts = {}) {
|
864 |
|
865 | opts.includeMeta = opts.includeHooks ? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks : opts.includeMeta
|
866 | return router.printRoutes(opts)
|
867 | }
|
868 |
|
869 | function wrapRouting (router, { rewriteUrl, logger }) {
|
870 | let isAsync
|
871 | return function preRouting (req, res) {
|
872 |
|
873 | if (isAsync === undefined) isAsync = router.isAsyncConstraint()
|
874 | if (rewriteUrl) {
|
875 | req.originalUrl = req.url
|
876 | const url = rewriteUrl.call(fastify, req)
|
877 | if (typeof url === 'string') {
|
878 | req.url = url
|
879 | } else {
|
880 | const err = new FST_ERR_ROUTE_REWRITE_NOT_STR(req.url, typeof url)
|
881 | req.destroy(err)
|
882 | }
|
883 | }
|
884 | router.routing(req, res, buildAsyncConstraintCallback(isAsync, req, res))
|
885 | }
|
886 | }
|
887 |
|
888 | function setGenReqId (func) {
|
889 | throwIfAlreadyStarted('Cannot call "setGenReqId"!')
|
890 |
|
891 | this[kGenReqId] = reqIdGenFactory(this[kOptions].requestIdHeader, func)
|
892 | return this
|
893 | }
|
894 | }
|
895 |
|
896 | function validateSchemaErrorFormatter (schemaErrorFormatter) {
|
897 | if (typeof schemaErrorFormatter !== 'function') {
|
898 | throw new FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN(typeof schemaErrorFormatter)
|
899 | } else if (schemaErrorFormatter.constructor.name === 'AsyncFunction') {
|
900 | throw new FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN('AsyncFunction')
|
901 | }
|
902 | }
|
903 |
|
904 |
|
905 |
|
906 |
|
907 |
|
908 |
|
909 |
|
910 |
|
911 |
|
912 |
|
913 |
|
914 |
|
915 | module.exports = fastify
|
916 | module.exports.errorCodes = errorCodes
|
917 | module.exports.fastify = fastify
|
918 | module.exports.default = fastify
|