small api rework and bugfixes
This commit is contained in:
parent
8d464d6810
commit
af7c75a0f7
43
demo.js
43
demo.js
|
@ -1,37 +1,38 @@
|
|||
"use strict";
|
||||
|
||||
var Greenlock = require("./");
|
||||
var greenlockOptions = {
|
||||
cluster: false,
|
||||
require("./")
|
||||
.init(initialize)
|
||||
.serve(worker)
|
||||
.master(function() {
|
||||
console.log("Hello from master");
|
||||
});
|
||||
|
||||
serverId: "bowie.local",
|
||||
servername: "foo-gl.test.utahrust.com",
|
||||
maintainerEmail: "greenlock-test@rootprojects.org",
|
||||
function initialize() {
|
||||
var pkg = require("./package.json");
|
||||
var config = {
|
||||
package: pkg,
|
||||
//serverId: "bowie.local",
|
||||
//servername: "foo-gl.test.utahrust.com",
|
||||
staging: true,
|
||||
|
||||
/*
|
||||
manager: {
|
||||
module: "greenlock-manager-sequelize",
|
||||
dbUrl: "postgres://foo@bar:baz/quux"
|
||||
}
|
||||
*/
|
||||
|
||||
challenges: {
|
||||
"dns-01": {
|
||||
module: "acme-dns-01-digitalocean"
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
Greenlock.create(greenlockOptions)
|
||||
.worker(function(glx) {
|
||||
notify: function (ev, params) {
|
||||
console.log(ev, params);
|
||||
}
|
||||
};
|
||||
return config;
|
||||
}
|
||||
|
||||
function worker(glx) {
|
||||
console.info();
|
||||
console.info("Hello from worker");
|
||||
|
||||
glx.serveApp(function(req, res) {
|
||||
res.end("Hello, Encrypted World!");
|
||||
});
|
||||
})
|
||||
.master(function() {
|
||||
console.log("Hello from master");
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,14 +1,32 @@
|
|||
"use strict";
|
||||
|
||||
require("./lib/compat");
|
||||
var cluster = require("cluster");
|
||||
|
||||
// Greenlock Express
|
||||
var GLE = module.exports;
|
||||
|
||||
// opts.approveDomains(options, certs, cb)
|
||||
GLE.create = function(opts) {
|
||||
if (!opts) {
|
||||
opts = {};
|
||||
// Node's cluster is awesome, because it encourages writing scalable services.
|
||||
//
|
||||
// The point of this provide an API that is consistent between single-process
|
||||
// and multi-process services so that beginners can more easily take advantage
|
||||
// of what cluster has to offer.
|
||||
//
|
||||
// This API provides just enough abstraction to make it easy, but leaves just
|
||||
// enough hoopla so that there's not a large gap in understanding what happens
|
||||
// under the hood. That's the hope, anyway.
|
||||
|
||||
GLE.init = function(fn) {
|
||||
if (cluster.isWorker) {
|
||||
// ignore the init function and launch the worker
|
||||
return require("./worker.js").create();
|
||||
}
|
||||
|
||||
var opts = fn();
|
||||
if (!opts || "object" !== typeof opts) {
|
||||
throw new Error(
|
||||
"the `Greenlock.init(fn)` function should return an object `{ maintainerEmail, packageAgent, notify }`"
|
||||
);
|
||||
}
|
||||
|
||||
// just for ironic humor
|
||||
|
@ -18,12 +36,9 @@ GLE.create = function(opts) {
|
|||
}
|
||||
});
|
||||
|
||||
// we want to be minimal, and only load the code that's necessary to load
|
||||
if (opts.cluster) {
|
||||
if (require("cluster").isMaster) {
|
||||
return require("./master.js").create(opts);
|
||||
}
|
||||
return require("./worker.js").create(opts);
|
||||
}
|
||||
|
||||
return require("./single.js").create(opts);
|
||||
};
|
||||
|
|
59
greenlock.js
59
greenlock.js
|
@ -1,18 +1,13 @@
|
|||
"use strict";
|
||||
|
||||
var pkg = require("./package.json");
|
||||
|
||||
module.exports.create = function(opts) {
|
||||
var Greenlock = require("@root/greenlock");
|
||||
var packageAgent = pkg.name + "/" + pkg.version;
|
||||
if ("string" === typeof opts.packageAgent) {
|
||||
opts.packageAgent += " ";
|
||||
} else {
|
||||
opts.packageAgent = "";
|
||||
}
|
||||
opts.packageAgent += packageAgent;
|
||||
opts = parsePackage(opts);
|
||||
opts.packageAgent = addGreenlockAgent(opts);
|
||||
|
||||
var Greenlock = require("@root/greenlock");
|
||||
var greenlock = Greenlock.create(opts);
|
||||
|
||||
// TODO move to greenlock proper
|
||||
greenlock.getAcmeHttp01ChallengeResponse = function(opts) {
|
||||
return greenlock.find({ servername: opts.servername }).then(function(sites) {
|
||||
if (!sites.length) {
|
||||
|
@ -45,3 +40,47 @@ module.exports.create = function(opts) {
|
|||
|
||||
return greenlock;
|
||||
};
|
||||
|
||||
function addGreenlockAgent(opts) {
|
||||
// Add greenlock as part of Agent, unless this is greenlock
|
||||
if (!/^greenlock(-express|-pro)?/.test(opts.packageAgent)) {
|
||||
var pkg = require("./package.json");
|
||||
var packageAgent = pkg.name + "/" + pkg.version;
|
||||
opts.packageAgent += " " + packageAgent;
|
||||
}
|
||||
|
||||
return opts.packageAgent;
|
||||
}
|
||||
|
||||
// ex: John Doe <john@example.com> (https://john.doe)
|
||||
var looseEmailRe = /.* <([^'" <>:;`]+@[^'" <>:;`]+\.[^'" <>:;`]+)> .*/;
|
||||
function parsePackage(opts) {
|
||||
// 'package' is sometimes a reserved word
|
||||
var pkg = opts.package || opts.pkg;
|
||||
if (!pkg) {
|
||||
return opts;
|
||||
}
|
||||
|
||||
if (!opts.packageAgent) {
|
||||
var err = "missing `package.THING`, which is used for the ACME client user agent string";
|
||||
if (!pkg.name) {
|
||||
throw new Error(err.replace("THING", "name"));
|
||||
}
|
||||
if (!pkg.version) {
|
||||
throw new Error(err.replace("THING", "version"));
|
||||
}
|
||||
opts.packageAgent = pkg.name + "/" + pkg.version;
|
||||
}
|
||||
|
||||
if (!opts.maintainerEmail) {
|
||||
try {
|
||||
opts.maintainerEmail = pkg.author.email || pkg.author.match(looseEmailRe)[1];
|
||||
} catch (e) {}
|
||||
}
|
||||
if (!opts.maintainerEmail) {
|
||||
throw new Error("missing or malformed `package.author`, which is used as the contact for support notices");
|
||||
}
|
||||
opts.package = undefined;
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
|
95
master.js
95
master.js
|
@ -6,9 +6,9 @@ var Master = module.exports;
|
|||
|
||||
var cluster = require("cluster");
|
||||
var os = require("os");
|
||||
var msgPrefix = "greenlock:";
|
||||
|
||||
Master.create = function(opts) {
|
||||
var workers = [];
|
||||
var resolveCb;
|
||||
var readyCb;
|
||||
var _kicked = false;
|
||||
|
@ -27,19 +27,16 @@ Master.create = function(opts) {
|
|||
}
|
||||
_kicked = true;
|
||||
|
||||
console.log("TODO: start the workers and such...");
|
||||
// handle messages from workers
|
||||
workers.push(null);
|
||||
Master._spawnWorkers(opts, greenlock);
|
||||
|
||||
ready.then(function(fn) {
|
||||
// not sure what this API should be yet
|
||||
fn({
|
||||
//workers: workers.slice(0)
|
||||
});
|
||||
fn();
|
||||
});
|
||||
}
|
||||
|
||||
var master = {
|
||||
worker: function() {
|
||||
serve: function() {
|
||||
kickoff();
|
||||
return master;
|
||||
},
|
||||
|
@ -54,22 +51,19 @@ Master.create = function(opts) {
|
|||
};
|
||||
};
|
||||
|
||||
// opts.approveDomains(options, certs, cb)
|
||||
GLE.create = function(opts) {
|
||||
GLE._spawnWorkers(opts);
|
||||
|
||||
gl.tlsOptions = {};
|
||||
|
||||
return master;
|
||||
};
|
||||
|
||||
function range(n) {
|
||||
n = parseInt(n, 10);
|
||||
if (!n) {
|
||||
return [];
|
||||
}
|
||||
return new Array(n).join(",").split(",");
|
||||
}
|
||||
|
||||
Master._spawnWorkers = function(opts) {
|
||||
Master._spawnWorkers = function(opts, greenlock) {
|
||||
var numCpus = parseInt(process.env.NUMBER_OF_PROCESSORS, 10) || os.cpus().length;
|
||||
|
||||
// process rpc messages
|
||||
// start when dead
|
||||
var numWorkers = parseInt(opts.numWorkers, 10);
|
||||
if (!numWorkers) {
|
||||
if (numCpus <= 2) {
|
||||
|
@ -79,7 +73,68 @@ Master._spawnWorkers = function(opts) {
|
|||
}
|
||||
}
|
||||
|
||||
return range(numWorkers).map(function() {
|
||||
return cluster.fork();
|
||||
return range(numWorkers - 1).map(function() {
|
||||
Master._spawnWorker(opts, greenlock);
|
||||
});
|
||||
};
|
||||
|
||||
Master._spawnWorker = function(opts, greenlock) {
|
||||
var w = cluster.fork();
|
||||
// automatically added to master's `cluster.workers`
|
||||
w.on("exit", function(code, signal) {
|
||||
// TODO handle failures
|
||||
// Should test if the first starts successfully
|
||||
// Should exit if failures happen too quickly
|
||||
|
||||
// For now just kill all when any die
|
||||
if (signal) {
|
||||
console.error("worker was killed by signal:", signal);
|
||||
} else if (code !== 0) {
|
||||
console.error("worker exited with error code:", code);
|
||||
} else {
|
||||
console.error("worker unexpectedly quit without exit code or signal");
|
||||
}
|
||||
process.exit(2);
|
||||
|
||||
//addWorker();
|
||||
});
|
||||
|
||||
function handleMessage(msg) {
|
||||
if (0 !== (msg._id || "").indexOf(msgPrefix)) {
|
||||
return;
|
||||
}
|
||||
if ("string" !== typeof msg._funcname) {
|
||||
// TODO developer error
|
||||
return;
|
||||
}
|
||||
|
||||
function rpc() {
|
||||
return greenlock[msg._funcname](msg._input)
|
||||
.then(function(result) {
|
||||
w.send({
|
||||
_id: msg._id,
|
||||
_result: result
|
||||
});
|
||||
})
|
||||
.catch(function(e) {
|
||||
var error = new Error(e.message);
|
||||
Object.getOwnPropertyNames(e).forEach(function(k) {
|
||||
error[k] = e[k];
|
||||
});
|
||||
w.send({
|
||||
_id: msg._id,
|
||||
_error: error
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
rpc();
|
||||
} catch (e) {
|
||||
console.error("Unexpected and uncaught greenlock." + msg._funcname + " error:");
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
w.on("message", handleMessage);
|
||||
};
|
||||
|
|
32
servers.js
32
servers.js
|
@ -6,6 +6,7 @@ var http = require("http");
|
|||
var HttpMiddleware = require("./http-middleware.js");
|
||||
var HttpsMiddleware = require("./https-middleware.js");
|
||||
var sni = require("./sni.js");
|
||||
var cluster = require("cluster");
|
||||
|
||||
Servers.create = function(greenlock, opts) {
|
||||
var servers = {};
|
||||
|
@ -22,14 +23,24 @@ Servers.create = function(greenlock, opts) {
|
|||
return _httpServer;
|
||||
}
|
||||
|
||||
_httpServer = http.createServer(HttpMiddleware.create(opts.greenlock, defaultApp));
|
||||
_httpServer = http.createServer(HttpMiddleware.create(greenlock, defaultApp));
|
||||
_httpServer.once("error", startError);
|
||||
|
||||
return _httpServer;
|
||||
};
|
||||
|
||||
var _middlewareApp;
|
||||
|
||||
servers.httpsServer = function(secureOpts, defaultApp) {
|
||||
if (defaultApp) {
|
||||
// TODO guard against being set twice?
|
||||
_middlewareApp = defaultApp;
|
||||
}
|
||||
|
||||
if (_httpsServer) {
|
||||
if (secureOpts && Object.keys(secureOpts)) {
|
||||
throw new Error("Call glx.httpsServer(tlsOptions) before calling glx.serveApp(app)");
|
||||
}
|
||||
return _httpsServer;
|
||||
}
|
||||
|
||||
|
@ -39,7 +50,12 @@ Servers.create = function(greenlock, opts) {
|
|||
|
||||
_httpsServer = createSecureServer(
|
||||
wrapDefaultSniCallback(opts, greenlock, secureOpts),
|
||||
HttpsMiddleware.create(greenlock, defaultApp)
|
||||
HttpsMiddleware.create(greenlock, function(req, res) {
|
||||
if (!_middlewareApp) {
|
||||
throw new Error("Set app with `glx.serveApp(app)` or `glx.httpsServer(tlsOptions, app)`");
|
||||
}
|
||||
_middlewareApp(req, res);
|
||||
})
|
||||
);
|
||||
_httpsServer.once("error", startError);
|
||||
|
||||
|
@ -53,18 +69,25 @@ Servers.create = function(greenlock, opts) {
|
|||
return;
|
||||
}
|
||||
|
||||
var id = cluster.isWorker && cluster.worker.id;
|
||||
var idstr = (id && "$" + id + " ") || "";
|
||||
var plainServer = servers.httpServer(require("redirect-https")());
|
||||
var plainAddr = "0.0.0.0";
|
||||
var plainPort = 80;
|
||||
plainServer.listen(plainPort, plainAddr, function() {
|
||||
console.info("Listening on", plainAddr + ":" + plainPort, "for ACME challenges, and redirecting to HTTPS");
|
||||
console.info(
|
||||
idstr + "Listening on",
|
||||
plainAddr + ":" + plainPort,
|
||||
"for ACME challenges, and redirecting to HTTPS"
|
||||
);
|
||||
|
||||
// TODO fetch greenlock.servername
|
||||
_middlewareApp = app || _middlewareApp;
|
||||
var secureServer = servers.httpsServer({}, app);
|
||||
var secureAddr = "0.0.0.0";
|
||||
var securePort = 443;
|
||||
secureServer.listen(securePort, secureAddr, function() {
|
||||
console.info("Listening on", secureAddr + ":" + securePort, "for secure traffic");
|
||||
console.info(idstr + "Listening on", secureAddr + ":" + securePort, "for secure traffic");
|
||||
|
||||
plainServer.removeListener("error", startError);
|
||||
secureServer.removeListener("error", startError);
|
||||
|
@ -73,6 +96,7 @@ Servers.create = function(greenlock, opts) {
|
|||
});
|
||||
});
|
||||
};
|
||||
|
||||
return servers;
|
||||
};
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ Single.create = function(opts) {
|
|||
var servers = Servers.create(greenlock, opts);
|
||||
|
||||
var single = {
|
||||
worker: function(fn) {
|
||||
serve: function(fn) {
|
||||
fn(servers);
|
||||
return single;
|
||||
},
|
||||
|
|
4
sni.js
4
sni.js
|
@ -14,7 +14,7 @@ var smallStagger = Math.round(Math.PI * (30 * 1000));
|
|||
//secureOpts.SNICallback = sni.create(opts, greenlock, secureOpts);
|
||||
sni.create = function(opts, greenlock, secureOpts) {
|
||||
var _cache = {};
|
||||
var defaultServername = opts.servername || greenlock.servername;
|
||||
var defaultServername = opts.servername || greenlock.servername || "";
|
||||
|
||||
if (secureOpts.cert) {
|
||||
// Note: it's fine if greenlock.servername is undefined,
|
||||
|
@ -157,6 +157,8 @@ sni.create = function(opts, greenlock, secureOpts) {
|
|||
[match.altnames || site.altnames || [match.subject || site.subject]].forEach(function(altname) {
|
||||
_cache[altname] = meta;
|
||||
});
|
||||
|
||||
return meta.secureContext;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
60
worker.js
60
worker.js
|
@ -1,18 +1,21 @@
|
|||
"use strict";
|
||||
|
||||
var Worker = module.exports;
|
||||
// *very* generous, but well below the http norm of 120
|
||||
var messageTimeout = 30 * 1000;
|
||||
var msgPrefix = 'greenlock:';
|
||||
|
||||
Worker.create = function(opts) {
|
||||
var greenlock = {
|
||||
// rename presentChallenge?
|
||||
getAcmeHttp01ChallengeResponse: presentChallenge,
|
||||
notify: notifyMaster,
|
||||
get: greenlockRenew
|
||||
Worker.create = function() {
|
||||
var greenlock = {};
|
||||
["getAcmeHttp01ChallengeResponse", "renew", "notify"].forEach(function(k) {
|
||||
greenlock[k] = function(args) {
|
||||
return rpc(k, args);
|
||||
};
|
||||
});
|
||||
|
||||
var worker = {
|
||||
worker: function(fn) {
|
||||
var servers = require("./servers.js").create(greenlock, opts);
|
||||
serve: function(fn) {
|
||||
var servers = require("./servers.js").create(greenlock);
|
||||
fn(servers);
|
||||
return worker;
|
||||
},
|
||||
|
@ -24,51 +27,32 @@ Worker.create = function(opts) {
|
|||
return worker;
|
||||
};
|
||||
|
||||
function greenlockRenew(args) {
|
||||
return request("renew", {
|
||||
servername: args.servername
|
||||
});
|
||||
}
|
||||
|
||||
function presentChallenge(args) {
|
||||
return request("challenge-response", {
|
||||
servername: args.servername,
|
||||
token: args.token
|
||||
});
|
||||
}
|
||||
|
||||
function request(typename, msg) {
|
||||
function rpc(funcname, msg) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var rnd = Math.random()
|
||||
.slice(2)
|
||||
.toString(16);
|
||||
var id = "greenlock:" + rnd;
|
||||
var id = msgPrefix + rnd;
|
||||
var timeout;
|
||||
|
||||
function getResponse(msg) {
|
||||
if (msg.id !== id) {
|
||||
if (msg._id !== id) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(timeout);
|
||||
resolve(msg);
|
||||
resolve(msg._result);
|
||||
}
|
||||
|
||||
process.on("message", getResponse);
|
||||
msg.id = msg;
|
||||
msg.type = typename;
|
||||
process.send(msg);
|
||||
process.send({
|
||||
_id: id,
|
||||
_funcname: funcname,
|
||||
_input: msg
|
||||
});
|
||||
|
||||
timeout = setTimeout(function() {
|
||||
process.removeListener("message", getResponse);
|
||||
reject(new Error("process message timeout"));
|
||||
}, 30 * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
function notifyMaster(ev, args) {
|
||||
process.on("message", {
|
||||
type: "notification",
|
||||
event: ev,
|
||||
parameters: args
|
||||
reject(new Error("worker rpc request timeout"));
|
||||
}, messageTimeout);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue