2017-04-27 22:05:34 +00:00
|
|
|
'use strict';
|
|
|
|
|
2017-05-10 22:05:54 +00:00
|
|
|
module.exports.create = function (deps, conf, greenlockMiddleware) {
|
2017-05-09 22:50:07 +00:00
|
|
|
var express = require('express');
|
|
|
|
var app = express();
|
2017-05-09 21:46:49 +00:00
|
|
|
var adminApp = require('./admin').create(deps, conf);
|
2017-05-09 20:16:21 +00:00
|
|
|
var domainMatches = require('../match-domain').match;
|
2017-05-16 07:20:02 +00:00
|
|
|
var proxyRoutes = [];
|
2017-05-09 20:16:21 +00:00
|
|
|
|
2017-05-09 21:46:49 +00:00
|
|
|
var adminDomains = [
|
|
|
|
/\blocalhost\.admin\./
|
|
|
|
, /\blocalhost\.alpha\./
|
|
|
|
, /\badmin\.localhost\./
|
|
|
|
, /\balpha\.localhost\./
|
|
|
|
];
|
|
|
|
|
2017-05-12 01:16:23 +00:00
|
|
|
function verifyHost(fullHost) {
|
|
|
|
var host = /^(.*?)(:\d+)?$/.exec(fullHost)[1];
|
|
|
|
|
|
|
|
if (host === 'localhost') {
|
|
|
|
return fullHost.replace(host, 'localhost.daplie.me');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test for IPv4 and IPv6 addresses. These patterns will match some invalid addresses,
|
|
|
|
// but since those still won't be valid domains that won't really be a problem.
|
|
|
|
if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host) || /^\[[0-9a-fA-F:]+\]$/.test(host)) {
|
|
|
|
if (!conf.http.primaryDomain) {
|
|
|
|
(conf.http.modules || []).some(function (mod) {
|
|
|
|
return mod.domains.some(function (domain) {
|
|
|
|
if (domain[0] !== '*') {
|
|
|
|
conf.http.primaryDomain = domain;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return fullHost.replace(host, conf.http.primaryDomain || host);
|
|
|
|
}
|
|
|
|
|
|
|
|
return fullHost;
|
|
|
|
}
|
|
|
|
|
2017-05-09 20:16:21 +00:00
|
|
|
// We handle both HTTPS and HTTP traffic on the same ports, and we want to redirect
|
|
|
|
// any unencrypted requests to the same port they came from unless it came in on
|
|
|
|
// the default HTTP port, in which case there wont be a port specified in the host.
|
|
|
|
var redirecters = {};
|
|
|
|
function redirectHttps(req, res, next) {
|
2017-05-12 01:16:23 +00:00
|
|
|
if (conf.http.allowInsecure) {
|
|
|
|
next();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var port = (/:(\d+)$/.exec(req.headers.host) || [])[1];
|
2017-05-10 22:05:54 +00:00
|
|
|
if (!redirecters[port]) {
|
|
|
|
redirecters[port] = require('redirect-https')({
|
|
|
|
port: port
|
|
|
|
, trustProxy: conf.http.trustProxy
|
|
|
|
});
|
2017-05-09 20:16:21 +00:00
|
|
|
}
|
2017-05-12 01:16:23 +00:00
|
|
|
|
|
|
|
// localhost and IP addresses cannot have real SSL certs (and don't contain any useful
|
|
|
|
// info for redirection either), so we direct some hosts to either localhost.daplie.me
|
|
|
|
// or the "primary domain" ie the first manually specified domain.
|
|
|
|
req.headers.host = verifyHost(req.headers.host);
|
|
|
|
|
2017-05-10 22:05:54 +00:00
|
|
|
redirecters[port](req, res, next);
|
2017-05-09 20:16:21 +00:00
|
|
|
}
|
|
|
|
|
2017-05-09 21:46:49 +00:00
|
|
|
function handleAdmin(req, res, next) {
|
|
|
|
var admin = adminDomains.some(function (re) {
|
|
|
|
return re.test(req.headers.host);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (admin) {
|
|
|
|
adminApp(req, res);
|
|
|
|
} else {
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-09 20:16:21 +00:00
|
|
|
function respond404(req, res) {
|
|
|
|
res.writeHead(404);
|
|
|
|
res.end('Not Found');
|
|
|
|
}
|
|
|
|
|
|
|
|
function createProxyRoute(mod) {
|
|
|
|
// This is the easiest way to override the createConnections function the proxy
|
|
|
|
// module uses, but take note the since we don't have control over where this is
|
|
|
|
// called the extra options availabled will be different.
|
|
|
|
var agent = new require('http').Agent({});
|
|
|
|
agent.createConnection = deps.net.createConnection;
|
|
|
|
|
|
|
|
var proxy = require('http-proxy').createProxyServer({
|
|
|
|
agent: agent
|
|
|
|
, target: 'http://' + mod.address
|
|
|
|
, xfwd: true
|
|
|
|
, toProxy: true
|
2017-05-06 07:08:10 +00:00
|
|
|
});
|
2017-04-27 22:05:34 +00:00
|
|
|
|
2017-05-09 21:46:49 +00:00
|
|
|
// We want to override the default value for some headers with the extra information we
|
|
|
|
// have available to us in the opts object attached to the connection.
|
|
|
|
proxy.on('proxyReq', function (proxyReq, req) {
|
|
|
|
var conn = req.connection;
|
|
|
|
var opts = conn.__opts;
|
|
|
|
proxyReq.setHeader('X-Forwarded-For', opts.remoteAddress || conn.remoteAddress);
|
|
|
|
});
|
|
|
|
|
2017-05-09 22:23:30 +00:00
|
|
|
proxy.on('error', function (err, req, res) {
|
|
|
|
console.log(err);
|
2017-05-11 22:42:14 +00:00
|
|
|
res.statusCode = 502;
|
|
|
|
res.setHeader('Content-Type', 'text/html');
|
|
|
|
res.setHeader('Connection', 'close');
|
|
|
|
res.end(require('../proxy-err-resp').getRespBody(err, conf.debug));
|
2017-05-09 22:23:30 +00:00
|
|
|
});
|
|
|
|
|
2017-05-16 07:20:02 +00:00
|
|
|
return {
|
|
|
|
web: function (req, res, next) {
|
|
|
|
var hostname = req.headers.host.split(':')[0];
|
|
|
|
var relevant = mod.domains.some(function (pattern) {
|
|
|
|
return domainMatches(pattern, hostname);
|
|
|
|
});
|
2017-05-09 20:16:21 +00:00
|
|
|
|
2017-05-16 07:20:02 +00:00
|
|
|
if (relevant) {
|
|
|
|
proxy.web(req, res);
|
|
|
|
} else {
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
, ws: function (req, socket, head, next) {
|
|
|
|
var hostname = req.headers.host.split(':')[0];
|
|
|
|
var relevant = mod.domains.some(function (pattern) {
|
|
|
|
return domainMatches(pattern, hostname);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (relevant) {
|
|
|
|
proxy.ws(req, socket, head);
|
|
|
|
} else {
|
|
|
|
next();
|
|
|
|
}
|
2017-05-09 20:16:21 +00:00
|
|
|
}
|
|
|
|
};
|
2017-05-06 07:08:10 +00:00
|
|
|
}
|
2017-04-27 22:05:34 +00:00
|
|
|
|
2017-05-09 22:50:07 +00:00
|
|
|
function createStaticRoute(mod) {
|
|
|
|
var getStaticApp, staticApp;
|
|
|
|
if (/:hostname/.test(mod.root)) {
|
|
|
|
staticApp = {};
|
|
|
|
getStaticApp = function (hostname) {
|
|
|
|
if (!staticApp[hostname]) {
|
|
|
|
staticApp[hostname] = express.static(mod.root.replace(':hostname', hostname));
|
|
|
|
}
|
|
|
|
return staticApp[hostname];
|
|
|
|
};
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
staticApp = express.static(mod.root);
|
|
|
|
getStaticApp = function () {
|
|
|
|
return staticApp;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return function (req, res, next) {
|
|
|
|
var hostname = req.headers.host.split(':')[0];
|
|
|
|
var relevant = mod.domains.some(function (pattern) {
|
|
|
|
return domainMatches(pattern, hostname);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (relevant) {
|
|
|
|
getStaticApp(hostname)(req, res, next);
|
|
|
|
} else {
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-05-10 22:05:54 +00:00
|
|
|
app.use(greenlockMiddleware);
|
2017-05-09 20:16:21 +00:00
|
|
|
app.use(redirectHttps);
|
2017-05-09 21:46:49 +00:00
|
|
|
app.use(handleAdmin);
|
2017-05-09 20:16:21 +00:00
|
|
|
|
|
|
|
(conf.http.modules || []).forEach(function (mod) {
|
|
|
|
if (mod.name === 'proxy') {
|
2017-05-16 07:20:02 +00:00
|
|
|
var proxyRoute = createProxyRoute(mod);
|
|
|
|
proxyRoutes.push(proxyRoute);
|
|
|
|
app.use(proxyRoute.web);
|
2017-05-06 07:08:10 +00:00
|
|
|
}
|
2017-05-09 22:50:07 +00:00
|
|
|
else if (mod.name === 'static') {
|
|
|
|
app.use(createStaticRoute(mod));
|
|
|
|
}
|
2017-05-09 20:16:21 +00:00
|
|
|
else {
|
|
|
|
console.warn('unknown HTTP module', mod);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
app.use(respond404);
|
2017-05-16 07:20:02 +00:00
|
|
|
|
|
|
|
var server = require('http').createServer(function (req, res) {
|
|
|
|
app(req, res)
|
|
|
|
});
|
|
|
|
|
|
|
|
server.on('upgrade', function (req, socket, head) {
|
|
|
|
if (!proxyRoutes.length) {
|
|
|
|
socket.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
function proxyWs() {
|
|
|
|
var proxyRoute = proxyRoutes.shift();
|
|
|
|
if (!proxyRoute) {
|
|
|
|
socket.end();
|
|
|
|
}
|
|
|
|
proxyRoute.ws(req, socket, head, proxyWs);
|
|
|
|
}
|
|
|
|
|
|
|
|
proxyWs();
|
|
|
|
});
|
|
|
|
|
|
|
|
return server;
|
2017-04-27 22:05:34 +00:00
|
|
|
};
|