greenlock-express.js/servers.js

172 lines
6.0 KiB
JavaScript

"use strict";
var Servers = module.exports;
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) {
var servers = {};
var _httpServer;
var _httpsServer;
function startError(e) {
explainError(e);
process.exit(1);
}
servers.httpServer = function(defaultApp) {
if (_httpServer) {
if (defaultApp) {
console.error("error: can only call httpServer(app) once");
process.exit(1);
}
return _httpServer;
}
if (!defaultApp) {
defaultApp = require("redirect-https")();
}
// HEADERS SENT DEBUG NOTE #1
// As seen above, it's only possible to create the server once.
// It always gets the http middleware, it always gets a single default app
// Therefore it seems impossible to be an http.on('connection', app) problem
_httpServer = http.createServer(HttpMiddleware.create(greenlock, defaultApp));
_httpServer.once("error", startError);
return _httpServer;
};
var _middlewareApp;
servers.http2Server = function(secureOpts, defaultApp) {
return servers._httpsServer(secureOpts, defaultApp, function(secureOpts, fn) {
secureOpts.allowHTTP1 = true;
return require("http2").createSecureServer(secureOpts, fn);
});
};
servers.httpsServer = function(secureOpts, defaultApp) {
return servers._httpsServer(secureOpts, defaultApp, function(secureOpts, fn) {
return require("https").createServer(secureOpts, fn);
});
};
servers._httpsServer = function(secureOpts, defaultApp, createSecureServer) {
if (defaultApp) {
// TODO guard against being set twice?
_middlewareApp = defaultApp;
}
if (_httpsServer) {
if (secureOpts && Object.keys(secureOpts).length) {
throw new Error("Call glx.httpsServer(tlsOptions) before calling glx.serveApp(app)");
}
return _httpsServer;
}
if (!secureOpts) {
secureOpts = {};
}
_httpsServer = createSecureServer(
wrapDefaultSniCallback(greenlock, secureOpts),
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);
return _httpsServer;
};
servers.id = function() {
return (cluster.isWorker && cluster.worker.id) || "0";
};
servers.serveApp = function(app) {
return new Promise(function(resolve, reject) {
if ("function" !== typeof app) {
reject(
new Error(
"glx.serveApp(app) expects a node/express app in the format `function (req, res) { ... }`"
)
);
return;
}
var id = cluster.isWorker && cluster.worker.id;
var idstr = (id && "#" + id + " ") || "";
var plainServer = servers.httpServer();
var plainAddr = "0.0.0.0";
var plainPort = 80;
plainServer.listen(plainPort, plainAddr, function() {
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(null, app);
var secureAddr = "0.0.0.0";
var securePort = 443;
secureServer.listen(securePort, secureAddr, function() {
console.info(idstr + "Listening on", secureAddr + ":" + securePort, "for secure traffic");
plainServer.removeListener("error", startError);
secureServer.removeListener("error", startError);
resolve();
});
});
});
};
return servers;
};
function explainError(e) {
console.error();
console.error("Error: " + e.message);
if ("EACCES" === e.errno) {
console.error("You don't have prmission to access '" + e.address + ":" + e.port + "'.");
console.error('You probably need to use "sudo" or "sudo setcap \'cap_net_bind_service=+ep\' $(which node)"');
} else if ("EADDRINUSE" === e.errno) {
console.error("'" + e.address + ":" + e.port + "' is already being used by some other program.");
console.error("You probably need to stop that program or restart your computer.");
} else {
console.error(e.code + ": '" + e.address + ":" + e.port + "'");
}
console.error();
}
function wrapDefaultSniCallback(greenlock, secureOpts) {
// I'm not sure yet if the original SNICallback
// should be called before or after, so I'm just
// going to delay making that choice until I have the use case
/*
if (!secureOpts.SNICallback) {
secureOpts.SNICallback = function(servername, cb) {
cb(null, null);
};
}
*/
if (secureOpts.SNICallback) {
console.warn();
console.warn("[warning] Ignoring the given tlsOptions.SNICallback function.");
console.warn();
console.warn(" We're very open to implementing support for this,");
console.warn(" we just don't understand the use case yet.");
console.warn(" Please open an issue to discuss. We'd love to help.");
console.warn();
}
// TODO greenlock.servername for workers
secureOpts.SNICallback = sni.create(greenlock, secureOpts);
return secureOpts;
}