From 0f4c0de2d8c8a8be1a59e6d9917d1e54d4171019 Mon Sep 17 00:00:00 2001 From: Julius Riegel Date: Fri, 12 Oct 2018 16:09:45 +0200 Subject: [PATCH] Statuscodes and Cleanups - Removed caching_dump.json - Added cleanup function that is called on process exit. - .cache directory will be deleted on cleanup - The server can now respond with error 403 or 404 depending on the request - Changed exit in testing mode to 10 seconds --- config/caching_dump.json | 3 -- config/server.json | 88 +++++++++++++++++++++------------------- glob/errors/403.html | 12 ++++++ glob/errors/404.html | 12 ++++++ glob/home.html | 8 ---- lib/caching.js | 27 ++++++++---- lib/preprocessor.js | 51 +++++++++++------------ lib/utils.js | 34 ++++++++++++++++ package.json | 4 +- server.js | 46 +++++++++++++-------- 10 files changed, 177 insertions(+), 108 deletions(-) delete mode 100644 config/caching_dump.json create mode 100644 glob/errors/403.html create mode 100644 glob/errors/404.html delete mode 100644 glob/home.html diff --git a/config/caching_dump.json b/config/caching_dump.json deleted file mode 100644 index 0db3279..0000000 --- a/config/caching_dump.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - -} diff --git a/config/server.json b/config/server.json index e423323..5fcc35d 100644 --- a/config/server.json +++ b/config/server.json @@ -1,44 +1,48 @@ { - "routes": { - ".html": { - "path": "./res/html/", - "mime": "text/html" - }, - ".htm": { - "path": "./res/html/", - "mime": "text/html" - }, - ".js": { - "path": "./res/scripts/", - "mime": "text/javascript" - }, - ".css": { - "path": "./res/css/", - "mime": "text/css" - }, - ".json": { - "path": "./res/data/", - "mime": "text/plain" - }, - ".ico": { - "path": "./res/img/", - "mime": "image/x-icon" - }, - ".jpg": { - "path": "./res/img/", - "mime": "image/jpeg" - }, - ".png": { - "path": "./res/img/", - "mime": "image/png" - }, - ".ttf": { - "path": "./res/fonts/", - "mime": "font/opentype" - }, - ".sass" :{ - "path": "./res/sass", - "mime": "style/css" - } - } + "errors": { + "403": "./glob/errors/403.html", + "404": "./glob/errors/404.html" + }, + "routes": { + ".html": { + "path": "./res/html/", + "mime": "text/html" + }, + ".htm": { + "path": "./res/html/", + "mime": "text/html" + }, + ".js": { + "path": "./res/scripts/", + "mime": "text/javascript" + }, + ".css": { + "path": "./res/css/", + "mime": "text/css" + }, + ".json": { + "path": "./res/data/", + "mime": "text/plain" + }, + ".ico": { + "path": "./res/img/", + "mime": "image/x-icon" + }, + ".jpg": { + "path": "./res/img/", + "mime": "image/jpeg" + }, + ".png": { + "path": "./res/img/", + "mime": "image/png" + }, + ".ttf": { + "path": "./res/fonts/", + "mime": "font/opentype" + }, + ".sass": { + "path": "./res/sass", + "mime": "style/css" + } + } } diff --git a/glob/errors/403.html b/glob/errors/403.html new file mode 100644 index 0000000..cf557e1 --- /dev/null +++ b/glob/errors/403.html @@ -0,0 +1,12 @@ + + + 403 - Forbidden + + +
+
+

403 - Forbidden

+

You are not allowed to access the requested ressource.

+ 403 + + diff --git a/glob/errors/404.html b/glob/errors/404.html new file mode 100644 index 0000000..b6eb135 --- /dev/null +++ b/glob/errors/404.html @@ -0,0 +1,12 @@ + + + 404 - Not Found + + +
+
+

404 - Not Found

+

The requested ressouce could not be found.

+ 404 + + diff --git a/glob/home.html b/glob/home.html deleted file mode 100644 index 4c5a8ad..0000000 --- a/glob/home.html +++ /dev/null @@ -1,8 +0,0 @@ - - - Home - - -

Welcome to the server

- - diff --git a/lib/caching.js b/lib/caching.js index b500c0d..a120d49 100644 --- a/lib/caching.js +++ b/lib/caching.js @@ -1,11 +1,8 @@ const fs = require("fs"), path = require("path"), - config_path = "./config/caching_dump.json", - cache_dump = JSON.parse(fs.readFileSync(config_path)), cache_dir = "./.cache"; let cache = {}; let logger = require("winston"); -if (cache_dump != null && cache_dump["last"] != null) cache = cache_dump["last"]; // read the data from the file dump /** * Sets the logger for logging @@ -89,10 +86,26 @@ exports.isCached = function (filename) { } /** - * A function that dumps the config into the config file after appending the cache to it. + * A function that clears the ./.cache directory */ exports.cleanup = function () { - logger.verbose("Dumping cache into cache_dump file"); - cache_dump["last"] = cache; // append the cache to the dump object - fs.writeFileSync(config_path, JSON.stringify(cache_dump)); // write the dump data to the file + deleteFolder(cache_dir); // delte the cache folder and it's contents +}; + +/** + * Deletes a folder with it's contents recursive + * @param {string} p the path to the folder + */ +var deleteFolder = function(p) { + if( fs.existsSync(p) ) { + fs.readdirSync(p).forEach(function(file,index){ // get the contents of the directory + var curPath = path.join(p, file); // create path by joining + if(fs.lstatSync(curPath).isDirectory()) { // recurse + deleteFolder(curPath); // call recursive + } else { // delete file + fs.unlinkSync(curPath); // delete file + } + }); + fs.rmdirSync(p); // delete directory + } }; diff --git a/lib/preprocessor.js b/lib/preprocessor.js index e6d0b03..a3a948f 100644 --- a/lib/preprocessor.js +++ b/lib/preprocessor.js @@ -28,34 +28,29 @@ exports.setLogger = function (newLogger) { * @return {String} The data that should be send */ exports.getProcessed = function (filename) { - try { - logger.debug("Processing File %s", filename); - let extension = utils.getExtension(filename); // use the utils function to get the files extension - let data = null; - if (caching.isCached(filename)) return caching.getCached(filename) // return the cached file if it exists - logger.debug("File is not cached. Processing..."); - switch (pp_config[extension]) { - case "sass": - logger.debug("Processing sass %s", filename); - data = Buffer.from(pp_sass.renderSync({ // use the sass preprocessor - file: filename - }).css).toString("utf-8"); - break; - case "html": - logger.debug("Processing html %s", filename); - data = pp_html.formatHtml(filename); // use the html-preprocessor - break; - default: - logger.debug("No processor found for %s. Returning data.", filename); - return fs.readFileSync(filename); // just read the data from the file - } - caching.cache(filename, data); // cache the file for faster access next time - logger.debug("Cached file %s", filename); - return data; // return the data - } catch (error) { - logger.error(error); - return "Processing Error"; + logger.debug("Processing File %s", filename); + let extension = utils.getExtension(filename); // use the utils function to get the files extension + let data = null; + if (caching.isCached(filename)) return caching.getCached(filename) // return the cached file if it exists + logger.debug("File is not cached. Processing..."); + switch (pp_config[extension]) { + case "sass": + logger.debug("Processing sass %s", filename); + data = Buffer.from(pp_sass.renderSync({ // use the sass preprocessor + file: filename + }).css).toString("utf-8"); + break; + case "html": + logger.debug("Processing html %s", filename); + data = pp_html.formatHtml(filename); // use the html-preprocessor + break; + default: + logger.debug("No processor found for %s. Returning data.", filename); + return fs.readFileSync(filename); // just read the data from the file } + caching.cache(filename, data); // cache the file for faster access next time + logger.debug("Cached file %s", filename); + return data; // return the data }; /** @@ -63,4 +58,4 @@ exports.getProcessed = function (filename) { */ exports.cleanup = function() { caching.cleanup(); -}; \ No newline at end of file +}; diff --git a/lib/utils.js b/lib/utils.js index d478594..f03b338 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -2,6 +2,8 @@ * A Series of utility functions */ +function noOp() {}; + /** * returns the extension of a file for the given filename. * @param {String} filename The name of the file. @@ -18,3 +20,35 @@ exports.getExtension = function (filename) { return null; } } + +/** + * lets you define a cleanup for your program exit + * @param {Function} callback the cleanup function + * @constructor + * @author CanyonCasa & Pier-Luc Gendreau on StackOverflow + */ +exports.Cleanup = function Cleanup(callback) { + + // attach user callback to the process event emitter + // if no callback, it will still exit gracefully on Ctrl-C + callback = callback || noOp; + process.on('cleanup',callback); + + // do app specific cleaning before exiting + process.on('exit', function () { + process.emit('cleanup'); + }); + + // catch ctrl+c event and exit normally + process.on('SIGINT', function () { + console.log('Ctrl-C...'); + process.exit(2); + }); + + //catch uncaught exceptions, trace, then exit normally + process.on('uncaughtException', function(e) { + console.log('Uncaught Exception...'); + console.log(e.stack); + process.exit(99); + }); +}; diff --git a/package.json b/package.json index b7950c7..d481c76 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "jsdom": "^12.2.0", "node-sass": "^4.9.3", "perfy": "^1.1.5", - "winston-daily-rotate-file": "^3.3.3", - "winston": "^3.1.0" + "winston": "^3.1.0", + "winston-daily-rotate-file": "^3.3.3" } } diff --git a/server.js b/server.js index c2fa3dc..ee4f9b6 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,8 @@ const fs = require('fs'), // own modules utils = require("./lib/utils"), prepro = require("./lib/preprocessor"), +// cleanup + clean = utils.Cleanup(cleanup), // args args = require('args-parser')(process.argv), // create an args parser // config file @@ -91,20 +93,18 @@ function main() { let uri = url.pathname; // set uri to the urls uriame logger.debug({"msg": 'Got URL by using url package', 'url': url, 'path': uri}); - let [response, mime] = getResponse(uri); // get a response for the url path - logger.debug({'response-length': response.length, 'mime-type': mime}); - - res.writeHead(200, {"Content-Type": mime || "text/plain"}); // write the mime as head + let resArray = getResponse(uri); // get a response for the url path + let response = resArray[0] || "", // the response + mime = resArray[1] || "text/plain", // the mime-type + statuscode = resArray[2] || 200; // the status code. 200 if not explicitly set + logger.debug({'response-length': response.length, 'mime-type': mime, 'code': statuscode}); + res.writeHead(statuscode, {"Content-Type": mime}); // write the mime as head res.end(response); // write the response let execTime = perfy.end('response-calculation').fullMilliseconds; // get the execution time logger.debug("Response-Time: " + execTime + " ms for " + req.url, "debug"); // log the execution time - }).listen(port); // server listens on port specified in the parameter } catch (error) { - logger.error(error); - logger.info("Shutting Down..."); - prepro.cleanup(); - winston.end(); + logger.error(error.message); process.exit(1); } } @@ -112,15 +112,15 @@ function main() { /** * Returns a string that depends on the uri It gets the data from the routes variable. * @param uri - * @return {string[]} An Array containing (Either the files content or an error message) and the mime-type. + * @return {string[]} An Array containing the files content, the mime type and sometimes the statuscode (if it is not 200) */ function getResponse(uri) { - if (!uri || uri === "/") uri = "/index.html"; // uri redirects to the index.html if it is not set or if it is root + let matches = uri.match(/(\/$|\/\w+$)/g); // matches when the url has no file extension or ends in a slash (/) + if (matches) uri += '/index.html'; // append index.html if there are matches logger.verbose({'msg': 'calculating response', 'path': uri}); - let gp = prepro.getProcessed; + let gp = prepro.getProcessed; // shorten the function name try { - // get the file extension - let extension = utils.getExtension(uri); + let extension = utils.getExtension(uri); // get the urls file-extension // returns the global script or css if the extension is css or js and the root-uriis glob. if (uri.includes("/glob") && (extension === ".sass" || extension === ".js")) { logger.verbose("Using global uri"); @@ -131,12 +131,16 @@ function getResponse(uri) { logger.verbose("Mount for uri is " + mount); let route = routes[extension]; // get the route from the extension json logger.verbose("Found route: " + JSON.stringify(route)); - if (!route) return ["Not Allowed", "text/plain"]; // return not allowed if no route was found + + if (!route) { + logger.warn(`No route found for ${uri}`); + return [gp(config.errors['403']), "text/html", 403]; // return not allowed if no route was found + } return [gp(mount || path.join(route["path"], uri)), route["mime"]]; // get processed output (done by preprocessor) } catch (error) { - logger.error(error); + logger.warn(error.message); if (args.test) process.exit(1); - return ["Error", "text/plain"]; + return [gp(config.errors['404']), "text/html", 404]; } } @@ -159,6 +163,12 @@ function getMount(uri) { return false; } +function cleanup() { + logger.info('Cleanup...'); + prepro.cleanup(); // cleanup the preprocessor + logger.end(); // let the logger finish all operations +} + // Executing the main function if (typeof require !== 'undefined' && require.main === module) { logger.exceptions.handle( @@ -174,7 +184,7 @@ if (typeof require !== 'undefined' && require.main === module) { } else protocoll = require('http'); // if no certs could be found start the server as http-server logger.info("Starting up... "); // log the current date so that the logfile is better to read. if (args.test) { - setTimeout(() => process.exit(0), 30000); + setTimeout(() => process.exit(0), 10000); // if in testing mode, exit after 10 seconds } main(); }