UNPKG

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