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,
|
||||
// but use a different process altogether
|
||||
|
||||
console.log('pid:', process.pid);
|
||||
console.log('title:', process.title);
|
||||
console.log('arch:', process.arch);
|
||||
console.log('platform:', process.platform);
|
||||
console.log('\n\n\n[MASTER] Welcome to WALNUT!');
|
||||
console.info('pid:', process.pid);
|
||||
console.info('title:', process.title);
|
||||
console.info('arch:', process.arch);
|
||||
console.info('platform:', process.platform);
|
||||
console.info('\n\n\n[MASTER] Welcome to WALNUT!');
|
||||
|
||||
var cluster = require('cluster');
|
||||
var path = require('path');
|
||||
var minWorkers = 2;
|
||||
//var minWorkers = 2;
|
||||
var numCores = 1; // Math.max(minWorkers, require('os').cpus().length);
|
||||
var workers = [];
|
||||
var caddypath = '/usr/local/bin/caddy';
|
||||
|
@ -23,8 +23,8 @@ var conf = {
|
|||
// TODO externalInsecurePort?
|
||||
, locked: false // TODO XXX
|
||||
, ipcKey: null
|
||||
, caddyfilepath: path.join(__dirname, 'Caddyfile')
|
||||
, sitespath: path.join(__dirname, 'sites-enabled')
|
||||
, caddyfilepath: path.join(__dirname, '..', 'Caddyfile')
|
||||
, sitespath: path.join(__dirname, '..', 'sites-enabled')
|
||||
};
|
||||
var state = {};
|
||||
var caddy;
|
||||
|
@ -45,9 +45,10 @@ cluster.on('online', function (worker) {
|
|||
var certPaths = [path.join(__dirname, 'certs', 'live')];
|
||||
var info;
|
||||
|
||||
console.log('[MASTER] Worker ' + worker.process.pid + ' is online');
|
||||
console.info('[MASTER] Worker ' + worker.process.pid + ' is online');
|
||||
fork();
|
||||
|
||||
// TODO communicate config with environment vars?
|
||||
info = {
|
||||
type: 'com.daplie.walnut.init'
|
||||
, conf: {
|
||||
|
@ -72,7 +73,7 @@ cluster.on('online', function (worker) {
|
|||
// calls init if init has not been called
|
||||
state.caddy = caddy;
|
||||
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.conf.ipcKey = conf.ipcKey;
|
||||
info.conf.memstoreSock = conf.memstoreSock;
|
||||
|
@ -84,7 +85,7 @@ cluster.on('online', function (worker) {
|
|||
});
|
||||
|
||||
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) {
|
||||
if (worker !== w) {
|
||||
|
@ -102,7 +103,7 @@ cluster.on('exit', function (worker, code, signal) {
|
|||
fork();
|
||||
|
||||
if (useCaddy) {
|
||||
caddy = require('./lib/spawn-caddy').create(conf);
|
||||
caddy = require('../lib/spawn-caddy').create(conf);
|
||||
// relies on { localPort, locked }
|
||||
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 path = require('path');
|
||||
console.log(route);
|
||||
// TODO needs some version stuff (which would also allow hot-loading of updates)
|
||||
// TODO version could be tied to sha256sum
|
||||
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) {
|
||||
try {
|
||||
route.route = require(pkgpath).create(conf, deps.app, app);
|
||||
route.route = require(pkgpath).create(conf, deps, app);
|
||||
} catch(e) {
|
||||
reject(e);
|
||||
return;
|
||||
|
@ -37,9 +33,6 @@ module.exports.create = function (conf, deps, app) {
|
|||
function api(req, res, next) {
|
||||
var apps;
|
||||
|
||||
console.log('hostname', req.hostname);
|
||||
console.log('headers', req.headers);
|
||||
|
||||
if (!vhostsMap[req.hostname]) {
|
||||
// TODO keep track of match-only vhosts, such as '*.example.com',
|
||||
// separate from exact matches
|
||||
|
@ -71,14 +64,10 @@ module.exports.create = function (conf, deps, app) {
|
|||
});
|
||||
|
||||
if (!apps) {
|
||||
console.log('No apps to try for this hostname');
|
||||
console.log(vhostsMap[req.hostname]);
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
//console.log(apps);
|
||||
|
||||
function nextify(err) {
|
||||
var route;
|
||||
|
||||
|
|
|
@ -109,8 +109,6 @@ function getVhostsMap(config) {
|
|||
vhosts.sort(sortApps);
|
||||
|
||||
vhosts.forEach(function (domain) {
|
||||
console.log(domain.hostname, domain.pathname, domain.dirname);
|
||||
|
||||
if (!vhostsMap[domain.hostname]) {
|
||||
vhostsMap[domain.hostname] = { pathnamesMap: {}, pathnames: [] };
|
||||
}
|
||||
|
@ -129,7 +127,6 @@ function getVhostsMap(config) {
|
|||
module.exports.deserialize = deserialize;
|
||||
module.exports.getVhostsMap = getVhostsMap;
|
||||
module.exports.create = function (db) {
|
||||
console.log('[DB -1]');
|
||||
var wrap = require('dbwrap');
|
||||
|
||||
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');
|
||||
|
||||
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) {
|
||||
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
|
||||
|
||||
function scrubTheDub(req, res, next) {
|
||||
console.log('[no-www]', req.method, req.url);
|
||||
var host = req.hostname;
|
||||
|
||||
if (!host || 'string' !== typeof host) {
|
||||
|
@ -84,11 +91,23 @@ module.exports.create = function (webserver, info, state) {
|
|||
require('./no-www').scrubTheDub(req, res);
|
||||
}
|
||||
|
||||
if (info.trustProxy) {
|
||||
app.set('trust proxy', ['loopback']);
|
||||
//app.set('trust proxy', function (ip) { ... });
|
||||
function caddyBugfix(req, res, next) {
|
||||
// workaround for Caddy
|
||||
// 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('/', caddyBugfix);
|
||||
|
||||
return PromiseA.all([
|
||||
cstore.create({
|
||||
|
@ -137,7 +156,6 @@ module.exports.create = function (webserver, info, state) {
|
|||
*/
|
||||
|
||||
function handleApi(req, res, next) {
|
||||
console.log('[API]', req.method, req.url);
|
||||
var myApp;
|
||||
|
||||
if (!/^\/api/.test(req.url)) {
|
||||
|
@ -155,8 +173,8 @@ module.exports.create = function (webserver, info, state) {
|
|||
|
||||
if (apiHandler) {
|
||||
if (apiHandler.then) {
|
||||
apiHandler.then(function (app) {
|
||||
app(req, res, next);
|
||||
apiHandler.then(function (myApp) {
|
||||
myApp(req, res, next);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -167,12 +185,16 @@ module.exports.create = function (webserver, info, state) {
|
|||
|
||||
// apiHandler = require('./vhost-server').create(info.localPort, vhostsdir).create(webserver, app)
|
||||
myApp = express();
|
||||
if (app.get('trust proxy')) {
|
||||
myApp.set('trust proxy', app.get('trust proxy'));
|
||||
}
|
||||
apiHandler = require('./api-server').create(
|
||||
{ apppath: '../packages/apps/'
|
||||
, apipath: '../packages/apis/'
|
||||
, vhostsMap: vhostsMap
|
||||
, server: webserver
|
||||
, externalPort: info.externalPort
|
||||
, apiPrefix: '/api'
|
||||
}
|
||||
, { app: myApp
|
||||
, memstore: memstore
|
||||
|
@ -185,11 +207,6 @@ module.exports.create = function (webserver, info, state) {
|
|||
}
|
||||
).api;
|
||||
|
||||
// TODO
|
||||
// X-Forwarded-For
|
||||
// X-Forwarded-Proto
|
||||
console.log('api server', req.hostname, req.secure, req.ip);
|
||||
|
||||
apiHandler(req, res, next);
|
||||
}
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
"escape-string-regexp": "1.x",
|
||||
"etag": "^1.5.1",
|
||||
"express": "4.x",
|
||||
"express-lazy": "1.x",
|
||||
"express-lazy": "^1.1.1",
|
||||
"express-session": "^1.11.3",
|
||||
"finalhandler": "^0.3.4",
|
||||
"foreachasync": "5.x",
|
||||
|
|
|
@ -20,7 +20,7 @@ Math.random = function () {
|
|||
};
|
||||
|
||||
if (cluster.isMaster) {
|
||||
require('./master');
|
||||
require('./boot/master');
|
||||
} 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