'use strict'; module.exports.create = function (deps, conf, greenlockMiddleware) { var express = require('express'); var app = express(); var adminApp = require('./admin').create(deps, conf); var domainMatches = require('../match-domain').match; var proxyRoutes = []; var adminDomains = [ /\blocalhost\.admin\./ , /\blocalhost\.alpha\./ , /\badmin\.localhost\./ , /\balpha\.localhost\./ ]; 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; } // 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) { if (conf.http.allowInsecure) { next(); return; } var port = (/:(\d+)$/.exec(req.headers.host) || [])[1]; if (!redirecters[port]) { redirecters[port] = require('redirect-https')({ port: port , trustProxy: conf.http.trustProxy }); } // 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); redirecters[port](req, res, next); } function handleAdmin(req, res, next) { var admin = adminDomains.some(function (re) { return re.test(req.headers.host); }); if (admin) { adminApp(req, res); } else { next(); } } 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 }); // 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); }); proxy.on('error', function (err, req, res) { console.log(err); res.statusCode = 502; res.setHeader('Content-Type', 'text/html'); res.setHeader('Connection', 'close'); res.end(require('../proxy-err-resp').getRespBody(err, conf.debug)); }); return { web: function (req, res, next) { var hostname = req.headers.host.split(':')[0]; var relevant = mod.domains.some(function (pattern) { return domainMatches(pattern, hostname); }); 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(); } } }; } 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(); } }; } app.use(greenlockMiddleware); app.use(redirectHttps); app.use(handleAdmin); (conf.http.modules || []).forEach(function (mod) { if (mod.name === 'proxy') { var proxyRoute = createProxyRoute(mod); proxyRoutes.push(proxyRoute); app.use(proxyRoute.web); } else if (mod.name === 'static') { app.use(createStaticRoute(mod)); } else { console.warn('unknown HTTP module', mod); } }); app.use(respond404); 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; };