diff --git a/lib/vhost-sni-server.js b/lib/vhost-sni-server.js index 6dfe5f2..3fc41c2 100644 --- a/lib/vhost-sni-server.js +++ b/lib/vhost-sni-server.js @@ -10,6 +10,100 @@ var connect = require('connect'); var vhost = require('vhost'); module.exports.create = function (securePort, certsPath, vhostsdir) { + function getDomainInfo(apppath) { + var parts = apppath.split(/[#%]+/); + var hostname = parts.shift(); + var pathname = parts.join('/').replace(/\/+/g, '/').replace(/^\//, ''); + + return { + hostname: hostname + , pathname: pathname + , dirpathname: parts.join('#') + , dirname: apppath + , isRoot: apppath === hostname + }; + } + + function loadDomainMounts(domaininfo) { + var appContext; + + // should order and group by longest domain, then longest path + if (!domainMergeMap[domaininfo.hostname]) { + // create an connect / express app exclusive to this domain + // TODO express?? + domainMergeMap[domaininfo.hostname] = { + hostname: domaininfo.hostname + , apps: connect() + , mountsMap: {} + }; + domainMerged.push(domainMergeMap[domaininfo.hostname]); + } + + if (domainMergeMap[domaininfo.hostname].mountsMap['/' + domaininfo.dirpathname]) { + return; + } + + domainMergeMap[domaininfo.hostname].mountsMap['/' + domaininfo.dirpathname] = function (req, res, next) { + if (appContext) { + appContext(req, res, next); + return; + } + + console.log('[log] LOADING "' + domaininfo.hostname + '/' + domaininfo.pathname + '"'); + getAppContext(domaininfo).then(function (localApp) { + // Note: pathname should NEVER have a leading '/' on its own + // we always add it explicitly + try { + domainMergeMap[domaininfo.hostname].apps.use('/' + domaininfo.pathname, localApp); + console.info('Loaded ' + domaininfo.hostname + ':' + securePort + '/' + domaininfo.pathname); + appContext = localApp; + appContext(req, res, next); + } catch(e) { + console.error('[ERROR] ' + domaininfo.hostname + ':' + securePort + '/' + domaininfo.pathname); + console.error(e); + res.send('{ "error": { "message": "[ERROR] could not load ' + + domaininfo.hostname + ':' + securePort + '/' + domaininfo.pathname + + 'or default error app." } }'); + } + }); + }; + domainMergeMap[domaininfo.hostname].apps.use( + '/' + domaininfo.pathname + , domainMergeMap[domaininfo.hostname].mountsMap['/' + domaininfo.dirpathname] + ); + + return PromiseA.resolve(); + } + + function loadDomainVhosts() { + domainMerged.forEach(function (domainApp) { + console.log('[log] merged ' + domainApp.hostname); + app.use(vhost(domainApp.hostname, domainApp.apps)); + app.use(vhost('www.' + domainApp.hostname, domainApp.apps)); + }); + } + + function readNewVhosts() { + return fs.readdirSync(vhostsdir).filter(function (node) { + // not a hidden or private file + return '.' !== node[0] && '_' !== node[0]; + }).map(getDomainInfo).sort(function (a, b) { + var hlen = b.hostname.length - a.hostname.length; + var plen = b.pathname.length - a.pathname.length; + + // A directory could be named example.com, example.com# example.com## + // to indicate order of preference (for API addons, for example) + var dlen = b.dirname.length - a.dirname.length; + if (!hlen) { + if (!plen) { + return dlen; + } + return plen; + } + return plen; + }); + } + // connect / express app var app = connect(); @@ -19,40 +113,11 @@ module.exports.create = function (securePort, certsPath, vhostsdir) { var secureOpts; var secureServer; - // the ssl domains I have - // TODO read vhosts minus - var domains = fs.readdirSync(vhostsdir).filter(function (node) { - // not a hidden or private file - return '.' !== node[0] && '_' !== node[0]; - }).map(function (apppath) { - var parts = apppath.split(/[#%]+/); - var hostname = parts.shift(); - var pathname = parts.join('/').replace(/\/+/g, '/').replace(/^\//, ''); - - return { - hostname: hostname - , pathname: pathname - , dirname: apppath - , isRoot: apppath === hostname - }; - }).sort(function (a, b) { - var hlen = b.hostname.length - a.hostname.length; - var plen = b.pathname.length - a.pathname.length; - - // A directory could be named example.com, example.com# example.com## - // to indicate order of preference (for API addons, for example) - var dlen = b.dirname.length - a.dirname.length; - if (!hlen) { - if (!plen) { - return dlen; - } - return plen; - } - return plen; - }); + /* var rootDomains = domains.filter(function (domaininfo) { return domaininfo.isRoot; }); + */ var domainMergeMap = {}; var domainMerged = []; @@ -66,6 +131,7 @@ module.exports.create = function (securePort, certsPath, vhostsdir) { } function getDummyAppContext(err, msg) { + console.error('[ERROR] getDummyAppContext'); console.error(err); console.error(msg); return function (req, res) { @@ -163,62 +229,30 @@ module.exports.create = function (securePort, certsPath, vhostsdir) { return secureContexts[domainname]; } - app.use(function (req, res, next) { - console.log('[log] request for ' + req.headers.host + req.url); - next(); - }); - - // TODO load these once the server has started + // TODO pre-cache these once the server has started? // return forEachAsync(rootDomains, loadCerts); - return forEachAsync(domains, function (domaininfo) { - var appContext; + // TODO load these even more lazily + return forEachAsync(readNewVhosts(), loadDomainMounts).then(loadDomainVhosts).then(runServer); - // should order and group by longest domain, then longest path - if (!domainMergeMap[domaininfo.hostname]) { - // create an connect / express app exclusive to this domain - // TODO express?? - domainMergeMap[domaininfo.hostname] = { hostname: domaininfo.hostname, apps: connect() }; - domainMerged.push(domainMergeMap[domaininfo.hostname]); + function hotloadApp(req, res, next) { + var vhost = (req.headers.host || '').split(':')[0]; + + if (!domainMergeMap[vhost]) { + // TODO reread directories } - domainMergeMap[domaininfo.hostname].apps.use( - '/' + domaininfo.pathname - , function (req, res, next) { - if (appContext) { - console.log('[log] has appContext'); - appContext(req, res, next); - return; - } + /* + // TODO loop through mounts and see if any fit + domainMergeMap[vhost].mountsMap['/' + domaininfo.dirpathname] + if (!domainMergeMap[domaininfo.hostname]) { + // TODO reread directories + } + */ + // TODO hot load all-the-things + next(); + } - console.log('[log] no appContext'); - getAppContext(domaininfo).then(function (localApp) { - // Note: pathname should NEVER have a leading '/' on its own - // we always add it explicitly - try { - domainMergeMap[domaininfo.hostname].apps.use('/' + domaininfo.pathname, localApp); - console.info('Loaded ' + domaininfo.hostname + ':' + securePort + '/' + domaininfo.pathname); - appContext = localApp; - appContext(req, res, next); - } catch(e) { - console.error('[ERROR] ' + domaininfo.hostname + ':' + securePort + '/' + domaininfo.pathname); - console.error(e); - res.send('{ "error": { "message": "[ERROR] could not load ' - + domaininfo.hostname + ':' + securePort + '/' + domaininfo.pathname - + 'or default error app." } }'); - } - }); - - } - ); - - return PromiseA.resolve(); - }).then(function () { - domainMerged.forEach(function (domainApp) { - console.log('[log] merged ' + domainApp.hostname); - app.use(vhost(domainApp.hostname, domainApp.apps)); - app.use(vhost('www.' + domainApp.hostname, domainApp.apps)); - }); - }).then(runServer); + app.use(hotloadApp); function runServer() { //provide a SNICallback when you create the options for the https server @@ -268,7 +302,6 @@ module.exports.create = function (securePort, certsPath, vhostsdir) { secureServer = https.createServer(secureOpts); secureServer.on('request', function (req, res) { - console.log('[log] request'); app(req, res); }); secureServer.listen(securePort, function () { diff --git a/walnut.js b/walnut.js index a4b6d2d..37517a0 100644 --- a/walnut.js +++ b/walnut.js @@ -1,4 +1,5 @@ -//var holepunch = require('./holepunch/beacon'); +'use strict'; + //var config = require('./device.json'); var securePort = process.argv[2] || 443; var insecurePort = process.argv[3] || 80; @@ -10,32 +11,33 @@ var certsPath = path.join(__dirname, 'certs'); // require('ssl-root-cas').inject(); var vhostsdir = path.join(__dirname, 'vhosts'); -require('./lib/insecure-server').create(securePort, insecurePort, redirects); -require('./lib/vhost-sni-server.js').create(securePort, certsPath, vhostsdir).then(function () { - var ports ; +function phoneHome() { + var holepunch = require('./holepunch/beacon'); + var ports; ports = [ - { private: 22 - , public: 22 + { private: 65022 + , public: 65022 , protocol: 'tcp' , ttl: 0 , test: { service: 'ssh' } , testable: false } - , { private: 443 - , public: 443 + , { private: 650443 + , public: 650443 , protocol: 'tcp' , ttl: 0 , test: { service: 'https' } } - , { private: 80 - , public: 80 + , { private: 65080 + , public: 65080 , protocol: 'tcp' , ttl: 0 , test: { service: 'http' } } ]; + // /* // TODO return a middleware holepunch.run(require('./redirects.json').reduce(function (all, redirect) { @@ -52,5 +54,7 @@ require('./lib/vhost-sni-server.js').create(securePort, certsPath, vhostsdir).th }, []), ports).catch(function () { console.error("Couldn't phone home. Oh well"); }); - */ -}); + //*/ +} +require('./lib/insecure-server').create(securePort, insecurePort, redirects); +require('./lib/vhost-sni-server.js').create(securePort, certsPath, vhostsdir).then(phoneHome);