diff --git a/package-lock.json b/package-lock.json index 95eb88d..c58e3ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "tortilla-api", - "version": "0.1.1", + "version": "0.1.10", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2072,6 +2072,33 @@ "is-buffer": "1.1.6" } }, + "lambda-restify": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/lambda-restify/-/lambda-restify-2.0.5.tgz", + "integrity": "sha1-WxJXQwTyJe/PlHXCfXRgnpwgNGI=", + "requires": { + "assert-plus": "1.0.0", + "mime": "1.4.1", + "negotiator": "0.6.1", + "once": "1.4.0", + "restify-errors": "4.3.0", + "semver": "5.4.1", + "uuid": "3.1.0" + }, + "dependencies": { + "restify-errors": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/restify-errors/-/restify-errors-4.3.0.tgz", + "integrity": "sha1-7JDzCTTX8xGRNRgd/DA+ML5gGr4=", + "requires": { + "assert-plus": "1.0.0", + "lodash": "4.17.4", + "safe-json-stringify": "1.0.4", + "verror": "1.10.0" + } + } + } + }, "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", diff --git a/package.json b/package.json index 72ffdf4..09bbeb3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tortilla-api", - "version": "0.1.10", + "version": "0.1.11", "description": "Wrapper of restify and swaggerRestify for a quick and easy creation of REST API in node", "main": "index.js", "scripts": { @@ -43,6 +43,7 @@ "config": "^1.29.0", "express": "^4.16.2", "guid": "0.0.12", + "lambda-restify": "^2.0.5", "lodash": "^4.17.4", "path": "^0.12.7", "restify": "^6.3.4", diff --git a/src/server.js b/src/server.js index 9503feb..d735451 100644 --- a/src/server.js +++ b/src/server.js @@ -7,199 +7,208 @@ const swaggerParser = require('swagger-parser'); const config = require('config'); const bodyParser = require('body-parser'); const busboyBodyParcer = require('busboy-body-parser'); +const Server = require('lambda-restify').default; const exitCode = { - success: 0, - uncaughtError: 99, - startFailed: 999 + success: 0, + uncaughtError: 99, + startFailed: 999 }; const signals = [ - 'SIGINT', - 'SIGTERM', - 'SIGQUIT', - 'SIGHUP' + 'SIGINT', + 'SIGTERM', + 'SIGQUIT', + 'SIGHUP' ]; let logger; const create = async ({definition, events, wrapper, serverLogger, bodyParser, securityHandlers}) => { - const context = { - env: process.env, //Register run environment - config, - internal: { - definition: { - appRoot: __dirname, //This directory - port: get(config, 'port', 8080), //Default port - logger: get(definition, 'logger'), //Controller logger - ...definition //Overwrite the properties - }, - }, - events,//{onServerStart,afterStart} - wrapper, - bodyParser, - securityHandlers, - }; - - logger = { - debug: (get(serverLogger, 'debug', noop)).bind(serverLogger), - log: get(serverLogger, 'log', noop).bind(serverLogger), - info: get(serverLogger, 'info', noop).bind(serverLogger), - warn: get(serverLogger, 'warn', noop).bind(serverLogger), - error: get(serverLogger, 'error', noop).bind(serverLogger) - }; - - if (isNil(get(context, 'internal.definition.appRoot'))) throw new Error('The application appRoot cannot be undefined'); - - if (isNil(get(context, 'internal.definition.port'))) throw new Error('The application port cannot be undefined'); - - const timeout = get(context, 'internal.definition.terminateTimeout', 5000); - - const error = get(context, 'events.error', noop); - - try { - registerErrorHandler(error); //Register and event handler when there is an unhandled exception on the application - signals.forEach(sig => process.on(sig, onSignal(context, timeout))); //Register termination signals and how to handle them - await serverInit(context); //Initialise and start server - } catch (err) { - logger.error({err}, 'Service failed to start'); - process.exit(exitCode.startFailed); - } + const context = { + env: process.env, //Register run environment + config, + internal: { + definition: { + appRoot: __dirname, //This directory + port: get(config, 'port', 8080), //Default port + logger: get(definition, 'logger'), //Controller logger + ...definition //Overwrite the properties + }, + }, + events,//{onServerStart,afterStart} + wrapper, + bodyParser, + securityHandlers, + }; + + logger = { + debug: (get(serverLogger, 'debug', noop)).bind(serverLogger), + log: get(serverLogger, 'log', noop).bind(serverLogger), + info: get(serverLogger, 'info', noop).bind(serverLogger), + warn: get(serverLogger, 'warn', noop).bind(serverLogger), + error: get(serverLogger, 'error', noop).bind(serverLogger) + }; + + if (isNil(get(context, 'internal.definition.appRoot'))) throw new Error('The application appRoot cannot be undefined'); + + if (isNil(get(context, 'internal.definition.port'))) throw new Error('The application port cannot be undefined'); + + const timeout = get(context, 'internal.definition.terminateTimeout', 5000); + + const error = get(context, 'events.error', noop); + + try { + registerErrorHandler(error); //Register and event handler when there is an unhandled exception on the application + signals.forEach(sig => process.on(sig, onSignal(context, timeout))); //Register termination signals and how to handle them + await serverInit(context); //Initialise and start server + } catch (err) { + logger.error({err}, 'Service failed to start'); + process.exit(exitCode.startFailed); + } }; const serverInit = async (context) => { - logger.info(`Starting API At Port ${context.internal.definition.port}`); + logger.info(`Starting API At Port ${context.internal.definition.port}`); - await get(context, 'events.onServerStart', Promise.resolve)(context);//Execute Event If Any + await get(context, 'events.onServerStart', Promise.resolve)(context);//Execute Event If Any - await loadSwaggerYaml(context);//Load swagger definition - createServer(context); // Create restify server - await swaggerize(context); //Load option for swagger configuration - await listen(context); //Start Server + await loadSwaggerYaml(context);//Load swagger definition + createServer(context); // Create restify server + await swaggerize(context); //Load option for swagger configuration + await listen(context); //Start Server - await get(context, 'events.afterStart', Promise.resolve)(context);//Execute Event If Any + await get(context, 'events.afterStart', Promise.resolve)(context);//Execute Event If Any - return get(context, ['restify', 'server']); + return get(context, ['restify', 'server']); }; const loadSwaggerYaml = async (context) => { - const appRoot = context.internal.definition.appRoot; - const swaggerPath = path.join(appRoot, 'api/swagger/swagger.yaml'); - const swaggerDefinition = await swaggerParser.dereference(swaggerPath); //Load swagger file - set(context, 'swagger.definition', swaggerDefinition); //Load definition to current context - global.swaggerDefinition = swaggerDefinition; + const appRoot = context.internal.definition.appRoot; + const swaggerPath = path.join(appRoot, 'api/swagger/swagger.yaml'); + const swaggerDefinition = await swaggerParser.dereference(swaggerPath); //Load swagger file + set(context, 'swagger.definition', swaggerDefinition); //Load definition to current context + global.swaggerDefinition = swaggerDefinition; }; const createServer = (context) => { - logger.debug('Creating restify server'); - const server = restify.createServer(); - server.on('uncaughtException', (req, res, route, err) => { //Register application exception handler - logger.error({route, err}, 'An unhandled exception has occurred'); - res.send(500, 'An internal error has occurred.'); - }); - server.use(bodyParser.json(get(context, 'bodyParser', {}))); - server.use(busboyBodyParcer()); - server.use(setLogger(context)); - get(context, 'events.middleware', []).map(middleware => server.use(middleware)); //Register server middleware - server.use(mapWrapperProperties(context)); //Register middleware for wrapper use - set(context, 'restify.server', server); ////Load server to current context + logger.debug('Creating restify server'); + let server; + + if (get(config, 'lambda', false)) { + server = new Server(); + } else { + server = restify.createServer(); + } + + + server.on('uncaughtException', (req, res, route, err) => { //Register application exception handler + logger.error({route, err}, 'An unhandled exception has occurred'); + res.send(500, 'An internal error has occurred.'); + }); + server.use(bodyParser.json(get(context, 'bodyParser', {}))); + server.use(busboyBodyParcer()); + server.use(setLogger(context)); + get(context, 'events.middleware', []).map(middleware => server.use(middleware)); //Register server middleware + server.use(mapWrapperProperties(context)); //Register middleware for wrapper use + set(context, 'restify.server', server); ////Load server to current context }; const setLogger = context => (req, res, next) => { - req.logger = get(context, 'internal.definition.logger', noop()); - next(); + req.logger = get(context, 'internal.definition.logger', noop()); + next(); }; const mapWrapperProperties = context => (req, res, next) => { - req.wrapperProperties = get(context, 'wrapper.props', noop); - res.errorHandler = get(context, 'wrapper.errorHandler'); - next(); + req.wrapperProperties = get(context, 'wrapper.props', noop); + res.errorHandler = get(context, 'wrapper.errorHandler'); + next(); }; //Create swagger configuration const swaggerize = async (context) => { - logger.debug('Loading swagger definition'); - const create = Promise.promisify(swaggerRestify.create); + logger.debug('Loading swagger definition'); + const create = Promise.promisify(swaggerRestify.create); - const fittingsPath = path.resolve(`${__dirname}/fittings`); - const swaggerConfig = require(`${__dirname}/config/defaultSwaggerConfig.json`); //Load default config - castArray(get(swaggerConfig, 'fittingsDirs', [])).push(fittingsPath); + const fittingsPath = path.resolve(`${__dirname}/fittings`); + const swaggerConfig = require(`${__dirname}/config/defaultSwaggerConfig.json`); //Load default config + castArray(get(swaggerConfig, 'fittingsDirs', [])).push(fittingsPath); - const cors = get(context, 'env.NODE_ENV', '').toLowerCase() === 'local_dev' ? true : false;//get value for cors + const cors = get(context, 'env.NODE_ENV', '').toLowerCase() === 'local_dev' ? true : false;//get value for cors - set(swaggerConfig, 'bagpipes._preflight.cors', cors); //Set value for cors + set(swaggerConfig, 'bagpipes._preflight.cors', cors); //Set value for cors - const options = { - ...swaggerConfig, - ...get(context, 'config.swagger', {}), - swagger: context.swagger.definition, - appRoot: context.internal.definition.appRoot, - securityHandlers: get(context, 'securityHandlers'), - }; + const options = { + ...swaggerConfig, + ...get(context, 'config.swagger', {}), + swagger: context.swagger.definition, + appRoot: context.internal.definition.appRoot, + securityHandlers: get(context, 'securityHandlers'), + }; - const swagger = await create(options); - logger.debug('Swagger definition loaded, registering routes with restify server'); - swagger.register(context.restify.server); - set(context, ['swagger', 'server'], swagger); + const swagger = await create(options); + logger.debug('Swagger definition loaded, registering routes with restify server'); + swagger.register(context.restify.server); + set(context, ['swagger', 'server'], swagger); }; const listen = async (context) => { - logger.info('Starting restify server'); - const port = context.internal.definition.port; - const server = context.restify.server; - const listen = Promise.promisify(server.listen, {context: server}); - await listen(port); - logger.info(`API started, now listening on ${port}`); + logger.info('Starting restify server'); + const port = context.internal.definition.port; + const server = context.restify.server; + const listen = Promise.promisify(server.listen, {context: server}); + await listen(port); + logger.info(`API started, now listening on ${port}`); }; //Register and event handler when there is an unhandled exception on the application const registerErrorHandler = (callback) => { - function unhandledError(err) { - logger.error({err: err}, 'An unhandled error has occurred'); //Log Error - return Promise.resolve( - (async () => { - try { - await callback(err); //Attempt to use call back to handle error - } catch (err) {//If there is an exception - logger.error({err}, 'Exception handler failed'); - } finally { - process.exit(exitCode.uncaughtError); //Terminate a - } - })()); - } - - //Map the event types - //https://nodejs.org/api/process.html#process_event_unhandledrejection - process.on('uncaughtException', unhandledError); - process.on('unhandledRejection', unhandledError); + function unhandledError(err) { + logger.error({err: err}, 'An unhandled error has occurred'); //Log Error + return Promise.resolve( + (async () => { + try { + await callback(err); //Attempt to use call back to handle error + } catch (err) {//If there is an exception + logger.error({err}, 'Exception handler failed'); + } finally { + process.exit(exitCode.uncaughtError); //Terminate a + } + })()); + } + + //Map the event types + //https://nodejs.org/api/process.html#process_event_unhandledrejection + process.on('uncaughtException', unhandledError); + process.on('unhandledRejection', unhandledError); }; const onSignal = (context, timeout) => async () => { - logger.info('Starting API Termination'); - - const timeoutEvent = async () => { - await Promise.delay(timeout); - logger.warn('API termination is waiting too long to finish'); - }; - - const onTerminate = async () => { - try { - await get(context, 'events.onTerminate', Promise.resolve)(context); - logger.info('API terminated successfully'); - } catch (err) { - logger.error({err}, 'An error occurred in terminate handler'); - } - }; - - const events = [ - onTerminate(), - timeoutEvent() - ]; - - await Promise.race(events); - process.exit(exitCode.success); + logger.info('Starting API Termination'); + + const timeoutEvent = async () => { + await Promise.delay(timeout); + logger.warn('API termination is waiting too long to finish'); + }; + + const onTerminate = async () => { + try { + await get(context, 'events.onTerminate', Promise.resolve)(context); + logger.info('API terminated successfully'); + } catch (err) { + logger.error({err}, 'An error occurred in terminate handler'); + } + }; + + const events = [ + onTerminate(), + timeoutEvent() + ]; + + await Promise.race(events); + process.exit(exitCode.success); };