UNPKG

5.08 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 kHooks
19} = require('./symbols.js')
20const { lifecycleHooks } = require('./hooks')
21const fourOhFourContext = {
22 config: {
23 },
24 onSend: [],
25 onError: []
26}
27
28/**
29 * Each fastify instance have a:
30 * kFourOhFourLevelInstance: point to a fastify instance that has the 404 handler setted
31 * kCanSetNotFoundHandler: bool to track if the 404 handler has alredy been set
32 * kFourOhFour: the singleton instance of this 404 module
33 * kFourOhFourContext: the context in the reply object where the handler will be executed
34 */
35function fourOhFour (options) {
36 const { logger, genReqId } = options
37
38 // 404 router, used for handling encapsulated 404 handlers
39 const router = FindMyWay({ defaultRoute: fourOhFourFallBack })
40
41 return { router, setNotFoundHandler, setContext, arrange404 }
42
43 function arrange404 (instance) {
44 // Change the pointer of the fastify instance to itself, so register + prefix can add new 404 handler
45 instance[kFourOhFourLevelInstance] = instance
46 instance[kCanSetNotFoundHandler] = true
47 }
48
49 function basic404 (request, reply) {
50 const { url, method } = request.raw
51 const message = `Route ${method}:${url} not found`
52 request.log.info(message)
53 reply.code(404).send({
54 message,
55 error: 'Not Found',
56 statusCode: 404
57 })
58 }
59
60 function setContext (instance, context) {
61 const _404Context = Object.assign({}, instance[kFourOhFourContext])
62 _404Context.onSend = context.onSend
63 context[kFourOhFourContext] = _404Context
64 }
65
66 function setNotFoundHandler (opts, handler, avvio, routeHandler) {
67 // First initialization of the fastify root instance
68 if (this[kCanSetNotFoundHandler] === undefined) {
69 this[kCanSetNotFoundHandler] = true
70 }
71 if (this[kFourOhFourContext] === undefined) {
72 this[kFourOhFourContext] = null
73 }
74
75 const _fastify = this
76 const prefix = this[kRoutePrefix] || '/'
77
78 if (this[kCanSetNotFoundHandler] === false) {
79 throw new Error(`Not found handler already set for Fastify instance with prefix: '${prefix}'`)
80 }
81
82 if (typeof opts === 'object') {
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 for (const hook of lifecycleHooks) {
135 const toSet = this[kHooks][hook]
136 .concat(opts[hook] || [])
137 .map(h => h.bind(this))
138 context[hook] = toSet.length ? toSet : null
139 }
140 })
141
142 if (this[kFourOhFourContext] !== null && prefix === '/') {
143 Object.assign(this[kFourOhFourContext], context) // Replace the default 404 handler
144 return
145 }
146
147 this[kFourOhFourLevelInstance][kFourOhFourContext] = context
148
149 router.all(prefix + (prefix.endsWith('/') ? '*' : '/*'), routeHandler, context)
150 router.all(prefix, routeHandler, context)
151 }
152
153 function fourOhFourFallBack (req, res) {
154 // if this happen, we have a very bad bug
155 // we might want to do some hard debugging
156 // here, let's print out as much info as
157 // we can
158 var id = genReqId(req)
159 var childLogger = logger.child({ reqId: id })
160
161 childLogger.info({ req }, 'incoming request')
162
163 var request = new Request(id, null, req, null, childLogger, fourOhFourContext)
164 var reply = new Reply(res, request, childLogger)
165
166 request.log.warn('the default handler for 404 did not catch this, this is likely a fastify bug, please report it')
167 request.log.warn(router.prettyPrint())
168 reply.code(404).send(new Error('Not Found'))
169 }
170}
171
172module.exports = fourOhFour