strong-tie/inbound-calls
0
1'use strict'2 3const VERSION = '5.2.1'4 5const Avvio = require('avvio')6const http = require('node:http')7const diagnostics = require('node:diagnostics_channel')8let lightMyRequest9 10const {11 kAvvioBoot,12 kChildren,13 kServerBindings,14 kBodyLimit,15 kSupportedHTTPMethods,16 kRoutePrefix,17 kLogLevel,18 kLogSerializers,19 kHooks,20 kSchemaController,21 kRequestAcceptVersion,22 kReplySerializerDefault,23 kContentTypeParser,24 kReply,25 kRequest,26 kFourOhFour,27 kState,28 kOptions,29 kPluginNameChain,30 kSchemaErrorFormatter,31 kErrorHandler,32 kKeepAliveConnections,33 kChildLoggerFactory,34 kGenReqId35} = require('./lib/symbols.js')36 37const { createServer } = require('./lib/server')38const Reply = require('./lib/reply')39const Request = require('./lib/request')40const Context = require('./lib/context.js')41const decorator = require('./lib/decorate')42const ContentTypeParser = require('./lib/contentTypeParser')43const SchemaController = require('./lib/schema-controller')44const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks')45const { createChildLogger, defaultChildLoggerFactory, createLogger } = require('./lib/logger-factory')46const pluginUtils = require('./lib/pluginUtils')47const { getGenReqId, reqIdGenFactory } = require('./lib/reqIdGenFactory')48const { buildRouting, validateBodyLimitOption } = require('./lib/route')49const build404 = require('./lib/fourOhFour')50const getSecuredInitialConfig = require('./lib/initialConfigValidation')51const override = require('./lib/pluginOverride')52const noopSet = require('./lib/noop-set')53const {54 appendStackTrace,55 AVVIO_ERRORS_MAP,56 ...errorCodes57} = require('./lib/errors')58 59const { defaultInitOptions } = getSecuredInitialConfig60 61const {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_INSTANCE_ALREADY_LISTENING,71 FST_ERR_REOPENED_CLOSE_SERVER,72 FST_ERR_ROUTE_REWRITE_NOT_STR,73 FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN,74 FST_ERR_ERROR_HANDLER_NOT_FN,75 FST_ERR_ROUTE_METHOD_INVALID76} = errorCodes77 78const { buildErrorHandler } = require('./lib/error-handler.js')79 80const initChannel = diagnostics.channel('fastify.initialization')81 82function defaultBuildPrettyMeta (route) {83 // return a shallow copy of route's sanitized context84 85 const cleanKeys = {}86 const allowedProps = ['errorHandler', 'logLevel', 'logSerializers']87 88 allowedProps.concat(supportedHooks).forEach(k => {89 cleanKeys[k] = route.store[k]90 })91 92 return Object.assign({}, cleanKeys)93}94 95/**96 * @param {import('./fastify.js').FastifyServerOptions} options97 */98function fastify (options) {99 // Options validations100 if (options && typeof options !== 'object') {101 throw new FST_ERR_OPTIONS_NOT_OBJ()102 } else {103 // Shallow copy options object to prevent mutations outside of this function104 options = Object.assign({}, options)105 }106 107 if (options.querystringParser && typeof options.querystringParser !== 'function') {108 throw new FST_ERR_QSP_NOT_FN(typeof options.querystringParser)109 }110 111 if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') {112 throw new FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN(typeof options.schemaController.bucket)113 }114 115 validateBodyLimitOption(options.bodyLimit)116 117 const requestIdHeader = typeof options.requestIdHeader === 'string' && options.requestIdHeader.length !== 0 ? options.requestIdHeader.toLowerCase() : (options.requestIdHeader === true && 'request-id')118 const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId)119 const requestIdLogLabel = options.requestIdLogLabel || 'reqId'120 const bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit121 const disableRequestLogging = options.disableRequestLogging || false122 123 const ajvOptions = Object.assign({124 customOptions: {},125 plugins: []126 }, options.ajv)127 const frameworkErrors = options.frameworkErrors128 129 // Ajv options130 if (!ajvOptions.customOptions || Object.prototype.toString.call(ajvOptions.customOptions) !== '[object Object]') {131 throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ(typeof ajvOptions.customOptions)132 }133 if (!ajvOptions.plugins || !Array.isArray(ajvOptions.plugins)) {134 throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR(typeof ajvOptions.plugins)135 }136 137 // Instance Fastify components138 139 const { logger, hasLogger } = createLogger(options)140 141 // Update the options with the fixed values142 options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout143 options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout144 options.maxRequestsPerSocket = options.maxRequestsPerSocket || defaultInitOptions.maxRequestsPerSocket145 options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout146 options.logger = logger147 options.requestIdHeader = requestIdHeader148 options.requestIdLogLabel = requestIdLogLabel149 options.disableRequestLogging = disableRequestLogging150 options.ajv = ajvOptions151 options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler152 153 const initialConfig = getSecuredInitialConfig(options)154 155 // exposeHeadRoutes have its default set from the validator156 options.exposeHeadRoutes = initialConfig.exposeHeadRoutes157 158 // Default router159 const router = buildRouting({160 config: {161 defaultRoute,162 onBadUrl,163 constraints: options.constraints,164 ignoreTrailingSlash: options.ignoreTrailingSlash || defaultInitOptions.ignoreTrailingSlash,165 ignoreDuplicateSlashes: options.ignoreDuplicateSlashes || defaultInitOptions.ignoreDuplicateSlashes,166 maxParamLength: options.maxParamLength || defaultInitOptions.maxParamLength,167 caseSensitive: options.caseSensitive,168 allowUnsafeRegex: options.allowUnsafeRegex || defaultInitOptions.allowUnsafeRegex,169 buildPrettyMeta: defaultBuildPrettyMeta,170 querystringParser: options.querystringParser,171 useSemicolonDelimiter: options.useSemicolonDelimiter ?? defaultInitOptions.useSemicolonDelimiter172 }173 })174 175 // 404 router, used for handling encapsulated 404 handlers176 const fourOhFour = build404(options)177 178 // HTTP server and its handler179 const httpHandler = wrapRouting(router, options)180 181 // we need to set this before calling createServer182 options.http2SessionTimeout = initialConfig.http2SessionTimeout183 const { server, listen } = createServer(options, httpHandler)184 185 const serverHasCloseAllConnections = typeof server.closeAllConnections === 'function'186 const serverHasCloseIdleConnections = typeof server.closeIdleConnections === 'function'187 188 let forceCloseConnections = options.forceCloseConnections189 if (forceCloseConnections === 'idle' && !serverHasCloseIdleConnections) {190 throw new FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE()191 } else if (typeof forceCloseConnections !== 'boolean') {192 /* istanbul ignore next: only one branch can be valid in a given Node.js version */193 forceCloseConnections = serverHasCloseIdleConnections ? 'idle' : false194 }195 196 const keepAliveConnections = !serverHasCloseAllConnections && forceCloseConnections === true ? new Set() : noopSet()197 198 const setupResponseListeners = Reply.setupResponseListeners199 const schemaController = SchemaController.buildSchemaController(null, options.schemaController)200 201 // Public API202 const fastify = {203 // Fastify internals204 [kState]: {205 listening: false,206 closing: false,207 started: false,208 ready: false,209 booting: false,210 readyPromise: null211 },212 [kKeepAliveConnections]: keepAliveConnections,213 [kSupportedHTTPMethods]: {214 bodyless: new Set([215 // Standard216 'GET',217 'HEAD',218 'TRACE'219 ]),220 bodywith: new Set([221 // Standard222 'DELETE',223 'OPTIONS',224 'PATCH',225 'PUT',226 'POST'227 ])228 },229 [kOptions]: options,230 [kChildren]: [],231 [kServerBindings]: [],232 [kBodyLimit]: bodyLimit,233 [kRoutePrefix]: '',234 [kLogLevel]: '',235 [kLogSerializers]: null,236 [kHooks]: new Hooks(),237 [kSchemaController]: schemaController,238 [kSchemaErrorFormatter]: null,239 [kErrorHandler]: buildErrorHandler(),240 [kChildLoggerFactory]: defaultChildLoggerFactory,241 [kReplySerializerDefault]: null,242 [kContentTypeParser]: new ContentTypeParser(243 bodyLimit,244 (options.onProtoPoisoning || defaultInitOptions.onProtoPoisoning),245 (options.onConstructorPoisoning || defaultInitOptions.onConstructorPoisoning)246 ),247 [kReply]: Reply.buildReply(Reply),248 [kRequest]: Request.buildRequest(Request, options.trustProxy),249 [kFourOhFour]: fourOhFour,250 [pluginUtils.kRegisteredPlugins]: [],251 [kPluginNameChain]: ['fastify'],252 [kAvvioBoot]: null,253 [kGenReqId]: genReqId,254 // routing method255 routing: httpHandler,256 // routes shorthand methods257 delete: function _delete (url, options, handler) {258 return router.prepareRoute.call(this, { method: 'DELETE', url, options, handler })259 },260 get: function _get (url, options, handler) {261 return router.prepareRoute.call(this, { method: 'GET', url, options, handler })262 },263 head: function _head (url, options, handler) {264 return router.prepareRoute.call(this, { method: 'HEAD', url, options, handler })265 },266 trace: function _trace (url, options, handler) {267 return router.prepareRoute.call(this, { method: 'TRACE', url, options, handler })268 },269 patch: function _patch (url, options, handler) {270 return router.prepareRoute.call(this, { method: 'PATCH', url, options, handler })271 },272 post: function _post (url, options, handler) {273 return router.prepareRoute.call(this, { method: 'POST', url, options, handler })274 },275 put: function _put (url, options, handler) {276 return router.prepareRoute.call(this, { method: 'PUT', url, options, handler })277 },278 options: function _options (url, options, handler) {279 return router.prepareRoute.call(this, { method: 'OPTIONS', url, options, handler })280 },281 all: function _all (url, options, handler) {282 return router.prepareRoute.call(this, { method: this.supportedMethods, url, options, handler })283 },284 // extended route285 route: function _route (options) {286 // we need the fastify object that we are producing so we apply a lazy loading of the function,287 // otherwise we should bind it after the declaration288 return router.route.call(this, { options })289 },290 hasRoute: function _route (options) {291 return router.hasRoute.call(this, { options })292 },293 findRoute: function _findRoute (options) {294 return router.findRoute(options)295 },296 // expose logger instance297 log: logger,298 // type provider299 withTypeProvider,300 // hooks301 addHook,302 // schemas303 addSchema,304 getSchema: schemaController.getSchema.bind(schemaController),305 getSchemas: schemaController.getSchemas.bind(schemaController),306 setValidatorCompiler,307 setSerializerCompiler,308 setSchemaController,309 setReplySerializer,310 setSchemaErrorFormatter,311 // set generated request id312 setGenReqId,313 // custom parsers314 addContentTypeParser: ContentTypeParser.helpers.addContentTypeParser,315 hasContentTypeParser: ContentTypeParser.helpers.hasContentTypeParser,316 getDefaultJsonParser: ContentTypeParser.defaultParsers.getDefaultJsonParser,317 defaultTextParser: ContentTypeParser.defaultParsers.defaultTextParser,318 removeContentTypeParser: ContentTypeParser.helpers.removeContentTypeParser,319 removeAllContentTypeParsers: ContentTypeParser.helpers.removeAllContentTypeParsers,320 // Fastify architecture methods (initialized by Avvio)321 register: null,322 after: null,323 ready: null,324 onClose: null,325 close: null,326 printPlugins: null,327 hasPlugin: function (name) {328 return this[pluginUtils.kRegisteredPlugins].includes(name) || this[kPluginNameChain].includes(name)329 },330 // http server331 listen,332 server,333 addresses: function () {334 /* istanbul ignore next */335 const binded = this[kServerBindings].map(b => b.address())336 binded.push(this.server.address())337 return binded.filter(adr => adr)338 },339 // extend fastify objects340 decorate: decorator.add,341 hasDecorator: decorator.exist,342 decorateReply: decorator.decorateReply,343 decorateRequest: decorator.decorateRequest,344 hasRequestDecorator: decorator.existRequest,345 hasReplyDecorator: decorator.existReply,346 addHttpMethod,347 // fake http injection348 inject,349 // pretty print of the registered routes350 printRoutes,351 // custom error handling352 setNotFoundHandler,353 setErrorHandler,354 // child logger355 setChildLoggerFactory,356 // Set fastify initial configuration options read-only object357 initialConfig,358 // constraint strategies359 addConstraintStrategy: router.addConstraintStrategy.bind(router),360 hasConstraintStrategy: router.hasConstraintStrategy.bind(router)361 }362 363 Object.defineProperties(fastify, {364 listeningOrigin: {365 get () {366 const address = this.addresses().slice(-1).pop()367 /* ignore if windows: unix socket is not testable on Windows platform */368 /* c8 ignore next 3 */369 if (typeof address === 'string') {370 return address371 }372 const host = address.family === 'IPv6' ? `[${address.address}]` : address.address373 return `${this[kOptions].https ? 'https' : 'http'}://${host}:${address.port}`374 }375 },376 pluginName: {377 configurable: true,378 get () {379 if (this[kPluginNameChain].length > 1) {380 return this[kPluginNameChain].join(' -> ')381 }382 return this[kPluginNameChain][0]383 }384 },385 prefix: {386 configurable: true,387 get () { return this[kRoutePrefix] }388 },389 validatorCompiler: {390 configurable: true,391 get () { return this[kSchemaController].getValidatorCompiler() }392 },393 serializerCompiler: {394 configurable: true,395 get () { return this[kSchemaController].getSerializerCompiler() }396 },397 childLoggerFactory: {398 configurable: true,399 get () { return this[kChildLoggerFactory] }400 },401 version: {402 configurable: true,403 get () { return VERSION }404 },405 errorHandler: {406 configurable: true,407 get () {408 return this[kErrorHandler].func409 }410 },411 genReqId: {412 configurable: true,413 get () { return this[kGenReqId] }414 },415 supportedMethods: {416 configurable: false,417 get () {418 return [419 ...this[kSupportedHTTPMethods].bodyless,420 ...this[kSupportedHTTPMethods].bodywith421 ]422 }423 }424 })425 426 if (options.schemaErrorFormatter) {427 validateSchemaErrorFormatter(options.schemaErrorFormatter)428 fastify[kSchemaErrorFormatter] = options.schemaErrorFormatter.bind(fastify)429 }430 431 // Install and configure Avvio432 // Avvio will update the following Fastify methods:433 // - register434 // - after435 // - ready436 // - onClose437 // - close438 439 const avvioPluginTimeout = Number(options.pluginTimeout)440 const avvio = Avvio(fastify, {441 autostart: false,442 timeout: isNaN(avvioPluginTimeout) === false ? avvioPluginTimeout : defaultInitOptions.pluginTimeout,443 expose: {444 use: 'register'445 }446 })447 // Override to allow the plugin encapsulation448 avvio.override = override449 avvio.on('start', () => (fastify[kState].started = true))450 fastify[kAvvioBoot] = fastify.ready // the avvio ready function451 fastify.ready = ready // overwrite the avvio ready function452 fastify.printPlugins = avvio.prettyPrint.bind(avvio)453 454 // cache the closing value, since we are checking it in an hot path455 avvio.once('preReady', () => {456 fastify.onClose((instance, done) => {457 fastify[kState].closing = true458 router.closeRoutes()459 460 hookRunnerApplication('preClose', fastify[kAvvioBoot], fastify, function () {461 if (fastify[kState].listening) {462 /* istanbul ignore next: Cannot test this without Node.js core support */463 if (forceCloseConnections === 'idle') {464 // Not needed in Node 19465 instance.server.closeIdleConnections()466 /* istanbul ignore next: Cannot test this without Node.js core support */467 } else if (serverHasCloseAllConnections && forceCloseConnections) {468 instance.server.closeAllConnections()469 } else if (forceCloseConnections === true) {470 for (const conn of fastify[kKeepAliveConnections]) {471 // We must invoke the destroy method instead of merely unreffing472 // the sockets. If we only unref, then the callback passed to473 // `fastify.close` will never be invoked; nor will any of the474 // registered `onClose` hooks.475 conn.destroy()476 fastify[kKeepAliveConnections].delete(conn)477 }478 }479 }480 481 // No new TCP connections are accepted.482 // We must call close on the server even if we are not listening483 // otherwise memory will be leaked.484 // https://github.com/nodejs/node/issues/48604485 if (!options.serverFactory || fastify[kState].listening) {486 instance.server.close(function (err) {487 /* c8 ignore next 6 */488 if (err && err.code !== 'ERR_SERVER_NOT_RUNNING') {489 done(null)490 } else {491 done()492 }493 })494 } else {495 process.nextTick(done, null)496 }497 })498 })499 })500 501 // Create bad URL context502 const onBadUrlContext = new Context({503 server: fastify,504 config: {}505 })506 507 // Set the default 404 handler508 fastify.setNotFoundHandler()509 fourOhFour.arrange404(fastify)510 511 router.setup(options, {512 avvio,513 fourOhFour,514 logger,515 hasLogger,516 setupResponseListeners,517 throwIfAlreadyStarted,518 keepAliveConnections519 })520 521 // Delay configuring clientError handler so that it can access fastify state.522 server.on('clientError', options.clientErrorHandler.bind(fastify))523 524 if (initChannel.hasSubscribers) {525 initChannel.publish({ fastify })526 }527 528 // Older nodejs versions may not have asyncDispose529 if ('asyncDispose' in Symbol) {530 fastify[Symbol.asyncDispose] = function dispose () {531 return fastify.close()532 }533 }534 535 return fastify536 537 function throwIfAlreadyStarted (msg) {538 if (fastify[kState].started) throw new FST_ERR_INSTANCE_ALREADY_LISTENING(msg)539 }540 541 // HTTP injection handling542 // If the server is not ready yet, this543 // utility will automatically force it.544 function inject (opts, cb) {545 // lightMyRequest is dynamically loaded as it seems very expensive546 // because of Ajv547 if (lightMyRequest === undefined) {548 lightMyRequest = require('light-my-request')549 }550 551 if (fastify[kState].started) {552 if (fastify[kState].closing) {553 // Force to return an error554 const error = new FST_ERR_REOPENED_CLOSE_SERVER()555 if (cb) {556 cb(error)557 return558 } else {559 return Promise.reject(error)560 }561 }562 return lightMyRequest(httpHandler, opts, cb)563 }564 565 if (cb) {566 this.ready(err => {567 if (err) cb(err, null)568 else lightMyRequest(httpHandler, opts, cb)569 })570 } else {571 return lightMyRequest((req, res) => {572 this.ready(function (err) {573 if (err) {574 res.emit('error', err)575 return576 }577 httpHandler(req, res)578 })579 }, opts)580 }581 }582 583 function ready (cb) {584 if (this[kState].readyPromise !== null) {585 if (cb != null) {586 this[kState].readyPromise.then(() => cb(null, fastify), cb)587 return588 }589 590 return this[kState].readyPromise591 }592 593 let resolveReady594 let rejectReady595 596 // run the hooks after returning the promise597 process.nextTick(runHooks)598 599 // Create a promise no matter what600 // It will work as a barrier for all the .ready() calls (ensuring single hook execution)601 // as well as a flow control mechanism to chain cbs and further602 // promises603 this[kState].readyPromise = new Promise(function (resolve, reject) {604 resolveReady = resolve605 rejectReady = reject606 })607 608 if (!cb) {609 return this[kState].readyPromise610 } else {611 this[kState].readyPromise.then(() => cb(null, fastify), cb)612 }613 614 function runHooks () {615 // start loading616 fastify[kAvvioBoot]((err, done) => {617 if (err || fastify[kState].started || fastify[kState].ready || fastify[kState].booting) {618 manageErr(err)619 } else {620 fastify[kState].booting = true621 hookRunnerApplication('onReady', fastify[kAvvioBoot], fastify, manageErr)622 }623 done()624 })625 }626 627 function manageErr (err) {628 // If the error comes out of Avvio's Error codes629 // We create a make and preserve the previous error630 // as cause631 err = err != null && AVVIO_ERRORS_MAP[err.code] != null632 ? appendStackTrace(err, new AVVIO_ERRORS_MAP[err.code](err.message))633 : err634 635 if (err) {636 return rejectReady(err)637 }638 639 resolveReady(fastify)640 fastify[kState].booting = false641 fastify[kState].ready = true642 fastify[kState].promise = null643 }644 }645 646 // Used exclusively in TypeScript contexts to enable auto type inference from JSON schema.647 function withTypeProvider () {648 return this649 }650 651 // wrapper that we expose to the user for hooks handling652 function addHook (name, fn) {653 throwIfAlreadyStarted('Cannot call "addHook"!')654 655 if (fn == null) {656 throw new errorCodes.FST_ERR_HOOK_INVALID_HANDLER(name, fn)657 }658 659 if (name === 'onSend' || name === 'preSerialization' || name === 'onError' || name === 'preParsing') {660 if (fn.constructor.name === 'AsyncFunction' && fn.length === 4) {661 throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER()662 }663 } else if (name === 'onReady' || name === 'onListen') {664 if (fn.constructor.name === 'AsyncFunction' && fn.length !== 0) {665 throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER()666 }667 } else if (name === 'onRequestAbort') {668 if (fn.constructor.name === 'AsyncFunction' && fn.length !== 1) {669 throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER()670 }671 } else {672 if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) {673 throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER()674 }675 }676 677 if (name === 'onClose') {678 this.onClose(fn.bind(this))679 } else if (name === 'onReady' || name === 'onListen' || name === 'onRoute') {680 this[kHooks].add(name, fn)681 } else {682 this.after((err, done) => {683 _addHook.call(this, name, fn)684 done(err)685 })686 }687 return this688 689 function _addHook (name, fn) {690 this[kHooks].add(name, fn)691 this[kChildren].forEach(child => _addHook.call(child, name, fn))692 }693 }694 695 // wrapper that we expose to the user for schemas handling696 function addSchema (schema) {697 throwIfAlreadyStarted('Cannot call "addSchema"!')698 this[kSchemaController].add(schema)699 this[kChildren].forEach(child => child.addSchema(schema))700 return this701 }702 703 function defaultClientErrorHandler (err, socket) {704 // In case of a connection reset, the socket has been destroyed and there is nothing that needs to be done.705 // https://nodejs.org/api/http.html#http_event_clienterror706 if (err.code === 'ECONNRESET' || socket.destroyed) {707 return708 }709 710 let body, errorCode, errorStatus, errorLabel711 712 if (err.code === 'ERR_HTTP_REQUEST_TIMEOUT') {713 errorCode = '408'714 errorStatus = http.STATUS_CODES[errorCode]715 body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}`716 errorLabel = 'timeout'717 } else if (err.code === 'HPE_HEADER_OVERFLOW') {718 errorCode = '431'719 errorStatus = http.STATUS_CODES[errorCode]720 body = `{"error":"${errorStatus}","message":"Exceeded maximum allowed HTTP header size","statusCode":431}`721 errorLabel = 'header_overflow'722 } else {723 errorCode = '400'724 errorStatus = http.STATUS_CODES[errorCode]725 body = `{"error":"${errorStatus}","message":"Client Error","statusCode":400}`726 errorLabel = 'error'727 }728 729 // Most devs do not know what to do with this error.730 // In the vast majority of cases, it's a network error and/or some731 // config issue on the load balancer side.732 this.log.trace({ err }, `client ${errorLabel}`)733 // Copying standard node behavior734 // https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666735 736 // If the socket is not writable, there is no reason to try to send data.737 if (socket.writable) {738 socket.write(`HTTP/1.1 ${errorCode} ${errorStatus}\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)739 }740 socket.destroy(err)741 }742 743 // If the router does not match any route, every request will land here744 // req and res are Node.js core objects745 function defaultRoute (req, res) {746 if (req.headers['accept-version'] !== undefined) {747 // we remove the accept-version header for performance result748 // because we do not want to go through the constraint checking749 // the usage of symbol here to prevent any collision on custom header name750 req.headers[kRequestAcceptVersion] = req.headers['accept-version']751 req.headers['accept-version'] = undefined752 }753 fourOhFour.router.lookup(req, res)754 }755 756 function onBadUrl (path, req, res) {757 if (frameworkErrors) {758 const id = getGenReqId(onBadUrlContext.server, req)759 const childLogger = createChildLogger(onBadUrlContext, logger, req, id)760 761 const request = new Request(id, null, req, null, childLogger, onBadUrlContext)762 const reply = new Reply(res, request, childLogger)763 764 if (disableRequestLogging === false) {765 childLogger.info({ req: request }, 'incoming request')766 }767 768 return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)769 }770 const body = `{"error":"Bad Request","code":"FST_ERR_BAD_URL","message":"'${path}' is not a valid url component","statusCode":400}`771 res.writeHead(400, {772 'Content-Type': 'application/json',773 'Content-Length': body.length774 })775 res.end(body)776 }777 778 function buildAsyncConstraintCallback (isAsync, req, res) {779 if (isAsync === false) return undefined780 return function onAsyncConstraintError (err) {781 if (err) {782 if (frameworkErrors) {783 const id = getGenReqId(onBadUrlContext.server, req)784 const childLogger = createChildLogger(onBadUrlContext, logger, req, id)785 786 const request = new Request(id, null, req, null, childLogger, onBadUrlContext)787 const reply = new Reply(res, request, childLogger)788 789 if (disableRequestLogging === false) {790 childLogger.info({ req: request }, 'incoming request')791 }792 793 return frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)794 }795 const body = '{"error":"Internal Server Error","message":"Unexpected error from async constraint","statusCode":500}'796 res.writeHead(500, {797 'Content-Type': 'application/json',798 'Content-Length': body.length799 })800 res.end(body)801 }802 }803 }804 805 function setNotFoundHandler (opts, handler) {806 throwIfAlreadyStarted('Cannot call "setNotFoundHandler"!')807 808 fourOhFour.setNotFoundHandler.call(this, opts, handler, avvio, router.routeHandler)809 return this810 }811 812 function setValidatorCompiler (validatorCompiler) {813 throwIfAlreadyStarted('Cannot call "setValidatorCompiler"!')814 this[kSchemaController].setValidatorCompiler(validatorCompiler)815 return this816 }817 818 function setSchemaErrorFormatter (errorFormatter) {819 throwIfAlreadyStarted('Cannot call "setSchemaErrorFormatter"!')820 validateSchemaErrorFormatter(errorFormatter)821 this[kSchemaErrorFormatter] = errorFormatter.bind(this)822 return this823 }824 825 function setSerializerCompiler (serializerCompiler) {826 throwIfAlreadyStarted('Cannot call "setSerializerCompiler"!')827 this[kSchemaController].setSerializerCompiler(serializerCompiler)828 return this829 }830 831 function setSchemaController (schemaControllerOpts) {832 throwIfAlreadyStarted('Cannot call "setSchemaController"!')833 const old = this[kSchemaController]834 const schemaController = SchemaController.buildSchemaController(old, Object.assign({}, old.opts, schemaControllerOpts))835 this[kSchemaController] = schemaController836 this.getSchema = schemaController.getSchema.bind(schemaController)837 this.getSchemas = schemaController.getSchemas.bind(schemaController)838 return this839 }840 841 function setReplySerializer (replySerializer) {842 throwIfAlreadyStarted('Cannot call "setReplySerializer"!')843 844 this[kReplySerializerDefault] = replySerializer845 return this846 }847 848 // wrapper that we expose to the user for configure the custom error handler849 function setErrorHandler (func) {850 throwIfAlreadyStarted('Cannot call "setErrorHandler"!')851 852 if (typeof func !== 'function') {853 throw new FST_ERR_ERROR_HANDLER_NOT_FN()854 }855 856 this[kErrorHandler] = buildErrorHandler(this[kErrorHandler], func.bind(this))857 return this858 }859 860 function setChildLoggerFactory (factory) {861 throwIfAlreadyStarted('Cannot call "setChildLoggerFactory"!')862 863 this[kChildLoggerFactory] = factory864 return this865 }866 867 function printRoutes (opts = {}) {868 // includeHooks:true - shortcut to include all supported hooks exported by fastify.Hooks869 opts.includeMeta = opts.includeHooks ? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks : opts.includeMeta870 return router.printRoutes(opts)871 }872 873 function wrapRouting (router, { rewriteUrl, logger }) {874 let isAsync875 return function preRouting (req, res) {876 // only call isAsyncConstraint once877 if (isAsync === undefined) isAsync = router.isAsyncConstraint()878 if (rewriteUrl) {879 req.originalUrl = req.url880 const url = rewriteUrl.call(fastify, req)881 if (typeof url === 'string') {882 req.url = url883 } else {884 const err = new FST_ERR_ROUTE_REWRITE_NOT_STR(req.url, typeof url)885 req.destroy(err)886 }887 }888 router.routing(req, res, buildAsyncConstraintCallback(isAsync, req, res))889 }890 }891 892 function setGenReqId (func) {893 throwIfAlreadyStarted('Cannot call "setGenReqId"!')894 895 this[kGenReqId] = reqIdGenFactory(this[kOptions].requestIdHeader, func)896 return this897 }898 899 function addHttpMethod (method, { hasBody = false } = {}) {900 if (typeof method !== 'string' || http.METHODS.indexOf(method) === -1) {901 throw new FST_ERR_ROUTE_METHOD_INVALID()902 }903 904 if (hasBody === true) {905 this[kSupportedHTTPMethods].bodywith.add(method)906 this[kSupportedHTTPMethods].bodyless.delete(method)907 } else {908 this[kSupportedHTTPMethods].bodywith.delete(method)909 this[kSupportedHTTPMethods].bodyless.add(method)910 }911 912 const _method = method.toLowerCase()913 if (!this.hasDecorator(_method)) {914 this.decorate(_method, function (url, options, handler) {915 return router.prepareRoute.call(this, { method, url, options, handler })916 })917 }918 919 return this920 }921}922 923function validateSchemaErrorFormatter (schemaErrorFormatter) {924 if (typeof schemaErrorFormatter !== 'function') {925 throw new FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN(typeof schemaErrorFormatter)926 } else if (schemaErrorFormatter.constructor.name === 'AsyncFunction') {927 throw new FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN('AsyncFunction')928 }929}930 931/**932 * These export configurations enable JS and TS developers933 * to consume fastify in whatever way best suits their needs.934 * Some examples of supported import syntax includes:935 * - `const fastify = require('fastify')`936 * - `const { fastify } = require('fastify')`937 * - `import * as Fastify from 'fastify'`938 * - `import { fastify, TSC_definition } from 'fastify'`939 * - `import fastify from 'fastify'`940 * - `import fastify, { TSC_definition } from 'fastify'`941 */942module.exports = fastify943module.exports.errorCodes = errorCodes944module.exports.fastify = fastify945module.exports.default = fastify946 