UNPKG

fastify

Version:

Fast and low overhead web framework, for Node.js

427 lines (361 loc) 9.01 kB
'use strict' const applicationHooks = [ 'onRoute', 'onRegister', 'onReady', 'onListen', 'preClose', 'onClose' ] const lifecycleHooks = [ 'onTimeout', 'onRequest', 'preParsing', 'preValidation', 'preSerialization', 'preHandler', 'onSend', 'onResponse', 'onError', 'onRequestAbort' ] const supportedHooks = lifecycleHooks.concat(applicationHooks) const { FST_ERR_HOOK_INVALID_TYPE, FST_ERR_HOOK_INVALID_HANDLER, FST_ERR_SEND_UNDEFINED_ERR, FST_ERR_HOOK_TIMEOUT, FST_ERR_HOOK_NOT_SUPPORTED, AVVIO_ERRORS_MAP, appendStackTrace } = require('./errors') const { kChildren, kHooks, kRequestPayloadStream } = require('./symbols') function Hooks () { this.onRequest = [] this.preParsing = [] this.preValidation = [] this.preSerialization = [] this.preHandler = [] this.onResponse = [] this.onSend = [] this.onError = [] this.onRoute = [] this.onRegister = [] this.onReady = [] this.onListen = [] this.onTimeout = [] this.onRequestAbort = [] this.preClose = [] } Hooks.prototype = Object.create(null) Hooks.prototype.validate = function (hook, fn) { if (typeof hook !== 'string') throw new FST_ERR_HOOK_INVALID_TYPE() if (Array.isArray(this[hook]) === false) { throw new FST_ERR_HOOK_NOT_SUPPORTED(hook) } if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(fn)) } Hooks.prototype.add = function (hook, fn) { this.validate(hook, fn) this[hook].push(fn) } function buildHooks (h) { const hooks = new Hooks() hooks.onRequest = h.onRequest.slice() hooks.preParsing = h.preParsing.slice() hooks.preValidation = h.preValidation.slice() hooks.preSerialization = h.preSerialization.slice() hooks.preHandler = h.preHandler.slice() hooks.onSend = h.onSend.slice() hooks.onResponse = h.onResponse.slice() hooks.onError = h.onError.slice() hooks.onRoute = h.onRoute.slice() hooks.onRegister = h.onRegister.slice() hooks.onTimeout = h.onTimeout.slice() hooks.onRequestAbort = h.onRequestAbort.slice() hooks.onReady = [] hooks.onListen = [] hooks.preClose = [] return hooks } function hookRunnerApplication (hookName, boot, server, cb) { const hooks = server[kHooks][hookName] let i = 0 let c = 0 next() function exit (err) { if (err) { if (err.code === 'AVV_ERR_READY_TIMEOUT') { err = appendStackTrace(err, new FST_ERR_HOOK_TIMEOUT(hookName)) } else { err = AVVIO_ERRORS_MAP[err.code] != null ? appendStackTrace(err, new AVVIO_ERRORS_MAP[err.code](err.message)) : err } cb(err) return } cb() } function next (err) { if (err) { exit(err) return } if (i === hooks.length && c === server[kChildren].length) { if (i === 0 && c === 0) { // speed up start exit() } else { // This is the last function executed for every fastify instance boot(function manageTimeout (err, done) { // this callback is needed by fastify to provide an hook interface without the error // as first parameter and managing it on behalf the user exit(err) // this callback is needed by avvio to continue the loading of the next `register` plugins done(err) }) } return } if (i === hooks.length && c < server[kChildren].length) { const child = server[kChildren][c++] hookRunnerApplication(hookName, boot, child, next) return } boot(wrap(hooks[i++], server)) next() } function wrap (fn, server) { return function (err, done) { if (err) { done(err) return } if (fn.length === 1) { try { fn.call(server, done) } catch (error) { done(error) } return } try { const ret = fn.call(server) if (ret && typeof ret.then === 'function') { ret.then(done, done) return } } catch (error) { err = error } done(err) // auto done } } } function onListenHookRunner (server) { const hooks = server[kHooks].onListen const hooksLen = hooks.length let i = 0 let c = 0 next() function next (err) { err && server.log.error(err) if ( i === hooksLen ) { while (c < server[kChildren].length) { const child = server[kChildren][c++] onListenHookRunner(child) } return } wrap(hooks[i++], server, next) } async function wrap (fn, server, done) { if (fn.length === 1) { try { fn.call(server, done) } catch (e) { done(e) } return } try { const ret = fn.call(server) if (ret && typeof ret.then === 'function') { ret.then(done, done) return } done() } catch (error) { done(error) } } } function hookRunnerGenerator (iterator) { return function hookRunner (functions, request, reply, cb) { let i = 0 function next (err) { if (err || i === functions.length) { cb(err, request, reply) return } let result try { result = iterator(functions[i++], request, reply, next) } catch (error) { cb(error, request, reply) return } if (result && typeof result.then === 'function') { result.then(handleResolve, handleReject) } } function handleResolve () { next() } function handleReject (err) { if (!err) { err = new FST_ERR_SEND_UNDEFINED_ERR() } cb(err, request, reply) } next() } } function onResponseHookIterator (fn, request, reply, next) { return fn(request, reply, next) } const onResponseHookRunner = hookRunnerGenerator(onResponseHookIterator) const preValidationHookRunner = hookRunnerGenerator(hookIterator) const preHandlerHookRunner = hookRunnerGenerator(hookIterator) const onTimeoutHookRunner = hookRunnerGenerator(hookIterator) const onRequestHookRunner = hookRunnerGenerator(hookIterator) function onSendHookRunner (functions, request, reply, payload, cb) { let i = 0 function next (err, newPayload) { if (err) { cb(err, request, reply, payload) return } if (newPayload !== undefined) { payload = newPayload } if (i === functions.length) { cb(null, request, reply, payload) return } let result try { result = functions[i++](request, reply, payload, next) } catch (error) { cb(error, request, reply) return } if (result && typeof result.then === 'function') { result.then(handleResolve, handleReject) } } function handleResolve (newPayload) { next(null, newPayload) } function handleReject (err) { if (!err) { err = new FST_ERR_SEND_UNDEFINED_ERR() } cb(err, request, reply, payload) } next() } const preSerializationHookRunner = onSendHookRunner function preParsingHookRunner (functions, request, reply, cb) { let i = 0 function next (err, newPayload) { if (reply.sent) { return } if (newPayload !== undefined) { request[kRequestPayloadStream] = newPayload } if (err || i === functions.length) { cb(err, request, reply) return } let result try { result = functions[i++](request, reply, request[kRequestPayloadStream], next) } catch (error) { cb(error, request, reply) return } if (result && typeof result.then === 'function') { result.then(handleResolve, handleReject) } } function handleResolve (newPayload) { next(null, newPayload) } function handleReject (err) { if (!err) { err = new FST_ERR_SEND_UNDEFINED_ERR() } cb(err, request, reply) } next() } function onRequestAbortHookRunner (functions, request, cb) { let i = 0 function next (err) { if (err || i === functions.length) { cb(err, request) return } let result try { result = functions[i++](request, next) } catch (error) { cb(error, request) return } if (result && typeof result.then === 'function') { result.then(handleResolve, handleReject) } } function handleResolve () { next() } function handleReject (err) { if (!err) { err = new FST_ERR_SEND_UNDEFINED_ERR() } cb(err, request) } next() } function hookIterator (fn, request, reply, next) { if (reply.sent === true) return undefined return fn(request, reply, next) } module.exports = { Hooks, buildHooks, hookRunnerGenerator, preParsingHookRunner, onResponseHookRunner, onSendHookRunner, preSerializationHookRunner, onRequestAbortHookRunner, hookIterator, hookRunnerApplication, onListenHookRunner, preHandlerHookRunner, preValidationHookRunner, onRequestHookRunner, onTimeoutHookRunner, lifecycleHooks, supportedHooks }