walnut.js/boot/worker.js

170 lines
5.5 KiB
JavaScript

'use strict';
module.exports.create = function (opts) {
var id = '0';
var promiseApp;
function createAndBindInsecure(lex, message, cb) {
var webserver = require('http').createServer(lex.middleware(require('redirect-https')())).listen(message.conf.insecurePort, function () {
console.log('handling http-01 ACME challenges on port', message.conf.insecurePort);
cb(null, webserver, null, message);
});
/*
// TODO conditional if 80 is being served by caddy
require('../lib/insecure-server').create(lex, message.conf.externalPort, message.conf.insecurePort, message, function (err, webserver) {
console.info("#" + id + " Listening on http://" + webserver.address().address + ":" + webserver.address().port, '\n');
// we are returning the promise result to the caller
return cb(null, webserver, null, message);
});
*/
}
function createLe(conf) {
var LEX = require('letsencrypt-express');
var lex = LEX.create({
server: 'https://acme-v01.api.letsencrypt.org/directory'
//server: 'staging'
, configDir: conf.letsencrypt.configDir // i.e. __dirname + '/letsencrypt.config'
, webrootPath: '/tmp/acme-challenges'
, approveDomains: function (opts, certs, cb) {
opts.email = conf.letsencrypt.email;
opts.agreeTos = conf.letsencrypt.agreeTos;
opts.domains = certs && certs.altnames || opts.domains;
cb(null, { options: opts, certs: certs });
}
});
//var letsencrypt = lex.letsencrypt;
return lex;
}
function createAndBindServers(message, cb) {
var lex;
if (message.conf.letsencrypt) {
lex = createLe(message.conf);
}
// NOTE that message.conf[x] will be overwritten when the next message comes in
require('../lib/local-server').create(lex, message.conf.certPaths, message.conf.localPort, message, function (err, webserver) {
if (err) {
console.error('[ERROR] worker.js');
console.error(err.stack);
throw err;
}
console.info("#" + id + " Listening on " + message.conf.protocol + "://" + webserver.address().address + ":" + webserver.address().port, '\n');
// we don't need time to pass, just to be able to return
process.nextTick(function () {
createAndBindInsecure(lex, message, cb);
});
// we are returning the promise result to the caller
return cb(null, null, webserver, message);
});
}
//
// Worker Mode
//
function waitForConfig(message) {
if ('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 (err, insecserver, webserver, oldMessage) {
// TODO deep merge new message into old message
Object.keys(message.conf).forEach(function (key) {
oldMessage.conf[key] = message.conf[key];
});
var PromiseA = require('bluebird');
if (promiseApp) {
return promiseApp;
}
promiseApp = new PromiseA(function (resolve) {
function initWebServer(srvmsg) {
if ('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: 'walnut.webserver.listening' });
process.on('message', initWebServer);
}).then(function (app) {
console.info('[Worker Ready]');
return app;
});
return promiseApp;
});
}
//
// Standalone Mode
//
if (opts) {
// NOTE: this callback must return a promise for an express app
createAndBindServers(opts, function (err, insecserver, webserver/*, message*/) {
var PromiseA = require('bluebird');
if (promiseApp) {
return promiseApp;
}
promiseApp = new PromiseA(function (resolve) {
opts.getConfig(function (srvmsg) {
resolve(require('../lib/worker').create(webserver, srvmsg));
});
}).then(function (app) {
console.info('[Standalone Ready]');
return app;
});
return promiseApp;
});
} 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);
});
};