Skip to content

Instantly share code, notes, and snippets.

@Jimbly
Last active March 13, 2019 17:38
Show Gist options
  • Select an option

  • Save Jimbly/d996bd8c80ae1a376a0b to your computer and use it in GitHub Desktop.

Select an option

Save Jimbly/d996bd8c80ae1a376a0b to your computer and use it in GitHub Desktop.

Revisions

  1. Jimbly revised this gist Nov 29, 2014. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions webroot.js
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,7 @@
    // Companion Blog Post about architecture: http://jimbesser.wordpress.com/2014/10/20/its-node-js-all-the-way-down/
    // Companion Blog Post about dealing with legacy requests: http://jimbesser.wordpress.com/2014/11/29/the-horrible-things-peoples-routers-do-to-my-packets/
    // Companion Blog Post about architecture:
    // http://jimbesser.wordpress.com/2014/10/20/its-node-js-all-the-way-down/
    // Companion Blog Post about dealing with legacy requests:
    // http://jimbesser.wordpress.com/2014/11/29/the-horrible-things-peoples-routers-do-to-my-packets/
    //
    // Routing handled by this app:
    // [www.]bigscreensmallgames.com -> static site: /var/data/smb_web/bigscreensmallgames.com/
  2. Jimbly revised this gist Nov 29, 2014. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion webroot.js
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,5 @@
    // Companion Blog Post: http://jimbesser.wordpress.com/2014/10/20/its-node-js-all-the-way-down/
    // Companion Blog Post about architecture: http://jimbesser.wordpress.com/2014/10/20/its-node-js-all-the-way-down/
    // Companion Blog Post about dealing with legacy requests: http://jimbesser.wordpress.com/2014/11/29/the-horrible-things-peoples-routers-do-to-my-packets/
    //
    // Routing handled by this app:
    // [www.]bigscreensmallgames.com -> static site: /var/data/smb_web/bigscreensmallgames.com/
  3. Jimbly revised this gist Oct 21, 2014. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions webroot.js
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,6 @@
    // Companion Blog Post: http://jimbesser.wordpress.com/2014/10/20/its-node-js-all-the-way-down/
    //
    // Routing handled by this app:
    // [www.]bigscreensmallgames.com -> static site: /var/data/smb_web/bigscreensmallgames.com/
    // fanime.info -> node app running entire site on port 4001
    // [default site]/app1: replace URL and redirect to single page app on 192.168.0.127:21022
  4. Jimbly revised this gist Oct 11, 2014. 1 changed file with 19 additions and 6 deletions.
    25 changes: 19 additions & 6 deletions webroot.js
    Original file line number Diff line number Diff line change
    @@ -31,13 +31,14 @@ function openNewLogFile() {
    openNewLogFile();
    setInterval(openNewLogFile, 12*60*60*1000);

    process.on('uncaughtException', function (e) {
    // Absolutely required, httpProxy throws uncaught socket hang-up exceptions
    // whenever proxying to another server which disconnects a socket (e.g. if you
    // you make a socket.io connection to the wrong endpoint).
    function handleError(e) {
    log('ERROR: ' + (e.stack || e));
    console.error('ERROR', new Date(), e);
    });
    }
    // Absolutely required, httpProxy throws uncaught socket hang-up exceptions
    // whenever proxying to another server which disconnects a socket (e.g. if you
    // you make a socket.io connection to the wrong endpoint).
    process.on('uncaughtException', handleError);

    function staticSite(app, dir) {
    app = app || express();
    @@ -109,7 +110,19 @@ vhost_app.use(main);
    vhost_ws.use(main_ws);


    var server = http.createServer(vhost_app);
    var server = http.createServer(function (req, res) {
    // Re-map a url with a host into the format express/vhost logic needs
    if (!req.headers.host && req.url.indexOf('://') !== -1) {
    log('Remapping legacy request with no host for ' + req.url);
    var parsed = url.parse(req.url);
    req.headers.host = parsed.host;
    req.url = parsed.path + (parsed.hash || '');
    }
    // Need to attach this error handler because morgan implicitly adds an error
    // handler which squashes this error from ever being seen!
    req.socket.on('error', handleError);
    vhost_app(req, res);
    });
    server.on('upgrade', function (req, socket, head) {
    // Use the same express.Router logic for vhost mapping of the upgrade request
    vhost_ws(req, { socket: socket, head: head }, function () {
  5. Jimbly revised this gist Oct 11, 2014. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions webroot.js
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,6 @@

    // [www.]bigscreensmallgames.com -> static site: /var/data/smb_web/bigscreensmallgames.com/
    // fanime.info -> node app running entire site on port 4001
    // [default site]/app1: replace URL and redirect to single page app on port 4002
    // [default site]/app1: replace URL and redirect to single page app on 192.168.0.127:21022
    // [default site] -> static site: /var/data/smb_web/dashingstrike.com/

    var express = require('express');
  6. Jimbly created this gist Oct 11, 2014.
    157 changes: 157 additions & 0 deletions webroot.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,157 @@

    // [www.]bigscreensmallgames.com -> static site: /var/data/smb_web/bigscreensmallgames.com/
    // fanime.info -> node app running entire site on port 4001
    // [default site]/app1: replace URL and redirect to single page app on port 4002
    // [default site] -> static site: /var/data/smb_web/dashingstrike.com/

    var express = require('express');
    var fs = require('fs');
    var http = require('http');
    var httpProxy = require('http-proxy');
    var morgan = require('morgan');
    var serveIndex = require('serve-index');
    var serveStatic = require('serve-static');
    var through = require('through');
    var vhost = require('vhost');

    var out_stream = through();
    out_stream.pipe(process.stdout);

    function log(msg) {
    out_stream.write('**LOG: ' + new Date().toISOString() + ' ' + msg + '\n');
    }

    var last_log_file;
    function openNewLogFile() {
    var filename = '/var/log/webroot.' + new Date().toISOString().slice(0, 13).replace(/\:/g, '_').replace('T', '_') + '.log';
    last_log_file && last_log_file.close();
    last_log_file = fs.createWriteStream(filename, { flags: 'a', mode: 0664 });
    out_stream.pipe(last_log_file);
    log('Opened new log file: ' + filename);
    }
    openNewLogFile();
    setInterval(openNewLogFile, 12*60*60*1000);

    process.on('uncaughtException', function (e) {
    // Absolutely required, httpProxy throws uncaught socket hang-up exceptions
    // whenever proxying to another server which disconnects a socket (e.g. if you
    // you make a socket.io connection to the wrong endpoint).
    log('ERROR: ' + (e.stack || e));
    console.error('ERROR', new Date(), e);
    });

    function staticSite(app, dir) {
    app = app || express();
    app.use(serveStatic('/var/data/smb_web/' + dir + '/'));
    app.use(serveIndex('/var/data/smb_web/' + dir + '/', { icons: true, view: 'details' }));
    return app;
    }

    function wsProxy(proxy) {
    return function (req, data) {
    log('Proxying ws upgrade request for ' + req.headers.host + ' ' + (req.originalUrl || req.url));
    proxy.ws(req, data.socket, data.head);
    };
    }

    function webProxy(proxy) {
    return proxy.web.bind(proxy);
    }

    function directoryize(app, dir) {
    // redirect requests for /foo to /foo/ so that relative paths don't get messed up
    app.get('/' + dir, function (req, res, next) {
    if (req.url !== '/' + dir) {
    return next();
    }
    res.redirect('/' + dir + '/');
    });
    }

    // Sub sites running as separate processes
    var proxy_fanimeinfo = httpProxy.createProxyServer({
    target: { host: 'localhost', port: 4001 },
    agent: http.globalAgent, // passing agent to prevent connection: close from being added
    xfwd: true, // add x-forwarded-for header so we get the real IP
    });

    var proxy_app1 = httpProxy.createProxyServer({
    target: { host: '192.168.0.127', port: 21022 },
    agent: http.globalAgent, // passing agent to prevent connection: close from being added
    xfwd: true, // add x-forwarded-for header so we get the real IP
    });

    var main = express();
    var main_ws = express.Router();

    directoryize(main, 'app1');
    main.use('/app1', webProxy(proxy_app1));
    main_ws.use('/app1', wsProxy(proxy_app1));

    staticSite(main, 'dashingstrike.com');

    // Root vhost app
    var vhost_app = express();
    var vhost_ws = express.Router();

    // Logging
    vhost_app.use(morgan(':remote-addr [:date] ":method :req[host]:url" START ":referrer" ":user-agent"', { stream: out_stream }));
    vhost_app.use(morgan(':remote-addr [:date] ":method :req[host]:url" FINISH :status :res[content-length] :response-time ms', { stream: out_stream }));

    // Directory mapped virtual hosts
    vhost_app.use(vhost(/(?:www\.)?bigscreensmallgames\.com/, staticSite(null, 'bigscreensmallgames.com')));

    // Virtual hosts mapping to other node apps running as separate processes
    vhost_app.use(vhost(/(?:www\.)?fanime\.info/, webProxy(proxy_fanimeinfo)));
    vhost_ws.use(vhost(/(?:www\.)?fanime\.info/, wsProxy(proxy_fanimeinfo)));

    // Default - dashingstrike.com and variants, anything unknown, etc
    vhost_app.use(main);
    vhost_ws.use(main_ws);


    var server = http.createServer(vhost_app);
    server.on('upgrade', function (req, socket, head) {
    // Use the same express.Router logic for vhost mapping of the upgrade request
    vhost_ws(req, { socket: socket, head: head }, function () {
    log('No one to proxy websocket to for ' + req.headers.host + ' ' + req.url);
    });
    });

    // Add intercept to remove null-termination from requests from libGlov's fetch.cpp
    server.on('connection', function (s) {
    var orig_ondata = s.ondata;
    var is_legacy = false;
    var call_count = 0;
    s.ondata = function (d, start, end) {
    ++call_count;
    if (call_count === 1) {
    var head = d.slice(start, Math.min(end, start + 11)).toString();
    is_legacy = (head === 'GET http://');
    if (is_legacy && d.slice(end-2, end).toString()==='\n\0') {
    // Remove null termination that will cause a HTTP parser error
    log('Stripping null from null terimianted request buffer');
    end = end - 1;
    s.ondata = orig_ondata; // Remove hook
    }
    } else if (call_count === 2) {
    // assert.ok(is_legacy);
    if (end - start <= 2 && d[end-1] === 0) {
    log('Stripping null from now empty request buffer');
    end = end - 1;
    }
    s.ondata = orig_ondata; // Remove hook
    }
    if (!is_legacy) {
    s.ondata = orig_ondata; // Remove hook
    }
    orig_ondata(d, start, end);
    };
    });


    // I gave the node binary privileges to listen on this port by running:
    // $ sudo setcap cap_net_bind_service=+ep `which node`
    var port = 80;
    server.listen(port);
    log('Started webroot on port ' + port + ', process id ' + process.pid);