The application is written for node.js using express.js and some other modules. Express is a framework used to create a server and router. The express directory hierarchy looks like this:
In the project root is the app.js file which looks like this:
// BLP 2017-03-07 -- This is a nodjs experment. It creates the // www.bartonlp.org:7000 webpage. "use strict"; const fs = require('fs'); const express = require('express'); const path = require('path'); const morgan = require('morgan'); const cookieParser = require('cookie-parser'); const bodyParser = require('body-parser'); const favicon = require('serve-favicon'); const count = require(path.join(__dirname, 'routes/utilfunctions.js')).count; const logger = require(path.join(__dirname, 'logger.js')); // get the routing unit const routes = require(path.join(__dirname, 'routes/index.js')); const app = express(); // view engine setup // NOTE we are using 'views.jade' which is the 'jade' implementation not // 'view' which uses 'pug'!! app.set('views', path.join(__dirname, 'views.jade')); app.set('view engine', 'jade'); //app.set('trust proxy', true); if(process.env.DEBUG == 'true') { // NOTE this is a string. app.use(morgan('combined')); } else { const stream = fs.createWriteStream(path.join(__dirname, 'access.log'), {flags: 'a'}); app.use(morgan('combined', {stream: stream})); } // Catch all app.use(function(req, res, next) { logger.debug("hostname: %s", req.hostname); let userAgent = req.get('User-Agent'); let re = /^.*(?:(msie\s*\d*)|(trident\/\s*\d*)).*$/i; let m = userAgent.match(re); if(m) { let which = m[1] ? m[1] : m[2]; if(m) { let msg = ` <!DOCTYPE html> <html> <head> <title>NO GOOD MSIE</title> </head> <body> <div style="background-color: red; color: white; padding: 10px;"> Your browser's <b>User Agent String</b> says it is:<br> ${m[0]}<br> Sorry you are using Microsoft's Broken Internet Explorer (${which}). </div> <div> <p>You should upgrade to Windows 10 and Edge if you must use MS-Windows.</p> <p>Better yet get <a href="https://www.google.com/chrome/"><b>Google Chrome</b></a> or <a href="https://www.mozilla.org/en-US/firefox/"><b>Mozilla Firefox</b>.</p></a> These two browsers will work with almost all previous versions of Windows and are very up to date.</p> <b>Better yet remove MS-Windows from your system and install Linux instead. Sorry but I just can not continue to support ancient versions of browsers.</b></p> </div> </body> </html>`; res.send(msg); return; } } if(req.hostname == 'www.bartonlp.org' || req.hostname == 'mynode.bartonlp.org') { next(); } else { logger.error("headers.host: %s\nHostname: %s\nOrg Url: %s, Url: %s\nERROR: Return", req.headers.host, req.hostname, req.originalUrl, req.url); next(new Error('Bad Route')); } }); /* * This goes before any router methods. This does the * counter and bots logic. */ app.use(function(req, res, next) { // Get the originalUrl and grab what is after the first / which will // be the name of the site jade file. count(req, function(err, cnt) { if(err) { next(err); return; } // Put the count into the req property as cnt. req.cnt = cnt; next(); }); }); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); // Set the document root to 'public' app.use(express.static(path.join(__dirname, 'public'))); app.use(favicon(path.join(__dirname, 'public', 'favicon.png'))); // favicon.png is a sprocket icon. // Now override this with the 'routes' to .../routes/index.js app.use('/', routes); // catch 404 and forward to error handler app.use(function(req, res, next) { logger.debug("req: %s", req.url); var err = new Error('Not Found'); err.status = 404; next(err); }); // development error handler // will print stacktrace // To remove development mode set the 'env' to blank. Uncomment to // disable development. //app.set('env', ''); if(app.get('env') === 'development') { // Error middle ware has 4 args! Must have 'next' even if not used. app.use(function(err, req, res, next) { res.status(err.status || 500); if(err.status != 404) { req.url = null; } res.render('error', { message: err.message, url: req.url, status: err.status, error: err }); }); } // production error handler // no stacktraces leaked to user app.use(function(err, req, res, next) { res.status(err.status || 500); if(err.status != 404) { req.url = null; } logger.error("MSG: %s\nURL: %s\nERROF: %s", err.message, req.url, err); res.render('error', { message: err.message, url: req.url, status: err.status, error: {} }); }); module.exports = app;
We load ./routes/index.js' and then we app.use('/', routes).
In the routes directory the file index.js does the routing for the application.
/* index.js * This is the 'router' module */ const express = require('express'); const router = express.Router(); const fs = require('fs'); const dns = require('dns'); const request = require('request'); const path = require('path'); const logger = require(path.join(__dirname, '../logger.js')); const utilfunctions = require(path.join(__dirname, "utilfunctions.js")); const query = utilfunctions.query; const count = utilfunctions.count; const mtime = utilfunctions.mtime; const run = utilfunctions.run; const robots = utilfunctions.robots; var args = { copyright: "2019 Barton Phillips", author: "Barton Phillips http://www.bartonphillips.com", address: "New Bern, North Carolina", desc: "node.js example", msg: "Counter Reset: Feb. 4, 2017", site: "Node", }; /* GET home page. */ router.get(['/','/index(\.(html|php))?'], function(req, res, next) { const port = req.headers.host.match(/:(.*)/)[1]; args.footer = req.cnt; return run(function *(resume) { try { var address = yield dns.lookup('bartonphillips.dyndns.org', resume); logger.debug("MY ADDRESS: %s", address); } catch(err) { logger.debug("NOT bartonphillips.dyndns.org"); } args.title = 'Node.js'; args.banner = "Node.js Page"; args.port = port; args.mtime = mtime(path.join(__dirname, "../views.jade/index.jade")); res.render('index', { args: args, }); }); }); /* GET howitworks */ router.get('/howitworks', function(req, res, next) { console.log("howitworks"); args.footer = req.cnt; return run(function *(resume) { try { var app = yield fs.readFile(path.join(__dirname, "../app.js"), resume); var index = yield fs.readFile(path.join(__dirname, "index.js"), resume); var views = yield fs.readFile(path.join(__dirname, "../views.jade/index.jade"), resume); var how = yield fs.readFile(path.join(__dirname, "../views.jade/howitworks.jade"), resume); var layout = yield fs.readFile(path.join(__dirname, "../views.jade/layout.jade"), resume); var utils = yield fs.readFile(path.join(__dirname, "utilfunctions.js"), resume); } catch(err) { return next(err); //new Error("Error: "+err)); } args.title = 'How It Works'; args.banner = 'How It Works Page'; args.mtime = mtime(path.join(__dirname, "../views.jade/howitworks.jade")); res.render('howitworks', { args: args, code1: app, code2: index, code3: utils, code4: views, code5: how, code6: layout, }); }); }); // Robots.txt router.get("/robots.txt", function(req, res, next) { robots(args.site, req, function(err, robottxt) { res.send(robottxt); }); }); // About website router.get('/aboutwebsite', function(req, res, next) { args.mtime = mtime(path.join(__dirname, "../views.jade/aboutwebsite.jade")); args.footer = req.cnt; args.title = 'About Website'; args.banner = 'About Website'; res.render('aboutwebsite', { args: args, }); }); // Make a restfull path router.get('/getdb/:ip', function(req, res, next) { args.mtime = mtime(path.join(__dirname, "../views.jade/getdb.jade")); args.footer = req.cnt; args.title = 'IP to Country'; args.banner = 'IP to Country'; var resorg = res; request.post("http://www.bartonlp.org/ipcountry.php", {json: true, form: {ip: req.params.ip}}, function(err, res, body) { if (!err && res.statusCode === 200) { if(body.name == "-") { body.name = "<span style='color: red'>was Not Found</span>"; } else { body.name = "is from the <b><i>"+body.name+"</i></b>"; } args.body = body; resorg.render('getdb', {args: args}); } }); }); module.exports = router;
The utilfunctions.js has almost all the rest of the logic needed. The actual connection
to the database is in a seperate file called createpool.js which only does a
const mysql = require('mysql'); const pool = mysql.createPool({ host: "localhost", user: "USERNAME", password: "PASSWORD", database: "DATABASENAME", });
Replace the UPPERCASE values with real mysql database information.
utilfunctions.js/* Utility Functions */ const mysql = require('mysql'); const fs = require('fs'); const path = require('path'); const logger = require(path.join(__dirname, '../logger.js')); const pool = require(path.join(__dirname, '../routes/createpool.js')).pool; // Do a mysql query const query = function(sql, value, cb) { if(typeof cb === 'undefined') { cb = value; value = null; } pool.getConnection(function(err, con) { if(err){ logger.debug('Error connecting to Db'); return cb(err); } // the query takes two aruments and a callback function. // sql is the sql statement // and value (if any) is the values for the sql statement is if has // the form 'select count from barton.counter where site=?' con.query(sql, value, function(err, result) { con.release(); //logger.debug("RESULT", result); return cb(err, result); }); }); } exports.query = query; // run is a little helper // 'gen' is a function iterator function run(gen) { // These two are the same. One uses normal function(...) format and the other // uses the new '=>' notation var iter = gen(function(err, data) { if(err) { iter.throw(err); } else { iter.next(data); } }); iter.next(); // We run gen() it returns an object to iter not a value. // Instead the flow goes directly to the iter.next() which initalizes // the iterator. // When the // (iter = gen((err, data) => (err && iter.throw(err)) || // iter.next(data))).next(); } // Count the number of hits. Bots and Bots2 exports.count = function(req, cb) { var site = req.originalUrl.match(/^\/applitec/i) == null ? 'Node' : 'Applitec'; var file = req.originalUrl.match(/^.*?(\/.*)$/)[1]; // This is the syntax for a generator. That is a function that has // 'yield' statements in it. // function* (resume) return run(function* (resume) { // resume looks like: // function(err, data) { // if(err) { // iter.throw(err); // } else { // iter.next(data); // } // } try { var agent = req.headers['user-agent']; var ip = req.ip.replace(/::.*?:/, ''); var sql = "insert into counter (filename, site, ip, agent, count, lasttime) "+ "values(?, ?, ?, ?, 1, now()) "+ "on duplicate key update site=?, ip=?, agent=?, count=count+1, lasttime=now()"; var value = [ file, site, ip, agent, site, ip, agent ]; var result = yield query(sql, value, resume); sql = "select count from barton.counter where site=? and filename=?"; value = [ site, file ]; var result = yield query(sql, value, resume); var cnt = result[0].count; sql = "select count(*) as one from information_schema.tables "+ "where (table_schema = 'barton') and (table_name = 'bots')"; var ok = (yield query(sql, resume))[0].one; //ok = ok[0].one; if(ok == 1) { var x = yield query("select ip from barton.bots where ip='"+ip+"'", resume); var isBot = x.length == 0 ? false: true; var isBot = agent.match(/\+*http:\/\/|Googlebot-Image|python|java|wget|nutch|perl|libwww|lwp-trivial|curl|PHP\/|urllib|GT::WWW|Snoopy|MFC_Tear_Sample|HTTP::Lite|PHPCrawl|URI::Fetch|Zend_Http_Client|http client|PECL::HTTP/i) || isBot; if(isBot) { try { // Bots has primary key(ip, agent) var result = yield query("insert into barton.bots (ip, agent, count, robots, who, creation_time, lasttime) "+ "values(?, ?, 1, 4, ?, now(), now())", [ip, agent, site], resume); } catch(err) { if(err.errno == 1062) { // duplicate key var who = (yield query("select who from barton.bots where ip=? and agent=?", [ip, agent], resume))[0].who; if(!who) { who = site; } if(who.indexOf(site) == -1) { who += ', ' + site; } result = yield query("update barton.bots set robots=robots | 8, who=?, "+ "count=count+1, lasttime=now() "+ "where ip=? and agent=?", [who, ip, agent], resume); } } } sql = "select count(*) as one from information_schema.tables "+ "where (table_schema = 'barton') and (table_name = 'bots2')"; var ok = (yield query(sql, resume))[0].one; if(ok == 1) { if(isBot) { // Bots2 has primary key (ip, agent, date, site, which) result = yield query("insert into barton.bots2 (ip, agent, date, site, which, count, lasttime) "+ "values(?, ?, current_date(), ?, 2, 1, now()) "+ "on duplicate key update count=count+1, lasttime=now()", [ip, agent, site], resume); } } } sql = "select count(*) as one from information_schema.tables "+ "where (table_schema = 'barton') and (table_name = 'tracker')"; var ok = (yield query(sql, resume))[0].one; //ok = ok[0].one; if(ok == 1) { var java = 0; if(isBot) { java = 0x2000; // This is the robots tag } // tracker key is id result = yield query("insert into barton.tracker (site, page, ip, agent, starttime, isJavaScript, lasttime) "+ "values(?, ?, ?, ?, now(), ?, now())", [site, file, ip, agent, java], resume); //$this->LAST_ID = $this->getLastInsertId(); } sql = "select count(*) as one from information_schema.tables "+ "where (table_schema = 'barton') and (table_name = 'daycounts')"; var ok = (yield query(sql, resume))[0].one; //ok = ok[0].one; if(ok == 1) { if(isBot) { var bots = 1, real = 0; } else { real = 1, bots = 0; } sql = "insert into barton.daycounts (site, `date`, `real`, bots, members, visits, lasttime) "+ "values(?, now(), ?, ?, 0, 1, now()) " + "on duplicate key update `real`=`real`+?, bots=bots+?, visits=visits+1, lasttime=now()"; result = yield query(sql, [site, real, bots, real, bots], resume); } return cb(null, cnt); } catch(err) { return cb(err); } }); } // Get last modified time exports.mtime = function(pugfile) { // we are using the views.jade which has .jade not .pug files // Note: mtime has '<date> GMT-nnnn (PDT)' and we want just the (PDT) // after the GMT-nnnn var stat = String(new Date(fs.statSync(pugfile).mtime)); //logger.debug("statSync: ", stat); return stat.replace(/GMT.*? /, ''); } exports.run = run; //function(gen, iter) { // (iter = gen((err, data) => (err && iter.throw(err)) || iter.next(data))).next(); //} exports.robots = function(site, req, cb) { var agent = req.headers['user-agent']; var ip = req.ip.replace(/::.*?:/, ''); return run(function* (resume) { try { var result = yield query("insert into barton.bots (ip, agent, count, robots, who, creation_time, lasttime) "+ "values(?, ?, 1, 1, ?, now(), now())", [ip, agent, site], resume); } catch(err) { if(err.errno == 1062) { // dup key var who = (yield query("select who from barton.bots where ip=? and agent=?", [ip, agent], resume))[0].who; if(!who) { who = site; } if(who.indexOf(site) == -1) { who += ', ' + site; } result = yield query("update barton.bots set robots=robots | 2, who=?, "+ "count=count+1, lasttime=now() "+ "where ip=? and agent=?", [who, ip, agent], resume); } } try { var robotsFile = site+".robots.txt"; var robottxt = yield fs.readFile(path.join(__dirname, robotsFile), 'utf8', resume); return cb(null, robottxt); } catch(err) { return cb(err); } }); }
The views directory has the main page and the howitworks.jade page
index.jade:extends layout.jade block link link(rel="canonical" href="https://www.bartonphillips.com") block stylesheets style. #show {width: 20rem; margin: auto;} #show img {width: 100%;} block jscripts // Because the images are done via java script the only code that needs to be added is here in the jade file. The dobanner function is from ximage.js and puts its code in #show below. script(src="https://bartonphillips.net/js/random.js") script(src="https://bartonphillips.net/js/yimage.js") script dobanner("Pictures/PasoRobles2013/*.JPG", {mode: 'rand'}) block content p. This page is on port #{args.port} and is written using <b>node.js</b> with <b>express.js</b>. p. While this may not seem like a wonderful thing it is interesting. I have used <b>express</b> as the framework. For details on the implementation <a href="howitworks">How It Works</a>. // The #show div is where the image goes from dobanner(). #showhowitworks.jade:
extends layout.jade append jscripts script(src="https://bartonphillips.net/js/syntaxhighlighter.js") script. jQuery(document).ready(function($) { $("pre").addClass("brush: js"); $(".nopre").removeClass("brush: js"); }); block stylesheets link(rel="stylesheet" href="https://bartonphillips.net/css/theme.css") style. .syntaxhighlighter { height: 10rem; font-size: .8rem !important; } .nopre { font-size: .8rem; border-left: .5rem solid gray; padding-left: .5rem; } block content p. The application is written for <b>node.js</b> using <b>express.js</b> and some other modules. Express is a framework used to create a server and router. The <b>express</b> directory hierarchy looks like this: ul li Project root ul li bin li node_modules li public li routes li views p. In the project root is the <b>app.js</b> file which looks like this: pre #{code1} p. We load <b>./routes/index.js'</b> and then we <b>app.use('/', routes)</b>. p. In the <b>routes</b> directory the file <b>index.js</b> does the routing for the application. pre #{code2} p. The <b>utilfunctions.js</b> has almost all the rest of the logic needed. The actual connection to the database is in a seperate file called <b>createpool.js</b> which only does a <br> pre(class='nopre'). const mysql = require('mysql'); const pool = mysql.createPool({ host: "localhost", user: "USERNAME", password: "PASSWORD", database: "DATABASENAME", }); p. Replace the UPPERCASE values with real mysql database information. b utilfunctions.js pre #{code3} p. The <b>views</b> directory has the main page and the <b>howitworks.jade</b> page b index.jade: pre #{code4} b howitworks.jade: pre #{code5} p. Finally <b>layout.jade</b> looks like this b layout.jade: pre #{code6} block footer
Finally layout.jade looks like this
layout.jade:doctype html html head title #{args.title} meta(name=viewport content="width=device-width, initial-scale=1") meta(charset='utf-8') meta(name="copyright" content="#{args.copyright}") meta(name="Author" content="#{args.author}") meta(name="description" content="#{args.desc}") meta(name="keywords" content="Barton Phillips") link(rel="stylesheet" href="https://bartonphillips.net/css/blp.css" title="blp default") block link style. .red {color: red;} .center {text-align: center;} #hitCounter { margin-left: auto; margin-right: auto; width: 50%; text-align: center; } #hitCountertbl { font-size: 1em; width: 0; border: 8px ridge yellow; margin-left: auto; margin-right: auto; background-color: #F5DEB3 } #hitCountertr { width: 0; border: 8px ridge yellow; margin-left: auto; margin-right: auto; background-color: #F5DEB3 } #hitCounterth { color: rgb(123, 16, 66); } block stylesheets script(src="https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js") block jscripts body header div a(href="/") img(id='blpimg' src="https://bartonphillips.net/images/blp-image.png" width='90' height-'120' alt="Barton's Picture") a(href="http://linuxcounter.net/") img(id='linuxcounter' src="https://bartonphillips.net/images/146624.png" width='190' height='110' alt="linux counter image.") h1.center #{args.banner} hr block content footer hr block footer |Return to a.underline(href="/") Home Page h2.center: a(href="/aboutwebsite") About This Site #counter div #{args.msg} #hitCounter table#hitCountertbl tr#hitCountertr th#hitCounterth #{args.footer} #address address. Copyright © #{args.copyright} <br> Barton Phillips, #{args.address} <br> Last Modified: #{args.mtime}