strong-tie/inbound-calls
0
1'use strict'2 3const FindMyWay = require('find-my-way')4 5const Reply = require('./reply')6const Request = require('./request')7const Context = require('./context')8const {9 kRoutePrefix,10 kCanSetNotFoundHandler,11 kFourOhFourLevelInstance,12 kFourOhFourContext,13 kHooks,14 kErrorHandler15} = require('./symbols.js')16const { lifecycleHooks } = require('./hooks')17const { buildErrorHandler } = require('./error-handler.js')18const {19 FST_ERR_NOT_FOUND20} = require('./errors')21const { createChildLogger } = require('./logger-factory')22const { getGenReqId } = require('./reqIdGenFactory.js')23 24/**25 * Each fastify instance have a:26 * kFourOhFourLevelInstance: point to a fastify instance that has the 404 handler set27 * kCanSetNotFoundHandler: bool to track if the 404 handler has already been set28 * kFourOhFour: the singleton instance of this 404 module29 * kFourOhFourContext: the context in the reply object where the handler will be executed30 */31function fourOhFour (options) {32 const { logger, disableRequestLogging } = options33 34 // 404 router, used for handling encapsulated 404 handlers35 const router = FindMyWay({ onBadUrl: createOnBadUrl(), defaultRoute: fourOhFourFallBack })36 let _onBadUrlHandler = null37 38 return { router, setNotFoundHandler, setContext, arrange404 }39 40 function arrange404 (instance) {41 // Change the pointer of the fastify instance to itself, so register + prefix can add new 404 handler42 instance[kFourOhFourLevelInstance] = instance43 instance[kCanSetNotFoundHandler] = true44 // we need to bind instance for the context45 router.onBadUrl = router.onBadUrl.bind(instance)46 router.defaultRoute = router.defaultRoute.bind(instance)47 }48 49 function basic404 (request, reply) {50 const { url, method } = request.raw51 const message = `Route ${method}:${url} not found`52 if (!disableRequestLogging) {53 request.log.info(message)54 }55 reply.code(404).send({56 message,57 error: 'Not Found',58 statusCode: 40459 })60 }61 62 function createOnBadUrl () {63 return function onBadUrl (path, req, res) {64 const fourOhFourContext = this[kFourOhFourLevelInstance][kFourOhFourContext]65 const id = getGenReqId(fourOhFourContext.server, req)66 const childLogger = createChildLogger(fourOhFourContext, logger, req, id)67 const request = new Request(id, null, req, null, childLogger, fourOhFourContext)68 const reply = new Reply(res, request, childLogger)69 70 _onBadUrlHandler(request, reply)71 }72 }73 74 function setContext (instance, context) {75 const _404Context = Object.assign({}, instance[kFourOhFourContext])76 _404Context.onSend = context.onSend77 context[kFourOhFourContext] = _404Context78 }79 80 function setNotFoundHandler (opts, handler, avvio, routeHandler) {81 // First initialization of the fastify root instance82 if (this[kCanSetNotFoundHandler] === undefined) {83 this[kCanSetNotFoundHandler] = true84 }85 if (this[kFourOhFourContext] === undefined) {86 this[kFourOhFourContext] = null87 }88 89 const _fastify = this90 const prefix = this[kRoutePrefix] || '/'91 92 if (this[kCanSetNotFoundHandler] === false) {93 throw new Error(`Not found handler already set for Fastify instance with prefix: '${prefix}'`)94 }95 96 if (typeof opts === 'object') {97 if (opts.preHandler) {98 if (Array.isArray(opts.preHandler)) {99 opts.preHandler = opts.preHandler.map(hook => hook.bind(_fastify))100 } else {101 opts.preHandler = opts.preHandler.bind(_fastify)102 }103 }104 105 if (opts.preValidation) {106 if (Array.isArray(opts.preValidation)) {107 opts.preValidation = opts.preValidation.map(hook => hook.bind(_fastify))108 } else {109 opts.preValidation = opts.preValidation.bind(_fastify)110 }111 }112 }113 114 if (typeof opts === 'function') {115 handler = opts116 opts = undefined117 }118 opts = opts || {}119 120 if (handler) {121 this[kFourOhFourLevelInstance][kCanSetNotFoundHandler] = false122 handler = handler.bind(this)123 // update onBadUrl handler124 _onBadUrlHandler = handler125 } else {126 handler = basic404127 // update onBadUrl handler128 _onBadUrlHandler = basic404129 }130 131 this.after((notHandledErr, done) => {132 _setNotFoundHandler.call(this, prefix, opts, handler, avvio, routeHandler)133 done(notHandledErr)134 })135 }136 137 function _setNotFoundHandler (prefix, opts, handler, avvio, routeHandler) {138 const context = new Context({139 schema: opts.schema,140 handler,141 config: opts.config || {},142 server: this143 })144 145 avvio.once('preReady', () => {146 const context = this[kFourOhFourContext]147 for (const hook of lifecycleHooks) {148 const toSet = this[kHooks][hook]149 .concat(opts[hook] || [])150 .map(h => h.bind(this))151 context[hook] = toSet.length ? toSet : null152 }153 context.errorHandler = opts.errorHandler ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) : this[kErrorHandler]154 })155 156 if (this[kFourOhFourContext] !== null && prefix === '/') {157 Object.assign(this[kFourOhFourContext], context) // Replace the default 404 handler158 return159 }160 161 this[kFourOhFourLevelInstance][kFourOhFourContext] = context162 163 router.all(prefix + (prefix.endsWith('/') ? '*' : '/*'), routeHandler, context)164 router.all(prefix, routeHandler, context)165 }166 167 function fourOhFourFallBack (req, res) {168 // if this happen, we have a very bad bug169 // we might want to do some hard debugging170 // here, let's print out as much info as171 // we can172 const fourOhFourContext = this[kFourOhFourLevelInstance][kFourOhFourContext]173 const id = getGenReqId(fourOhFourContext.server, req)174 const childLogger = createChildLogger(fourOhFourContext, logger, req, id)175 176 childLogger.info({ req }, 'incoming request')177 178 const request = new Request(id, null, req, null, childLogger, fourOhFourContext)179 const reply = new Reply(res, request, childLogger)180 181 request.log.warn('the default handler for 404 did not catch this, this is likely a fastify bug, please report it')182 request.log.warn(router.prettyPrint())183 reply.code(404).send(new FST_ERR_NOT_FOUND())184 }185}186 187module.exports = fourOhFour188 