UNPKG

6.3 kBJavaScriptView Raw
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 kReply,
13 kRequest,
14 kContentTypeParser,
15 kBodyLimit,
16 kLogLevel,
17 kFourOhFourContext,
18 kMiddlewares,
19 kHooks
20} = require('./symbols.js')
21const { beforeHandlerWarning } = require('./warnings')
22const { buildMiddie } = require('./middleware')
23
24/**
25 * Each fastify instance have a:
26 * kFourOhFourLevelInstance: point to a fastify instance that has the 404 handler setted
27 * kCanSetNotFoundHandler: bool to track if the 404 handler has alredy been set
28 * kFourOhFour: the singleton instance of this 404 module
29 * kFourOhFourContext: the context in the reply object where the handler will be executed
30 */
31function fourOhFour (options) {
32 const { logger, modifyCoreObjects, genReqId } = options
33
34 // 404 router, used for handling encapsulated 404 handlers
35 const router = FindMyWay({ defaultRoute: fourOhFourFallBack })
36
37 return { router, setNotFoundHandler, setContext, arrange404 }
38
39 function arrange404 (instance) {
40 // Change the pointer of the fastify instance to itself, so register + prefix can add new 404 handler
41 instance[kFourOhFourLevelInstance] = instance
42 instance[kCanSetNotFoundHandler] = true
43 }
44
45 function basic404 (request, reply) {
46 const { url, method } = request.raw
47 const message = `Route ${method}:${url} not found`
48 request.log.info(message)
49 reply.code(404).send({
50 message,
51 error: 'Not Found',
52 statusCode: 404
53 })
54 }
55
56 function setContext (instance, context) {
57 const _404Context = Object.assign({}, instance[kFourOhFourContext])
58 _404Context.onSend = context.onSend
59 context[kFourOhFourContext] = _404Context
60 }
61
62 function setNotFoundHandler (opts, handler, avvio, routeHandler) {
63 // First initialization of the fastify root instance
64 if (this[kCanSetNotFoundHandler] === undefined) {
65 this[kCanSetNotFoundHandler] = true
66 }
67 if (this[kFourOhFourContext] === undefined) {
68 this[kFourOhFourContext] = null
69 }
70
71 const _fastify = this
72 const prefix = this[kRoutePrefix] || '/'
73
74 if (this[kCanSetNotFoundHandler] === false) {
75 throw new Error(`Not found handler already set for Fastify instance with prefix: '${prefix}'`)
76 }
77
78 if (typeof opts === 'object') {
79 if (opts.preHandler == null && opts.beforeHandler != null) {
80 beforeHandlerWarning()
81 opts.preHandler = opts.beforeHandler
82 }
83 if (opts.preHandler) {
84 if (Array.isArray(opts.preHandler)) {
85 opts.preHandler = opts.preHandler.map(hook => hook.bind(_fastify))
86 } else {
87 opts.preHandler = opts.preHandler.bind(_fastify)
88 }
89 }
90
91 if (opts.preValidation) {
92 if (Array.isArray(opts.preValidation)) {
93 opts.preValidation = opts.preValidation.map(hook => hook.bind(_fastify))
94 } else {
95 opts.preValidation = opts.preValidation.bind(_fastify)
96 }
97 }
98 }
99
100 if (typeof opts === 'function') {
101 handler = opts
102 opts = undefined
103 }
104 opts = opts || {}
105
106 if (handler) {
107 this[kFourOhFourLevelInstance][kCanSetNotFoundHandler] = false
108 handler = handler.bind(this)
109 } else {
110 handler = basic404
111 }
112
113 this.after((notHandledErr, done) => {
114 _setNotFoundHandler.call(this, prefix, opts, handler, avvio, routeHandler)
115 done(notHandledErr)
116 })
117 }
118
119 function _setNotFoundHandler (prefix, opts, handler, avvio, routeHandler) {
120 const context = new Context(
121 opts.schema,
122 handler,
123 this[kReply],
124 this[kRequest],
125 this[kContentTypeParser],
126 opts.config || {},
127 this._errorHandler,
128 this[kBodyLimit],
129 this[kLogLevel]
130 )
131
132 avvio.once('preReady', () => {
133 const context = this[kFourOhFourContext]
134
135 const onRequest = this[kHooks].onRequest
136 const preParsing = this[kHooks].preParsing.concat(opts.preParsing || [])
137 const preValidation = this[kHooks].preValidation.concat(opts.preValidation || [])
138 const preSerialization = this[kHooks].preSerialization.concat(opts.preSerialization || [])
139 const preHandler = this[kHooks].preHandler.concat(opts.beforeHandler || opts.preHandler || [])
140 const onSend = this[kHooks].onSend
141 const onError = this[kHooks].onError
142 const onResponse = this[kHooks].onResponse
143
144 context.onRequest = onRequest.length ? onRequest : null
145 context.preParsing = preParsing.length ? preParsing : null
146 context.preValidation = preValidation.length ? preValidation : null
147 context.preSerialization = preSerialization.length ? preSerialization : null
148 context.preHandler = preHandler.length ? preHandler : null
149 context.onSend = onSend.length ? onSend : null
150 context.onError = onError.length ? onError : null
151 context.onResponse = onResponse.length ? onResponse : null
152
153 context._middie = buildMiddie(this[kMiddlewares])
154 })
155
156 if (this[kFourOhFourContext] !== null && prefix === '/') {
157 Object.assign(this[kFourOhFourContext], context) // Replace the default 404 handler
158 return
159 }
160
161 this[kFourOhFourLevelInstance][kFourOhFourContext] = context
162
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 bug
169 // we might want to do some hard debugging
170 // here, let's print out as much info as
171 // we can
172 req.id = genReqId(req)
173 req.originalUrl = req.url
174 var childLogger = logger.child({ reqId: req.id })
175 if (modifyCoreObjects) {
176 req.log = res.log = childLogger
177 }
178
179 childLogger.info({ req }, 'incoming request')
180
181 var request = new Request(null, req, null, req.headers, childLogger)
182 var reply = new Reply(res, { onSend: [], onError: [] }, request, childLogger)
183
184 request.log.warn('the default handler for 404 did not catch this, this is likely a fastify bug, please report it')
185 request.log.warn(router.prettyPrint())
186 reply.code(404).send(new Error('Not Found'))
187 }
188}
189
190module.exports = fourOhFour