diff --git a/.gitignore b/.gitignore index 8d87b1d..5cba732 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules/* +/services/* +package-lock.json diff --git a/index.js b/index.js new file mode 100644 index 0000000..e9dc30b --- /dev/null +++ b/index.js @@ -0,0 +1,9 @@ +const Server = require('./src/server'); +require('colors'); + +let server = new Server({ + scanInterval: 2000, + port: 8999 +}); + +server.start(); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..6f4f242 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "pd.serv-con", + "version": "1.0.0", + "description": "Service Container for easy use", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "server", + "node", + "express", + "service" + ], + "author": "Niclas Thobaben", + "license": "ISC", + "dependencies": { + "colors": "^1.4.0", + "express": "^4.17.1" + } +} diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000..f9f5739 --- /dev/null +++ b/src/server.js @@ -0,0 +1,101 @@ +const fs = require('fs'); +const path = require('path'); +const url = require('url'); + +const express = require('express'); + +const service_loader = require('./service-loader'); + +module.exports = class ServConServer { + + constructor(opts){ + this._port = opts.port || 80; + this._scanInterval = opts.scanInterval || 20000; + this._scanTask = null; + this._running = false; + this._loaded = {}; + this._router = null; + this.serviceDirectory = opts.directory || path.join(process.cwd(), 'services'); + this.app = express(); + this.app.use((req, res, next) => this._dispatch(req, res, next)); + this.server = null; + } + + start(){ + if(!this._running){ + this._scanTask = setInterval(() => this._scanServices(), this._scanInterval); + this._scanServices(); + this.server = this.app.listen(this._port); + this._running = true; + } + } + + stop(){ + if(this._running){ + clearInterval(this._scanTask); + this._scanTask = null; + this.server.close(); + this._running = false; + } + } + + _loadService(fname, service_path){ + console.log("Load Service",fname, service_path); + service_loader.loadService(service_path, service => { + this._loaded[fname] = service; + }); + } + + _scanServices(){ + console.log('scan...'); + fs.readdir(this.serviceDirectory, (err, files) => { + if(err){ + console.log('error while scanning services', err); + }else { + files.forEach(file => { + let name = file.match(/^([a-z0-9\-\_]+)\.[0-9a-z]+$/i)[1]; + let service_path = path.join(this.serviceDirectory, file); + if(this._loaded[name]){ + fs.stat(service_path, (err, stats) => { + if(err){ + console.error(err); + }else if(stats.mtimeMs > this._loaded[name].modMs) { + this._loadService(name, service_path); + } + }); + }else { + this._loadService(name, service_path); + } + }); + } + }); + } + + _logRequest(req, res){ + let time = process.hrtime(); + let msg = `[${req.method.magenta}] ${req.url.magenta} "${req.header('user-agent')}"@${req.ip}`; + res.on('finish', () => { + let elapsed = process.hrtime(time); + let reqTime = Math.ceil(elapsed[0] * 1000 + elapsed[1] / 1e6) + ''; + console.debug(`${msg} [${reqTime.green}] ms`); + }); + } + + _dispatch(req, res, next){ + this._logRequest(req, res); + let req_url = url.parse(req.url); + let req_url_tokens = req_url.pathname.split('/'); + let service_name = req_url_tokens[1]; + let service = this._loaded[service_name]; + if(service){ + let path_tokens_splice = req_url_tokens.splice(2); + let new_path = '/'; + path_tokens_splice.forEach(t => new_path += `${t}/`); + req.url = `${new_path}${req_url.search || ''}`; + service.router(req, res, next); + }else { + res.sendStatus(404); + } + } + +} \ No newline at end of file diff --git a/src/service-loader.js b/src/service-loader.js new file mode 100644 index 0000000..b3aa0e0 --- /dev/null +++ b/src/service-loader.js @@ -0,0 +1,38 @@ +const fs = require('fs'); +const path = require('path'); + + +function loadJS(file, callback){ + fs.readFile(file, { encoding: 'utf-8' }, (err, data) => { + if(err){ + console.error(err); + }else { + let service = eval(data); + callback(service); + } + }); +} + + +module.exports.loadService = (file, callback) => { + let file_ext = file.match(/\.[0-9a-z]+$/i)[0]; + fs.exists(file, exists => { + if(exists){ + fs.stat(file, (err, stats) => { + if(err){ + console.error(err); + }else { + let modtime = stats.mtimeMs; + let cb = service => { + service = service || {}; + service.modMs = modtime; + callback(service); + } + switch(file_ext){ + case '.js': loadJS(file, cb); break; + } + } + }); + } + }); +} \ No newline at end of file