UNPKG

9.01 kBJavaScriptView Raw
1'use strict'
2
3const applicationHooks = [
4 'onRoute',
5 'onRegister',
6 'onReady',
7 'onListen',
8 'preClose',
9 'onClose'
10]
11const lifecycleHooks = [
12 'onTimeout',
13 'onRequest',
14 'preParsing',
15 'preValidation',
16 'preSerialization',
17 'preHandler',
18 'onSend',
19 'onResponse',
20 'onError',
21 'onRequestAbort'
22]
23const supportedHooks = lifecycleHooks.concat(applicationHooks)
24const {
25 FST_ERR_HOOK_INVALID_TYPE,
26 FST_ERR_HOOK_INVALID_HANDLER,
27 FST_ERR_SEND_UNDEFINED_ERR,
28 FST_ERR_HOOK_TIMEOUT,
29 FST_ERR_HOOK_NOT_SUPPORTED,
30 AVVIO_ERRORS_MAP,
31 appendStackTrace
32} = require('./errors')
33
34const {
35 kChildren,
36 kHooks,
37 kRequestPayloadStream
38} = require('./symbols')
39
40function Hooks () {
41 this.onRequest = []
42 this.preParsing = []
43 this.preValidation = []
44 this.preSerialization = []
45 this.preHandler = []
46 this.onResponse = []
47 this.onSend = []
48 this.onError = []
49 this.onRoute = []
50 this.onRegister = []
51 this.onReady = []
52 this.onListen = []
53 this.onTimeout = []
54 this.onRequestAbort = []
55 this.preClose = []
56}
57
58Hooks.prototype = Object.create(null)
59
60Hooks.prototype.validate = function (hook, fn) {
61 if (typeof hook !== 'string') throw new FST_ERR_HOOK_INVALID_TYPE()
62 if (Array.isArray(this[hook]) === false) {
63 throw new FST_ERR_HOOK_NOT_SUPPORTED(hook)
64 }
65 if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(fn))
66}
67
68Hooks.prototype.add = function (hook, fn) {
69 this.validate(hook, fn)
70 this[hook].push(fn)
71}
72
73function buildHooks (h) {
74 const hooks = new Hooks()
75 hooks.onRequest = h.onRequest.slice()
76 hooks.preParsing = h.preParsing.slice()
77 hooks.preValidation = h.preValidation.slice()
78 hooks.preSerialization = h.preSerialization.slice()
79 hooks.preHandler = h.preHandler.slice()
80 hooks.onSend = h.onSend.slice()
81 hooks.onResponse = h.onResponse.slice()
82 hooks.onError = h.onError.slice()
83 hooks.onRoute = h.onRoute.slice()
84 hooks.onRegister = h.onRegister.slice()
85 hooks.onTimeout = h.onTimeout.slice()
86 hooks.onRequestAbort = h.onRequestAbort.slice()
87 hooks.onReady = []
88 hooks.onListen = []
89 hooks.preClose = []
90 return hooks
91}
92
93function hookRunnerApplication (hookName, boot, server, cb) {
94 const hooks = server[kHooks][hookName]
95 let i = 0
96 let c = 0
97
98 next()
99
100 function exit (err) {
101 if (err) {
102 if (err.code === 'AVV_ERR_READY_TIMEOUT') {
103 err = appendStackTrace(err, new FST_ERR_HOOK_TIMEOUT(hookName))
104 } else {
105 err = AVVIO_ERRORS_MAP[err.code] != null
106 ? appendStackTrace(err, new AVVIO_ERRORS_MAP[err.code](err.message))
107 : err
108 }
109
110 cb(err)
111 return
112 }
113 cb()
114 }
115
116 function next (err) {
117 if (err) {
118 exit(err)
119 return
120 }
121
122 if (i === hooks.length && c === server[kChildren].length) {
123 if (i === 0 && c === 0) { // speed up start
124 exit()
125 } else {
126 // This is the last function executed for every fastify instance
127 boot(function manageTimeout (err, done) {
128 // this callback is needed by fastify to provide an hook interface without the error
129 // as first parameter and managing it on behalf the user
130 exit(err)
131
132 // this callback is needed by avvio to continue the loading of the next `register` plugins
133 done(err)
134 })
135 }
136 return
137 }
138
139 if (i === hooks.length && c < server[kChildren].length) {
140 const child = server[kChildren][c++]
141 hookRunnerApplication(hookName, boot, child, next)
142 return
143 }
144
145 boot(wrap(hooks[i++], server))
146 next()
147 }
148
149 function wrap (fn, server) {
150 return function (err, done) {
151 if (err) {
152 done(err)
153 return
154 }
155
156 if (fn.length === 1) {
157 try {
158 fn.call(server, done)
159 } catch (error) {
160 done(error)
161 }
162 return
163 }
164
165 try {
166 const ret = fn.call(server)
167 if (ret && typeof ret.then === 'function') {
168 ret.then(done, done)
169 return
170 }
171 } catch (error) {
172 err = error
173 }
174
175 done(err) // auto done
176 }
177 }
178}
179
180function onListenHookRunner (server) {
181 const hooks = server[kHooks].onListen
182 const hooksLen = hooks.length
183
184 let i = 0
185 let c = 0
186
187 next()
188
189 function next (err) {
190 err && server.log.error(err)
191
192 if (
193 i === hooksLen
194 ) {
195 while (c < server[kChildren].length) {
196 const child = server[kChildren][c++]
197 onListenHookRunner(child)
198 }
199 return
200 }
201
202 wrap(hooks[i++], server, next)
203 }
204
205 async function wrap (fn, server, done) {
206 if (fn.length === 1) {
207 try {
208 fn.call(server, done)
209 } catch (e) {
210 done(e)
211 }
212 return
213 }
214 try {
215 const ret = fn.call(server)
216 if (ret && typeof ret.then === 'function') {
217 ret.then(done, done)
218 return
219 }
220 done()
221 } catch (error) {
222 done(error)
223 }
224 }
225}
226
227function hookRunnerGenerator (iterator) {
228 return function hookRunner (functions, request, reply, cb) {
229 let i = 0
230
231 function next (err) {
232 if (err || i === functions.length) {
233 cb(err, request, reply)
234 return
235 }
236
237 let result
238 try {
239 result = iterator(functions[i++], request, reply, next)
240 } catch (error) {
241 cb(error, request, reply)
242 return
243 }
244 if (result && typeof result.then === 'function') {
245 result.then(handleResolve, handleReject)
246 }
247 }
248
249 function handleResolve () {
250 next()
251 }
252
253 function handleReject (err) {
254 if (!err) {
255 err = new FST_ERR_SEND_UNDEFINED_ERR()
256 }
257
258 cb(err, request, reply)
259 }
260
261 next()
262 }
263}
264
265function onResponseHookIterator (fn, request, reply, next) {
266 return fn(request, reply, next)
267}
268
269const onResponseHookRunner = hookRunnerGenerator(onResponseHookIterator)
270const preValidationHookRunner = hookRunnerGenerator(hookIterator)
271const preHandlerHookRunner = hookRunnerGenerator(hookIterator)
272const onTimeoutHookRunner = hookRunnerGenerator(hookIterator)
273const onRequestHookRunner = hookRunnerGenerator(hookIterator)
274
275function onSendHookRunner (functions, request, reply, payload, cb) {
276 let i = 0
277
278 function next (err, newPayload) {
279 if (err) {
280 cb(err, request, reply, payload)
281 return
282 }
283
284 if (newPayload !== undefined) {
285 payload = newPayload
286 }
287
288 if (i === functions.length) {
289 cb(null, request, reply, payload)
290 return
291 }
292
293 let result
294 try {
295 result = functions[i++](request, reply, payload, next)
296 } catch (error) {
297 cb(error, request, reply)
298 return
299 }
300 if (result && typeof result.then === 'function') {
301 result.then(handleResolve, handleReject)
302 }
303 }
304
305 function handleResolve (newPayload) {
306 next(null, newPayload)
307 }
308
309 function handleReject (err) {
310 if (!err) {
311 err = new FST_ERR_SEND_UNDEFINED_ERR()
312 }
313
314 cb(err, request, reply, payload)
315 }
316
317 next()
318}
319
320const preSerializationHookRunner = onSendHookRunner
321
322function preParsingHookRunner (functions, request, reply, cb) {
323 let i = 0
324
325 function next (err, newPayload) {
326 if (reply.sent) {
327 return
328 }
329
330 if (newPayload !== undefined) {
331 request[kRequestPayloadStream] = newPayload
332 }
333
334 if (err || i === functions.length) {
335 cb(err, request, reply)
336 return
337 }
338
339 let result
340 try {
341 result = functions[i++](request, reply, request[kRequestPayloadStream], next)
342 } catch (error) {
343 cb(error, request, reply)
344 return
345 }
346
347 if (result && typeof result.then === 'function') {
348 result.then(handleResolve, handleReject)
349 }
350 }
351
352 function handleResolve (newPayload) {
353 next(null, newPayload)
354 }
355
356 function handleReject (err) {
357 if (!err) {
358 err = new FST_ERR_SEND_UNDEFINED_ERR()
359 }
360
361 cb(err, request, reply)
362 }
363
364 next()
365}
366
367function onRequestAbortHookRunner (functions, request, cb) {
368 let i = 0
369
370 function next (err) {
371 if (err || i === functions.length) {
372 cb(err, request)
373 return
374 }
375
376 let result
377 try {
378 result = functions[i++](request, next)
379 } catch (error) {
380 cb(error, request)
381 return
382 }
383 if (result && typeof result.then === 'function') {
384 result.then(handleResolve, handleReject)
385 }
386 }
387
388 function handleResolve () {
389 next()
390 }
391
392 function handleReject (err) {
393 if (!err) {
394 err = new FST_ERR_SEND_UNDEFINED_ERR()
395 }
396
397 cb(err, request)
398 }
399
400 next()
401}
402
403function hookIterator (fn, request, reply, next) {
404 if (reply.sent === true) return undefined
405 return fn(request, reply, next)
406}
407
408module.exports = {
409 Hooks,
410 buildHooks,
411 hookRunnerGenerator,
412 preParsingHookRunner,
413 onResponseHookRunner,
414 onSendHookRunner,
415 preSerializationHookRunner,
416 onRequestAbortHookRunner,
417 hookIterator,
418 hookRunnerApplication,
419 onListenHookRunner,
420 preHandlerHookRunner,
421 preValidationHookRunner,
422 onRequestHookRunner,
423 onTimeoutHookRunner,
424 lifecycleHooks,
425 supportedHooks
426}