diff --git a/.gitignore b/.gitignore index b38069d..0941888 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ logs *.log npm-debug.log* - # Runtime data pids *.pid @@ -26,3 +25,7 @@ build/Release # Dependency directory # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git node_modules + +# Custom additions +.ssh +.log diff --git a/README.md b/README.md index a7801eb..bfa2716 100644 --- a/README.md +++ b/README.md @@ -1 +1,6 @@ rcn-frontserver +==== + +#About +This is the front-web-server of the Raspberry pi Communication network. +More details following soon. diff --git a/glob/script.js b/glob/script.js new file mode 100644 index 0000000..a6d78e9 --- /dev/null +++ b/glob/script.js @@ -0,0 +1,45 @@ +// -- Functions -- + +/** + * binds the data for each element + */ +function bindData(){ + let mods = $('model'); + mods.each(function(){ + let model = $(this); + if (model.attr('refresh-interval') != undefined) { + bindModelData(model); + setInterval(()=> { bindModelData(model) }, model.attr('refresh-interval')); + } + else bindModelData(model); + }); +} + +function bindModelData(model) { + $.get(model.attr('src'), function(data){ + $('*[model='+model.attr('name')+']').each(function() { + let elem = $(this); + elem.html(getDataByPath(JSON.parse(data), elem.attr('datapath'))); + }) + }); +} + +/** + * Returns the data of an array by accessing the given path-string + * @param {JSON-Object} array Contains the data. + * @param {String} path The path to the wanted data. + * @return {Object} Returns anything that the path points to. + */ +function getDataByPath(array, path) { + let data = array; + path.split('.').forEach(function(p){ + data = data[p]; + }); + return data; +} + +// -- Events -- +// Define what happens onLoad +$(document).ready(function(){ + bindData(); +}); diff --git a/glob/style.css b/glob/style.css new file mode 100644 index 0000000..e69de29 diff --git a/server.js b/server.js new file mode 100644 index 0000000..bb8ce5a --- /dev/null +++ b/server.js @@ -0,0 +1,202 @@ +// requirement +const https = require('https'), + fs = require('fs'), + urlparse = require('url'), + { JSDOM } = require('jsdom'), + perfy = require('perfy'), + winston = require('winston'), + DailyRotateFile = require('winston-daily-rotate-file'), +// args + args = require('args-parser')(process.argv), +// ressources + defaultCss = "/glob/style.css", + defaultJs = "/glob/script.js", +// logging config using winston + fileLoggingFormat = winston.format.printf(info => { + return `${info.timestamp} ${info.level.toUpperCase()}: ${JSON.stringify(info.message)}`; + }); + consoleLoggingFormat = winston.format.printf(info => { + return `${info.timestamp} [${info.level}] ${JSON.stringify(info.message)}`; + }); + loggingFullFormat = winston.format.combine( + winston.format.splat(), + winston.format.timestamp({ + format: 'MM-DD HH:mm:ss.SSS' + }), + fileLoggingFormat + ), + logger = winston.createLogger({ + level: winston.config.npm.levels, + format: loggingFullFormat, + transports: [ + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.splat(), + winston.format.timestamp({ + format: 'YY-MM-DD HH:mm:ss.SSS' + }), + consoleLoggingFormat + ), + level: args.loglevel || 'info' + }), + new winston.transports.File({ + level: 'debug', + filename: './.log/rcn-frontserver.log' + }), + new DailyRotateFile({ + level: 'verbose', + filename: './.log/frontserver-%DATE%.log', + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '32m', + maxFiles: '30d' + }) + ] + }), +// serveroptions + options = { + key: fs.readFileSync('.ssh/key.pem'), // the key-file + cert: fs.readFileSync('.ssh/cert.pem') // the certificate-file + }, + port = args.port || 80; // The port of the web server. + +// --- functional declaration part --- + +/** + * Creates a server running on port 80 with tls enabled. + * @return {boolean} Returns false if the server stopped by error. + */ +function main() { + try { + https.createServer(options, function(req, res) { + logger.verbose({'msg': 'Received request', 'method': req.method, 'url': req.url}); + + perfy.start('response-calculation'); // caluclate the response time + let url = urlparse.parse(req.url); // parse the url + let path = url.pathname; // set path to the urls pathname + logger.debug({msg: 'Got URL by using url package','url': url, 'path': path}); + + let [response, mime] = getResponse(path); // get a response for the url path + logger.debug({'response-length': response.length, 'mime-type': mime}); + + res.writeHead(200, {"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..."); + return false; + } +} + +/** + * returns the extension of a file for the given filename. + * @param {String} filename The name of the file. + * @return {String} A string that represents the file-extension. + */ +function getExtension(filename) { + try { + let exts = filename.match(/\.[a-z]+/g); // get the extension by using regex + if (exts) return exts[exts.length - 1]; // return the found extension + else return null; // return null if no extension could be found + } catch (error) { + logger.warn(error); + return null; + } +} + +/** + * Returns a string that depends on the path. + * @param {String} path Normally a file-name. Depending on the extension, an other root-path is choosen. + * @return {String} An Array containing (Either the files content or an error message) and the mime-type. + */ +function getResponse(path) { + logger.verbose({'msg':'calculationg response', 'path': path}); + try { + // get the file extension + let extension = getExtension(path); + // returns the home-html file if the path is root. + if (path == "/") return [fs.readFileSync("./glob/home.html"), "text/html"]; + // returns the global script or css if the extension is css or js and the root-path is glob. + if (path.includes("/glob") && extension == ".css" || getExtension(path) == ".js") { + if (extension == ".css") return [fs.readFileSync("." + path), "text/css"]; + else return [fs.readFileSync("." + path), "text/javascript"]; + } + // test the extension for differend file types. + switch (extension) { + case '.html': + case '.htm': + return [formatHtml(fs.readFileSync("./res/html/" + path)), "text/html"]; + case '.css': + return [fs.readFileSync("./res/css/"+path), "text/css"]; + case '.js': + return [fs.readFileSync("./res/scripts/"+path), "text/javascript"]; + case '.json': + return [fs.readFileSync("./res/data/"+path), "text/plain"]; + // return some images + case '.ico': + return [fs.readFileSync("./res/img/"+path), "image/x-icon"] + case '.jpg': + return [fs.readFileSync("./res/img/"+path), "image/jpeg"]; + case '.png': + return [fs.readFileSync("./res/img/"+path), "image/png"]; + // return some videos + case '.mp4': + return [fs.readFileSync("./res/vid/"+path), "video/mpeg"]; + // default return + default: + // if the extension is not in those above, the access is not allowed. + logger.verbose({'msg': 'Illegal request', 'path': path}) + return ["Not Allowed", "text/plain"]; + } + } catch (error) { + logger.error(error); + return "Error"; + } +} + +/** + * Formats the html string by adding a link to the standard css and to the standard javascript file. + * @param {String} htmlstring A string read from an html file or a html document string itself. + * @return {String} A html-string that represents a document. + */ +function formatHtml(htmlstring) { + logger.verbose({'msg': 'Formatting HTML', 'htmlstring': htmlstring}); + try { + let dom = new JSDOM(htmlstring); // creates a dom from the html string + let document = dom.window.document; + let head = document.getElementsByTagName('head')[0]; // gets the documents head + let css = document.createElement('link'); // create a link for the css embeding + css.setAttribute("rel", "stylesheet"); + css.setAttribute("type", "text/css"); + css.setAttribute("href", defaultCss); + head.prepend(css); // prepend the link to the head + let js = document.createElement('script'); // create a script for js embedding + js.setAttribute("type", "text/javascript"); + js.setAttribute("src", defaultJs); + head.prepend(js); // prepend the script to the head + let jquery = document.createElement('script'); // create a script for js embedding + jquery.setAttribute("type", "text/javascript"); + jquery.setAttribute("src", "/glob/jquery.js"); + head.prepend(jquery); // prepend the script to the head + return dom.serialize(); // return a string of the document + } catch(error) { + logger.error(error); + return htmlstring; + } +} + +// Executing the main function +if (typeof require != 'undefined' && require.main == module) { + logger.exceptions.handle( + new winston.transports.File({ + filename: './.log/frontserver-exceptions.log' + }) + ); + logger.info("Starting up... "); // log the current date so that the logfile is better to read. + main(); +}