From fe1a21f222ef07b08d1da5b277c955a502409394 Mon Sep 17 00:00:00 2001 From: velvettear Date: Tue, 15 Mar 2022 01:36:47 +0100 Subject: [PATCH] code optimization --- config.json | 13 +++--- libs/commands.js | 92 ++++++++++++++++++++++++++++++------- libs/logger.js | 115 +++++++++++++++++++++++++++++++++-------------- libs/server.js | 93 ++++++++++++++++++++++++-------------- remex.js | 48 +++++++++++++++++--- 5 files changed, 264 insertions(+), 97 deletions(-) diff --git a/config.json b/config.json index 61d2413..896adf9 100644 --- a/config.json +++ b/config.json @@ -1,22 +1,21 @@ { "server": { "listen": "0.0.0.0", - "port": 3000, - "timestamp": "DD.MM.YYYY HH:mm:ss:SS" + "port": 3000 }, "log": { - "level": "debug" + "level": "debug", + "timestamp": "DD.MM.YYYY HH:mm:ss:SS" }, "api": [ { "url": "/uptime", "type": "get", - "command": "uptime123", + "command": "uptime1", "args": [ - "-V", - "-p" ], - "passargs": true + "passargs": true, + "detach": false } ] } \ No newline at end of file diff --git a/libs/commands.js b/libs/commands.js index 2631a9c..7889214 100644 --- a/libs/commands.js +++ b/libs/commands.js @@ -1,27 +1,85 @@ -const logger = require('../libs/logger.js'); +const logger = require('./logger.js'); const { spawn } = require('child_process') -function execute(endpoint) { - if (!endpoint || !endpoint.command) { - logger.warn('no command defined'); +const cmds = new Map(); + +async function execute(endpoint) { + if (endpoint === undefined) { return; } - logger.debug('executing command \'' + endpoint.command + '\' with args \'' + endpoint.args + '\'...'); - var cmd = spawn(endpoint.command, endpoint.args); - cmd.stdout.on('data', function(data) { - logger.debug(data); - }); - cmd.stderr.on('data', function(data) { - logger.error(data); - }); - cmd.on('close', function(code) { - logger.debug('command \'' + endpoint.command + '\' with args \'' + endpoint.args + '\' finished with exit code ' + code); - }); - cmd.on('error', function(err) { - logger.error('command \'' + endpoint.command + '\' with args \'' + endpoint.args + '\' encountered an error >>> ' + err); + return new Promise((resolve, reject) => { + logger.info('executing command \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\')...'); + var cmd = spawn(endpoint.command, endpoint.args); + cmd.timestamp = new Date().getTime(); + let result = ''; + let error = ''; + cmd.stdout.on('data', (data) => { + result += data; + }); + cmd.stderr.on('data', (data) => { + error += data; + }); + cmd.on('spawn', () => { + logger.info('spawned command \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\')'); + addCommand(cmd, endpoint); + if (endpoint.detach !== false) { + resolve(); + } + }); + cmd.on('error', (err) => { + error += err; + removeCommand(endpoint); + if (endpoint.detach !== false) { + reject(); + } + }); + cmd.on('close', (code) => { + removeCommand(endpoint); + let fn = logger.info; + let msg = 'command \'' + endpoint.command + '\' (args: \'' + endpoint.args + '\') finished with exit code ' + code + ' after ' + (new Date().getTime() - cmd.timestamp) + 'ms'; + if (error !== undefined && error.length > 0) { + msg += ' >>> ' + error; + fn = logger.error; + reject(error); + } + if (result !== undefined && result.length > 0) { + msg += ' > ' + result; + } + fn(msg); + resolve(result); + }); }); } +function addCommand(command, endpoint) { + if (command === undefined || endpoint === undefined) { + return; + } + cmds.set(JSON.stringify(endpoint), command); +} + +function removeCommand(endpoint) { + if (endpoint === undefined) { + return; + } + cmds.delete(JSON.stringify(endpoint)); +} + +function isCommandActive(endpoint) { + return endpoint !== undefined && cmds.has(JSON.stringify(endpoint)); +} + +function killCommand(endpoint) { + if (endpoint === undefined) { + return; + } + const command = cmds.get(JSON.stringify(endpoint)); + if (command === undefined) { + return; + } + process.kill(command.pid); +} + module.exports = { execute } \ No newline at end of file diff --git a/libs/logger.js b/libs/logger.js index bdbdcfa..bfb436b 100644 --- a/libs/logger.js +++ b/libs/logger.js @@ -1,48 +1,80 @@ -const config = require("../config.json"); -const moment = require("moment"); +const moment = require('moment'); // constants -const LOG_PREFIX_DEBUG = "debug"; -const LOG_PREFIX_INFO = "info"; -const LOG_PREFIX_WARNING = "warning"; -const LOG_PREFIX_ERROR = "error"; +const LOG_PREFIX_DEBUG = 'debug'; +const LOG_PREFIX_INFO = 'info'; +const LOG_PREFIX_WARNING = 'warning'; +const LOG_PREFIX_ERROR = 'error'; const LOGLEVEL_DEBUG = 0; const LOGLEVEL_INFO = 1; const LOGLEVEL_WARNING = 2; const LOGLEVEL_ERROR = 3; -// set loglevel on "require" -const loglevel = function() { - switch (config.log.level) { +let loglevel; +let timestamp; + +initialize(); + +function initialize(loglevel, timestamp) { + setLogLevel(loglevel || global.config?.log?.level); + setTimestamp(timestamp || global.config?.log?.timestamp); +} + +// set the loglevel +function setLogLevel(value) { + switch (value) { case LOG_PREFIX_DEBUG: case LOGLEVEL_DEBUG: - return LOGLEVEL_DEBUG; + loglevel = LOGLEVEL_DEBUG; + break; case LOG_PREFIX_INFO: case LOGLEVEL_INFO: - return LOGLEVEL_INFO; + loglevel = LOGLEVEL_INFO; + break; case LOG_PREFIX_WARNING: case LOGLEVEL_WARNING: - return LOGLEVEL_WARNING; + loglevel = LOGLEVEL_WARNING; + break; case LOG_PREFIX_ERROR: case LOGLEVEL_ERROR: - return LOGLEVEL_ERROR; + loglevel = LOGLEVEL_ERROR; + break; default: - return LOGLEVEL_INFO; + loglevel = LOGLEVEL_INFO; + break; } -}(); +} -// log a http request -function request(request) { - let message = "[" + request.method + "] url: \"" + request.url + "\""; +// get the timestamp format +function setTimestamp(value) { + timestamp = value || 'DD.MM.YYYY HH:mm:ss:SS'; +} + +// log a http request - response object +function http(object) { + if (object === undefined) { + return; + } + let message = '[' + object.request.method + ':' + object.code + '] url: \'' + object.request.url + '\''; let counter = 1; - for (let param in request.body) { - message += ", parameter " + counter + ": \"" + param + "=" + request.body[param] + "\""; + for (let param in object.request.body) { + message += ', parameter ' + counter + ': \'' + param + '=' + object.request.body[param] + '\''; counter++; } + if (object.request.timestamp) { + message += ' > ' + (new Date().getTime() - object.request.timestamp) + 'ms'; + } + if (object.data) { + message += ' > data: ' + object.data; + } + if (object.code != 200) { + error(message.trim()); + return; + } debug(message.trim()); } -// prefix log with "info" +// prefix log with 'info' function info(message) { if (loglevel > LOGLEVEL_INFO) { return; @@ -50,28 +82,42 @@ function info(message) { trace(message); } -// prefix log with "info" +// prefix log with 'info' function warn(message) { if (loglevel > LOGLEVEL_WARNING) { return; } - trace(message, "warning"); + trace(message, 'warning'); } -// prefix log with "debug" +// prefix log with 'debug' function debug(message) { if (loglevel > LOGLEVEL_DEBUG) { return; } - trace(message, "debug"); + trace(message, 'debug'); } -// prefix log with "error" +// prefix log with 'error' function error(message) { if (loglevel > LOGLEVEL_ERROR) { return; } - trace(message, "error"); + if (message.stack) { + trace(message.stack, 'error'); + return; + } + if (message.errors !== undefined) { + for (let index = 0; index < message.errors.length; index++) { + trace(message.errors[index], 'error'); + } + return; + } + if (message.message) { + trace(message.message, 'error'); + return; + } + trace(message, 'error'); } // default logging function @@ -80,31 +126,32 @@ function trace(message, prefix) { return; } if (prefix === undefined || prefix === null || prefix.length === 0) { - prefix = "info"; + prefix = 'info'; } let print; switch (prefix) { - case "error": + case 'error': print = console.error; break; - case "debug": + case 'debug': print = console.debug; break; - case "warning": + case 'warning': print = console.warn; break; default: print = console.log; } - message = moment().format(config.server.timestamp) + " | " + prefix + " > " + message; + message = moment().format(timestamp) + ' | ' + prefix + ' > ' + message; print(message); } // exports module.exports = { + initialize, info, warn, debug, error, - request -}; \ No newline at end of file + http +} \ No newline at end of file diff --git a/libs/server.js b/libs/server.js index c3606c3..9365466 100644 --- a/libs/server.js +++ b/libs/server.js @@ -1,50 +1,75 @@ -const config = require('../config.json'); const logger = require('../libs/logger.js'); const commands = require('../libs/commands.js'); const http = require('http'); +const { time } = require('console'); -var server; -var api; +let server; +let api; -function start(callback) { +async function start() { + const listen = global.config?.server?.listen || '0.0.0.0'; + const port = global.config?.server?.port; buildAPI(); - if (!server) { + if (server === undefined) { server = http.createServer(); } - server.listen(config.server.port, config.server.listen) - .on('listening', function () { - logger.debug('server listening on ' + config.server.listen + ':' + config.server.port + '...'); - handleRequests(); - }); -} - -function handleRequests() { - server.on('request', function (request, response) { - logger.request(request); - var endpoint = api.get(request.url); - if (!endpoint) { - response.writeHead(501); - response.end('endpoint not defined\n'); - return; - } - if (request.method.toLowerCase() != (endpoint.type.toLowerCase())) { - response.writeHead(405); - response.end('endpoint does not support ' + request.method + ' requests\n'); - return; - } - commands.execute(endpoint); - response.end(); + return new Promise((resolve, reject) => { + server.listen(port, listen) + .on('listening', function () { + logger.debug('server listening on ' + config.server.listen + ':' + config.server.port + '...'); + handleRequests(); + resolve(); + }); }); } -function buildAPI(callback) { - if (!config.api) { - logger.warn('no api defined'); +function handleRequests() { + server.on('request', (request, response) => { + request.timestamp = new Date().getTime(); + respond(request, response, api.get(request.url)); + }); +} + +async function respond(request, response, endpoint) { + if (response === undefined) { + return; + } + let data = { + status: 'ok' + }; + let code = 200; + if (endpoint === undefined) { + code = 501; + data.status = 'error'; + data.msg = 'endpoint not defined'; + } else if (request.method.toLowerCase() !== (endpoint.type.toLowerCase())) { + code = 405; + data.status = 'error'; + data.msg = 'endpoint does not support ' + request.method + ' requests'; + } else { + try { + data.result = await commands.execute(endpoint); + } catch (err) { + code = 501; + data.status = 'error'; + data.msg = err; + } + } + const json = JSON.stringify(data); + response.writeHead(code); + response.end(json); + logger.http({ request: request, code: code, data: data }); +} + +function buildAPI() { + const apiConfig = global.config?.api; + if (apiConfig === undefined || apiConfig.length === 0) { + throw new Error('no api endpoints configured - aborting'); } api = new Map(); - config.api.forEach(function(endpoint) { - var url = endpoint.url; - var tmp = endpoint; + config.api.forEach(function (endpoint) { + let url = endpoint.url; + let tmp = endpoint; delete tmp.url; api.set(url, tmp); }); diff --git a/remex.js b/remex.js index 946acb1..49ba37c 100644 --- a/remex.js +++ b/remex.js @@ -1,11 +1,49 @@ const logger = require('./libs/logger.js'); const packageJSON = require('./package.json'); -const server = require('./libs/server.js') +const server = require('./libs/server.js'); +const path = require('path'); -logger.info("launching " + packageJSON.name + " " + packageJSON.version) -server.start(function(err) { +const INTERRUPTS = ['beforeExit', 'SIGINT', 'SIGTERM']; + +main(); + +async function main() { + let configPath = path.resolve('./config.json'); + try { + global.config = require(configPath); + } catch (err) { + logger.warn('could not read config file at \'' + configPath + '\''); + } + logger.initialize(); + handleExit(); + logger.info("launching " + packageJSON.name + " " + packageJSON.version + "..."); + try { + await server.start(); + } catch (err) { + exit(err); + } +}; + +function handleExit() { + for (var index = 0; index < INTERRUPTS.length; index++) { + process.on(INTERRUPTS[index], async (code) => { + exit(undefined, code); + }); + } +} + +function exit(err, code) { + if (code === undefined) { + code = 0; + if (err !== undefined) { + code = 1; + } + } if (err) { logger.error(err); - process.exit(1); + logger.error(packageJSON.name + ' ' + packageJSON.version + ' ended due to an error'); + } else { + logger.info(packageJSON.name + ' ' + packageJSON.version + ' shutting down gracefully') } -}); \ No newline at end of file + process.exit(code); +} \ No newline at end of file