clustering works (re-started work for standalone)
This commit is contained in:
parent
65645a7602
commit
96b7c9bb65
|
@ -3,15 +3,15 @@
|
||||||
// TODO if RAM is very low we should not fork at all,
|
// TODO if RAM is very low we should not fork at all,
|
||||||
// but use a different process altogether
|
// but use a different process altogether
|
||||||
|
|
||||||
console.log('pid:', process.pid);
|
console.info('pid:', process.pid);
|
||||||
console.log('title:', process.title);
|
console.info('title:', process.title);
|
||||||
console.log('arch:', process.arch);
|
console.info('arch:', process.arch);
|
||||||
console.log('platform:', process.platform);
|
console.info('platform:', process.platform);
|
||||||
console.log('\n\n\n[MASTER] Welcome to WALNUT!');
|
console.info('\n\n\n[MASTER] Welcome to WALNUT!');
|
||||||
|
|
||||||
var cluster = require('cluster');
|
var cluster = require('cluster');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var minWorkers = 2;
|
//var minWorkers = 2;
|
||||||
var numCores = 1; // Math.max(minWorkers, require('os').cpus().length);
|
var numCores = 1; // Math.max(minWorkers, require('os').cpus().length);
|
||||||
var workers = [];
|
var workers = [];
|
||||||
var caddypath = '/usr/local/bin/caddy';
|
var caddypath = '/usr/local/bin/caddy';
|
||||||
|
@ -23,8 +23,8 @@ var conf = {
|
||||||
// TODO externalInsecurePort?
|
// TODO externalInsecurePort?
|
||||||
, locked: false // TODO XXX
|
, locked: false // TODO XXX
|
||||||
, ipcKey: null
|
, ipcKey: null
|
||||||
, caddyfilepath: path.join(__dirname, 'Caddyfile')
|
, caddyfilepath: path.join(__dirname, '..', 'Caddyfile')
|
||||||
, sitespath: path.join(__dirname, 'sites-enabled')
|
, sitespath: path.join(__dirname, '..', 'sites-enabled')
|
||||||
};
|
};
|
||||||
var state = {};
|
var state = {};
|
||||||
var caddy;
|
var caddy;
|
||||||
|
@ -45,9 +45,10 @@ cluster.on('online', function (worker) {
|
||||||
var certPaths = [path.join(__dirname, 'certs', 'live')];
|
var certPaths = [path.join(__dirname, 'certs', 'live')];
|
||||||
var info;
|
var info;
|
||||||
|
|
||||||
console.log('[MASTER] Worker ' + worker.process.pid + ' is online');
|
console.info('[MASTER] Worker ' + worker.process.pid + ' is online');
|
||||||
fork();
|
fork();
|
||||||
|
|
||||||
|
// TODO communicate config with environment vars?
|
||||||
info = {
|
info = {
|
||||||
type: 'com.daplie.walnut.init'
|
type: 'com.daplie.walnut.init'
|
||||||
, conf: {
|
, conf: {
|
||||||
|
@ -72,7 +73,7 @@ cluster.on('online', function (worker) {
|
||||||
// calls init if init has not been called
|
// calls init if init has not been called
|
||||||
state.caddy = caddy;
|
state.caddy = caddy;
|
||||||
state.workers = workers;
|
state.workers = workers;
|
||||||
require('./lib/master').touch(conf, state).then(function () {
|
require('../lib/master').touch(conf, state).then(function () {
|
||||||
info.type = 'com.daplie.walnut.webserver.onrequest';
|
info.type = 'com.daplie.walnut.webserver.onrequest';
|
||||||
info.conf.ipcKey = conf.ipcKey;
|
info.conf.ipcKey = conf.ipcKey;
|
||||||
info.conf.memstoreSock = conf.memstoreSock;
|
info.conf.memstoreSock = conf.memstoreSock;
|
||||||
|
@ -84,7 +85,7 @@ cluster.on('online', function (worker) {
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.on('exit', function (worker, code, signal) {
|
cluster.on('exit', function (worker, code, signal) {
|
||||||
console.log('[MASTER] Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal);
|
console.info('[MASTER] Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal);
|
||||||
|
|
||||||
workers = workers.map(function (w) {
|
workers = workers.map(function (w) {
|
||||||
if (worker !== w) {
|
if (worker !== w) {
|
||||||
|
@ -102,7 +103,7 @@ cluster.on('exit', function (worker, code, signal) {
|
||||||
fork();
|
fork();
|
||||||
|
|
||||||
if (useCaddy) {
|
if (useCaddy) {
|
||||||
caddy = require('./lib/spawn-caddy').create(conf);
|
caddy = require('../lib/spawn-caddy').create(conf);
|
||||||
// relies on { localPort, locked }
|
// relies on { localPort, locked }
|
||||||
caddy.spawn(conf);
|
caddy.spawn(conf);
|
||||||
}
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports.create = function (opts) {
|
||||||
|
var id = '0';
|
||||||
|
|
||||||
|
function createAndBindServers(message, cb) {
|
||||||
|
var msg = message.conf;
|
||||||
|
|
||||||
|
require('../lib/local-server').create(msg.certPaths, msg.localPort, function (err, webserver) {
|
||||||
|
if (err) {
|
||||||
|
console.error('[ERROR] worker.js');
|
||||||
|
console.error(err.stack);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info("#" + id + " Listening on " + msg.protocol + "://" + webserver.address().address + ":" + webserver.address().port, '\n');
|
||||||
|
|
||||||
|
return cb(webserver);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO conditional if 80 is being served by caddy
|
||||||
|
require('../lib/insecure-server').create(msg.externalPort, msg.insecurePort);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Worker Mode
|
||||||
|
//
|
||||||
|
function waitForConfig(message) {
|
||||||
|
if ('com.daplie.walnut.init' !== message.type) {
|
||||||
|
console.warn('[Worker] 0 got unexpected message:');
|
||||||
|
console.warn(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.removeListener('message', waitForConfig);
|
||||||
|
|
||||||
|
// NOTE: this callback must return a promise for an express app
|
||||||
|
createAndBindServers(message, function (webserver) {
|
||||||
|
var PromiseA = require('bluebird');
|
||||||
|
return new PromiseA(function (resolve) {
|
||||||
|
function initWebServer(srvmsg) {
|
||||||
|
if ('com.daplie.walnut.webserver.onrequest' !== srvmsg.type) {
|
||||||
|
console.warn('[Worker] 1 got unexpected message:');
|
||||||
|
console.warn(srvmsg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.removeListener('message', initWebServer);
|
||||||
|
|
||||||
|
resolve(require('../lib/worker').create(webserver, srvmsg));
|
||||||
|
}
|
||||||
|
|
||||||
|
process.send({ type: 'com.daplie.walnut.webserver.listening' });
|
||||||
|
process.on('message', initWebServer);
|
||||||
|
}).then(function (app) {
|
||||||
|
console.info('[Worker Ready]');
|
||||||
|
return app;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Standalone Mode
|
||||||
|
//
|
||||||
|
if (opts) {
|
||||||
|
// NOTE: this callback must return a promise for an express app
|
||||||
|
createAndBindServers(opts, function (webserver) {
|
||||||
|
var PromiseA = require('bluebird');
|
||||||
|
return new PromiseA(function (resolve) {
|
||||||
|
opts.getConfig(function (srvmsg) {
|
||||||
|
resolve(require('../lib/worker').create(webserver, srvmsg));
|
||||||
|
});
|
||||||
|
}).then(function (app) {
|
||||||
|
console.info('[Standalone Ready]');
|
||||||
|
return app;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// we are in cluster mode, as opposed to standalone mode
|
||||||
|
id = require('cluster').worker.id.toString();
|
||||||
|
// We have to wait to get the configuration from the master process
|
||||||
|
// before we can start our webserver
|
||||||
|
console.info('[Worker #' + id + '] online!');
|
||||||
|
process.on('message', waitForConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Debugging
|
||||||
|
//
|
||||||
|
process.on('exit', function (code) {
|
||||||
|
// only sync code can run here
|
||||||
|
console.info('uptime:', process.uptime());
|
||||||
|
console.info(process.memoryUsage());
|
||||||
|
console.info('[exit] process.exit() has been called (or master has killed us).');
|
||||||
|
console.info(code);
|
||||||
|
});
|
||||||
|
process.on('beforeExit', function () {
|
||||||
|
// async can be scheduled here
|
||||||
|
console.info('[beforeExit] Event Loop is empty. Process will end.');
|
||||||
|
});
|
||||||
|
process.on('unhandledRejection', function (err) {
|
||||||
|
// this should always throw
|
||||||
|
// (it means somewhere we're not using bluebird by accident)
|
||||||
|
console.error('[caught] [unhandledRejection]');
|
||||||
|
console.error(Object.keys(err));
|
||||||
|
console.error(err);
|
||||||
|
console.error(err.stack);
|
||||||
|
});
|
||||||
|
process.on('rejectionHandled', function (msg) {
|
||||||
|
console.error('[rejectionHandled]');
|
||||||
|
console.error(msg);
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,23 @@
|
||||||
|
# sudo rsync -av etc/init/caddy.conf /etc/init/caddy.conf
|
||||||
|
|
||||||
|
description "Caddy Server"
|
||||||
|
version "1.0"
|
||||||
|
author "AJ ONeal"
|
||||||
|
|
||||||
|
# Upstart has nothing in $PATH by default
|
||||||
|
env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
|
||||||
|
# Keep the server running on crash or machine reboot
|
||||||
|
respawn
|
||||||
|
respawn limit 10 120
|
||||||
|
start on runlevel [2345]
|
||||||
|
|
||||||
|
# Start the server using spark and redirect output to log files
|
||||||
|
script
|
||||||
|
DATE=`date '+%F_%H-%M-%S'`
|
||||||
|
cd /srv/walnut
|
||||||
|
# exec /usr/local/bin/caddy -conf /srv/walnut/Caddyfile -pidfile /tmp/caddy.pid \
|
||||||
|
exec start-stop-daemon --start --pidfile /tmp/caddy.pid --exec /usr/local/bin/caddy -- -conf /srv/walnut/Caddyfile --pidfile /tmp/caddy.pid \
|
||||||
|
> "./logs/access.caddy.${DATE}.log" \
|
||||||
|
2> "./logs/error.caddy.${DATE}.log"
|
||||||
|
end script
|
|
@ -14,17 +14,13 @@ module.exports.create = function (conf, deps, app) {
|
||||||
|
|
||||||
var PromiseA = require('bluebird');
|
var PromiseA = require('bluebird');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
console.log(route);
|
|
||||||
// TODO needs some version stuff (which would also allow hot-loading of updates)
|
// TODO needs some version stuff (which would also allow hot-loading of updates)
|
||||||
// TODO version could be tied to sha256sum
|
// TODO version could be tied to sha256sum
|
||||||
var pkgpath = path.join(conf.apipath, (route.api.package || route.api.id), (route.api.version || ''));
|
var pkgpath = path.join(conf.apipath, (route.api.package || route.api.id), (route.api.version || ''));
|
||||||
|
|
||||||
console.log('pkgpath');
|
|
||||||
console.log(pkgpath);
|
|
||||||
|
|
||||||
return new PromiseA(function (resolve, reject) {
|
return new PromiseA(function (resolve, reject) {
|
||||||
try {
|
try {
|
||||||
route.route = require(pkgpath).create(conf, deps.app, app);
|
route.route = require(pkgpath).create(conf, deps, app);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
return;
|
return;
|
||||||
|
@ -37,9 +33,6 @@ module.exports.create = function (conf, deps, app) {
|
||||||
function api(req, res, next) {
|
function api(req, res, next) {
|
||||||
var apps;
|
var apps;
|
||||||
|
|
||||||
console.log('hostname', req.hostname);
|
|
||||||
console.log('headers', req.headers);
|
|
||||||
|
|
||||||
if (!vhostsMap[req.hostname]) {
|
if (!vhostsMap[req.hostname]) {
|
||||||
// TODO keep track of match-only vhosts, such as '*.example.com',
|
// TODO keep track of match-only vhosts, such as '*.example.com',
|
||||||
// separate from exact matches
|
// separate from exact matches
|
||||||
|
@ -71,14 +64,10 @@ module.exports.create = function (conf, deps, app) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!apps) {
|
if (!apps) {
|
||||||
console.log('No apps to try for this hostname');
|
|
||||||
console.log(vhostsMap[req.hostname]);
|
|
||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log(apps);
|
|
||||||
|
|
||||||
function nextify(err) {
|
function nextify(err) {
|
||||||
var route;
|
var route;
|
||||||
|
|
||||||
|
|
|
@ -109,8 +109,6 @@ function getVhostsMap(config) {
|
||||||
vhosts.sort(sortApps);
|
vhosts.sort(sortApps);
|
||||||
|
|
||||||
vhosts.forEach(function (domain) {
|
vhosts.forEach(function (domain) {
|
||||||
console.log(domain.hostname, domain.pathname, domain.dirname);
|
|
||||||
|
|
||||||
if (!vhostsMap[domain.hostname]) {
|
if (!vhostsMap[domain.hostname]) {
|
||||||
vhostsMap[domain.hostname] = { pathnamesMap: {}, pathnames: [] };
|
vhostsMap[domain.hostname] = { pathnamesMap: {}, pathnames: [] };
|
||||||
}
|
}
|
||||||
|
@ -129,7 +127,6 @@ function getVhostsMap(config) {
|
||||||
module.exports.deserialize = deserialize;
|
module.exports.deserialize = deserialize;
|
||||||
module.exports.getVhostsMap = getVhostsMap;
|
module.exports.getVhostsMap = getVhostsMap;
|
||||||
module.exports.create = function (db) {
|
module.exports.create = function (db) {
|
||||||
console.log('[DB -1]');
|
|
||||||
var wrap = require('dbwrap');
|
var wrap = require('dbwrap');
|
||||||
|
|
||||||
var dir = [
|
var dir = [
|
||||||
|
|
|
@ -1,343 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports.create = function (securePort, vhostsdir) {
|
|
||||||
var PromiseA = require('bluebird').Promise;
|
|
||||||
var serveStatic;
|
|
||||||
var fs = require('fs');
|
|
||||||
var path = require('path');
|
|
||||||
var dummyCerts;
|
|
||||||
var loopbackToken = require('crypto').randomBytes(32).toString('hex');
|
|
||||||
|
|
||||||
function handleAppScopedError(tag, domaininfo, req, res, fn) {
|
|
||||||
function next(err) {
|
|
||||||
if (!err) {
|
|
||||||
fn(req, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.headersSent) {
|
|
||||||
console.error('[ERROR] handleAppScopedError headersSent');
|
|
||||||
console.log(err);
|
|
||||||
console.log(err.stack);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error('[ERROR] handleAppScopedError');
|
|
||||||
console.log(err);
|
|
||||||
console.log(err.stack);
|
|
||||||
|
|
||||||
res.writeHead(500);
|
|
||||||
res.end(
|
|
||||||
"<html>"
|
|
||||||
+ "<head>"
|
|
||||||
+ '<link rel="icon" href="favicon.ico" />'
|
|
||||||
+ "</head>"
|
|
||||||
+ "<body>"
|
|
||||||
+ "<pre>"
|
|
||||||
+ "<code>"
|
|
||||||
+ "Method: " + encodeURI(req.method)
|
|
||||||
+ '\n'
|
|
||||||
+ "Hostname: " + encodeURI(domaininfo.hostname)
|
|
||||||
+ '\n'
|
|
||||||
+ "App: " + encodeURI(domaininfo.pathname ? (domaininfo.pathname + '/') : '')
|
|
||||||
+ '\n'
|
|
||||||
+ "Route: " + encodeURI(req.url)//.replace(/^\//, '')
|
|
||||||
+ '\n'
|
|
||||||
// TODO better sanatization
|
|
||||||
+ 'Error: ' + (err.message || err.toString()).replace(/</g, '<')
|
|
||||||
+ "</code>"
|
|
||||||
+ "</pre>"
|
|
||||||
+ "</body>"
|
|
||||||
+ "</html>"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createPromiseApps(secureServer) {
|
|
||||||
return new PromiseA(function (resolve) {
|
|
||||||
var forEachAsync = require('foreachasync').forEachAsync.create(PromiseA);
|
|
||||||
var connect = require('connect');
|
|
||||||
// TODO make lazy
|
|
||||||
var app = connect().use(require('compression')());
|
|
||||||
var vhost = require('vhost');
|
|
||||||
|
|
||||||
var domainMergeMap = {};
|
|
||||||
var domainMerged = [];
|
|
||||||
|
|
||||||
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 connectContext = {};
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[log] [once] Preparing mount for', domaininfo.hostname + '/' + domaininfo.dirpathname);
|
|
||||||
domainMergeMap[domaininfo.hostname].mountsMap['/' + domaininfo.dirpathname] = function (req, res, next) {
|
|
||||||
res.setHeader('Strict-Transport-Security', 'max-age=10886400; includeSubDomains; preload');
|
|
||||||
function loadThatApp() {
|
|
||||||
var time = Date.now();
|
|
||||||
|
|
||||||
console.log('[log] LOADING "' + domaininfo.hostname + '/' + domaininfo.pathname + '"', req.url);
|
|
||||||
return getAppContext(domaininfo).then(function (localApp) {
|
|
||||||
console.info((Date.now() - time) + 'ms Loaded ' + domaininfo.hostname + ':' + securePort + '/' + domaininfo.pathname);
|
|
||||||
//if (localApp.arity >= 2) { /* connect uses .apply(null, arguments)*/ }
|
|
||||||
if ('function' !== typeof localApp) {
|
|
||||||
localApp = getDummyAppContext(null, "[ERROR] no connect-style export from " + domaininfo.dirname);
|
|
||||||
}
|
|
||||||
|
|
||||||
function fourohfour(req, res) {
|
|
||||||
res.writeHead(404);
|
|
||||||
res.end(
|
|
||||||
"<html>"
|
|
||||||
+ "<head>"
|
|
||||||
+ '<link rel="icon" href="favicon.ico" />'
|
|
||||||
+ "</head>"
|
|
||||||
+ "<body>"
|
|
||||||
+ "Cannot "
|
|
||||||
+ encodeURI(req.method)
|
|
||||||
+ " 'https://"
|
|
||||||
+ encodeURI(domaininfo.hostname)
|
|
||||||
+ '/'
|
|
||||||
+ encodeURI(domaininfo.pathname ? (domaininfo.pathname + '/') : '')
|
|
||||||
+ encodeURI(req.url.replace(/^\//, ''))
|
|
||||||
+ "'"
|
|
||||||
+ "<br/>"
|
|
||||||
+ "<br/>"
|
|
||||||
+ "Domain: " + encodeURI(domaininfo.hostname)
|
|
||||||
+ "<br/>"
|
|
||||||
+ "App: " + encodeURI(domaininfo.pathname)
|
|
||||||
+ "<br/>"
|
|
||||||
+ "Route : " + encodeURI(req.url)
|
|
||||||
+ "</body>"
|
|
||||||
+ "</html>"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: pathname should NEVER have a leading '/' on its own
|
|
||||||
// we always add it explicitly
|
|
||||||
function localAppWrapped(req, res) {
|
|
||||||
console.log('[debug]', domaininfo.hostname + '/' + domaininfo.pathname, req.url);
|
|
||||||
localApp(req, res, handleAppScopedError('localApp', domaininfo, req, res, fourohfour));
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
var localConnect = connect();
|
|
||||||
localConnect.use(require('connect-query')());
|
|
||||||
localConnect.use(localAppWrapped);
|
|
||||||
domainMergeMap[domaininfo.hostname].apps.use('/' + domaininfo.pathname, localConnect);
|
|
||||||
return localConnect;
|
|
||||||
} catch(e) {
|
|
||||||
console.error('[ERROR] '
|
|
||||||
+ domaininfo.hostname + ':' + securePort
|
|
||||||
+ '/' + domaininfo.pathname
|
|
||||||
);
|
|
||||||
console.error(e);
|
|
||||||
// TODO this may not work in web apps (due to 500), probably okay
|
|
||||||
res.writeHead(500);
|
|
||||||
res.end('{ "error": { "message": "[ERROR] could not load '
|
|
||||||
+ encodeURI(domaininfo.hostname) + ':' + securePort + '/' + encodeURI(domaininfo.pathname)
|
|
||||||
+ 'or default error app." } }');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function suckItDubDubDub(req, res) {
|
|
||||||
var newLoc = 'https://' + (req.headers.host||'').replace(/^www\./) + req.url;
|
|
||||||
res.statusCode = 301;
|
|
||||||
res.setHeader('Location', newLoc);
|
|
||||||
res.end("<html><head><title></title></head><body><!-- redirecting nowww --></body><html>");
|
|
||||||
}
|
|
||||||
|
|
||||||
function nextify() {
|
|
||||||
if (!appContext) {
|
|
||||||
appContext = loadThatApp();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!appContext.then) {
|
|
||||||
appContext(req, res, next);
|
|
||||||
} else {
|
|
||||||
appContext.then(function (localConnect) {
|
|
||||||
appContext = localConnect;
|
|
||||||
appContext(req, res, next);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!serveStatic) {
|
|
||||||
serveStatic = require('serve-static');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!connectContext.static) {
|
|
||||||
console.log('[static]', path.join(vhostsdir, domaininfo.dirname, 'public'));
|
|
||||||
connectContext.static = serveStatic(path.join(vhostsdir, domaininfo.dirname, 'public'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (/^www\./.test(req.headers.host)) {
|
|
||||||
if (/\.appcache\b/.test(req.url)) {
|
|
||||||
res.setHeader('Content-Type', 'text/cache-manifest');
|
|
||||||
res.end('CACHE MANIFEST\n\n# v0__DELETE__CACHE__MANIFEST__\n\nNETWORK:\n*');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
suckItDubDubDub(req, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (/^\/api\//.test(req.url)) {
|
|
||||||
nextify();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
connectContext.static(req, res, nextify);
|
|
||||||
};
|
|
||||||
domainMergeMap[domaininfo.hostname].apps.use(
|
|
||||||
'/' + domaininfo.pathname
|
|
||||||
, domainMergeMap[domaininfo.hostname].mountsMap['/' + domaininfo.dirpathname]
|
|
||||||
);
|
|
||||||
|
|
||||||
return PromiseA.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDummyAppContext(err, msg) {
|
|
||||||
console.error('[ERROR] getDummyAppContext');
|
|
||||||
console.error(err);
|
|
||||||
console.error(msg);
|
|
||||||
return function (req, res) {
|
|
||||||
res.writeHead(500);
|
|
||||||
res.end('{ "error": { "message": "' + msg + '" } }');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLoopbackApp() {
|
|
||||||
return function (req, res) {
|
|
||||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
||||||
res.end(JSON.stringify({ "success": true, "token": loopbackToken }));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAppContext(domaininfo) {
|
|
||||||
var localApp;
|
|
||||||
|
|
||||||
if ('loopback.daplie.invalid' === domaininfo.dirname) {
|
|
||||||
return getLoopbackApp();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// TODO live reload required modules
|
|
||||||
localApp = require(path.join(vhostsdir, domaininfo.dirname, 'app.js'));
|
|
||||||
if (localApp.create) {
|
|
||||||
// TODO read local config.yml and pass it in
|
|
||||||
// TODO pass in websocket
|
|
||||||
localApp = localApp.create(secureServer, {
|
|
||||||
dummyCerts: dummyCerts
|
|
||||||
, hostname: domaininfo.hostname
|
|
||||||
, port: securePort
|
|
||||||
, url: domaininfo.pathname
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!localApp) {
|
|
||||||
localApp = getDummyAppContext(null, "[ERROR] no app was returned by app.js for " + domaininfo.dirname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!localApp.then) {
|
|
||||||
localApp = PromiseA.resolve(localApp);
|
|
||||||
} else {
|
|
||||||
return localApp.catch(function (e) {
|
|
||||||
console.error("[ERROR] initialization failed during create() for " + domaininfo.dirname);
|
|
||||||
console.error(e);
|
|
||||||
throw e;
|
|
||||||
//return getDummyAppContext(e, "[ERROR] initialization failed during create() for " + domaininfo.dirname);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
localApp = getDummyAppContext(e, "[ERROR] could not load app.js for " + domaininfo.dirname);
|
|
||||||
localApp = PromiseA.resolve(localApp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return localApp;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadDomainVhosts() {
|
|
||||||
domainMerged.forEach(function (domainApp) {
|
|
||||||
if (domainApp._loaded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[log] [once] Loading all mounts for ' + domainApp.hostname);
|
|
||||||
domainApp._loaded = true;
|
|
||||||
app.use(vhost(domainApp.hostname, domainApp.apps));
|
|
||||||
app.use(vhost('www.' + domainApp.hostname, function (req, res) {
|
|
||||||
res.send({
|
|
||||||
error: {
|
|
||||||
message: "this is an api. ain't no www belong here"
|
|
||||||
, code: "E_WWW"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO pre-cache these once the server has started?
|
|
||||||
// return forEachAsync(rootDomains, loadCerts);
|
|
||||||
// TODO load these even more lazily
|
|
||||||
return forEachAsync(readNewVhosts(), loadDomainMounts).then(loadDomainVhosts).then(function () {
|
|
||||||
console.log('[log] TODO fix and use hotload');
|
|
||||||
//app.use(hotloadApp);
|
|
||||||
resolve(app);
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { create: createPromiseApps };
|
|
||||||
};
|
|
|
@ -37,6 +37,14 @@ module.exports.create = function (webserver, info, state) {
|
||||||
});
|
});
|
||||||
var cstore = require('cluster-store');
|
var cstore = require('cluster-store');
|
||||||
|
|
||||||
|
if (info.conf.trustProxy) {
|
||||||
|
console.info('[Trust Proxy]');
|
||||||
|
app.set('trust proxy', ['loopback']);
|
||||||
|
//app.set('trust proxy', function (ip) { console.log('[ip]', ip); return true; });
|
||||||
|
} else {
|
||||||
|
console.info('[DO NOT trust proxy]');
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
function unlockDevice(conf, state) {
|
function unlockDevice(conf, state) {
|
||||||
return require('./lib/unlock-device').create().then(function (result) {
|
return require('./lib/unlock-device').create().then(function (result) {
|
||||||
|
@ -65,7 +73,6 @@ module.exports.create = function (webserver, info, state) {
|
||||||
// I guess I just needs letsencrypt
|
// I guess I just needs letsencrypt
|
||||||
|
|
||||||
function scrubTheDub(req, res, next) {
|
function scrubTheDub(req, res, next) {
|
||||||
console.log('[no-www]', req.method, req.url);
|
|
||||||
var host = req.hostname;
|
var host = req.hostname;
|
||||||
|
|
||||||
if (!host || 'string' !== typeof host) {
|
if (!host || 'string' !== typeof host) {
|
||||||
|
@ -84,11 +91,23 @@ module.exports.create = function (webserver, info, state) {
|
||||||
require('./no-www').scrubTheDub(req, res);
|
require('./no-www').scrubTheDub(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.trustProxy) {
|
function caddyBugfix(req, res, next) {
|
||||||
app.set('trust proxy', ['loopback']);
|
// workaround for Caddy
|
||||||
//app.set('trust proxy', function (ip) { ... });
|
// https://github.com/mholt/caddy/issues/341
|
||||||
|
if (app.get('trust proxy')) {
|
||||||
|
if (req.headers['x-forwarded-proto']) {
|
||||||
|
req.headers['x-forwarded-proto'] = (req.headers['x-forwarded-proto'] || '').split(/,\s+/g)[0] || undefined;
|
||||||
|
}
|
||||||
|
if (req.headers['x-forwarded-host']) {
|
||||||
|
req.headers['x-forwarded-host'] = (req.headers['x-forwarded-host'] || '').split(/,\s+/g)[0] || undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use('/', scrubTheDub);
|
app.use('/', scrubTheDub);
|
||||||
|
app.use('/', caddyBugfix);
|
||||||
|
|
||||||
return PromiseA.all([
|
return PromiseA.all([
|
||||||
cstore.create({
|
cstore.create({
|
||||||
|
@ -137,7 +156,6 @@ module.exports.create = function (webserver, info, state) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function handleApi(req, res, next) {
|
function handleApi(req, res, next) {
|
||||||
console.log('[API]', req.method, req.url);
|
|
||||||
var myApp;
|
var myApp;
|
||||||
|
|
||||||
if (!/^\/api/.test(req.url)) {
|
if (!/^\/api/.test(req.url)) {
|
||||||
|
@ -155,8 +173,8 @@ module.exports.create = function (webserver, info, state) {
|
||||||
|
|
||||||
if (apiHandler) {
|
if (apiHandler) {
|
||||||
if (apiHandler.then) {
|
if (apiHandler.then) {
|
||||||
apiHandler.then(function (app) {
|
apiHandler.then(function (myApp) {
|
||||||
app(req, res, next);
|
myApp(req, res, next);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -167,12 +185,16 @@ module.exports.create = function (webserver, info, state) {
|
||||||
|
|
||||||
// apiHandler = require('./vhost-server').create(info.localPort, vhostsdir).create(webserver, app)
|
// apiHandler = require('./vhost-server').create(info.localPort, vhostsdir).create(webserver, app)
|
||||||
myApp = express();
|
myApp = express();
|
||||||
|
if (app.get('trust proxy')) {
|
||||||
|
myApp.set('trust proxy', app.get('trust proxy'));
|
||||||
|
}
|
||||||
apiHandler = require('./api-server').create(
|
apiHandler = require('./api-server').create(
|
||||||
{ apppath: '../packages/apps/'
|
{ apppath: '../packages/apps/'
|
||||||
, apipath: '../packages/apis/'
|
, apipath: '../packages/apis/'
|
||||||
, vhostsMap: vhostsMap
|
, vhostsMap: vhostsMap
|
||||||
, server: webserver
|
, server: webserver
|
||||||
, externalPort: info.externalPort
|
, externalPort: info.externalPort
|
||||||
|
, apiPrefix: '/api'
|
||||||
}
|
}
|
||||||
, { app: myApp
|
, { app: myApp
|
||||||
, memstore: memstore
|
, memstore: memstore
|
||||||
|
@ -185,11 +207,6 @@ module.exports.create = function (webserver, info, state) {
|
||||||
}
|
}
|
||||||
).api;
|
).api;
|
||||||
|
|
||||||
// TODO
|
|
||||||
// X-Forwarded-For
|
|
||||||
// X-Forwarded-Proto
|
|
||||||
console.log('api server', req.hostname, req.secure, req.ip);
|
|
||||||
|
|
||||||
apiHandler(req, res, next);
|
apiHandler(req, res, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
"escape-string-regexp": "1.x",
|
"escape-string-regexp": "1.x",
|
||||||
"etag": "^1.5.1",
|
"etag": "^1.5.1",
|
||||||
"express": "4.x",
|
"express": "4.x",
|
||||||
"express-lazy": "1.x",
|
"express-lazy": "^1.1.1",
|
||||||
"express-session": "^1.11.3",
|
"express-session": "^1.11.3",
|
||||||
"finalhandler": "^0.3.4",
|
"finalhandler": "^0.3.4",
|
||||||
"foreachasync": "5.x",
|
"foreachasync": "5.x",
|
||||||
|
|
|
@ -20,7 +20,7 @@ Math.random = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cluster.isMaster) {
|
if (cluster.isMaster) {
|
||||||
require('./master');
|
require('./boot/master');
|
||||||
} else {
|
} else {
|
||||||
require('./worker');
|
require('./boot/worker').create(null);
|
||||||
}
|
}
|
||||||
|
|
76
worker.js
76
worker.js
|
@ -1,76 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var cluster = require('cluster');
|
|
||||||
var id = cluster.worker.id.toString();
|
|
||||||
|
|
||||||
function waitForInit(message) {
|
|
||||||
if ('com.daplie.walnut.init' !== message.type) {
|
|
||||||
console.warn('[Worker] 0 got unexpected message:');
|
|
||||||
console.warn(message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg = message.conf;
|
|
||||||
process.removeListener('message', waitForInit);
|
|
||||||
require('./lib/local-server').create(msg.certPaths, msg.localPort, function (err, webserver) {
|
|
||||||
if (err) {
|
|
||||||
console.error('[ERROR] worker.js');
|
|
||||||
console.error(err.stack);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("#" + id + " Listening on " + msg.protocol + "://" + webserver.address().address + ":" + webserver.address().port, '\n');
|
|
||||||
|
|
||||||
var PromiseA = require('bluebird');
|
|
||||||
return new PromiseA(function (resolve) {
|
|
||||||
function initWebServer(srvmsg) {
|
|
||||||
if ('com.daplie.walnut.webserver.onrequest' !== srvmsg.type) {
|
|
||||||
console.warn('[Worker] 1 got unexpected message:');
|
|
||||||
console.warn(srvmsg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
process.removeListener('message', initWebServer);
|
|
||||||
resolve(require('./lib/worker').create(webserver, srvmsg));
|
|
||||||
}
|
|
||||||
process.send({ type: 'com.daplie.walnut.webserver.listening' });
|
|
||||||
process.on('message', initWebServer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO conditional if 80 is being served by caddy
|
|
||||||
require('./lib/insecure-server').create(msg.externalPort, msg.insecurePort);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to wait to get the configuration from the master process
|
|
||||||
// before we can start our webserver
|
|
||||||
console.log('[Worker #' + id + '] online!');
|
|
||||||
process.on('message', waitForInit);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Debugging
|
|
||||||
//
|
|
||||||
process.on('exit', function (code) {
|
|
||||||
// only sync code can run here
|
|
||||||
console.log('uptime:', process.uptime());
|
|
||||||
console.log(process.memoryUsage());
|
|
||||||
console.log('[exit] process.exit() has been called (or master has killed us).');
|
|
||||||
console.log(code);
|
|
||||||
});
|
|
||||||
process.on('beforeExit', function (msg) {
|
|
||||||
// async can be scheduled here
|
|
||||||
console.log('[beforeExit] Event Loop is empty. Process will end.');
|
|
||||||
console.log(msg);
|
|
||||||
});
|
|
||||||
process.on('unhandledRejection', function (err) {
|
|
||||||
// this should always throw
|
|
||||||
// (it means somewhere we're not using bluebird by accident)
|
|
||||||
console.error('[caught] [unhandledRejection]');
|
|
||||||
console.error(Object.keys(err));
|
|
||||||
console.error(err);
|
|
||||||
console.error(err.stack);
|
|
||||||
});
|
|
||||||
process.on('rejectionHandled', function (msg) {
|
|
||||||
console.error('[rejectionHandled]');
|
|
||||||
console.error(msg);
|
|
||||||
});
|
|
Loading…
Reference in New Issue