247 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			247 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| '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('../domain-utils').match;
 | |
|   var separatePort = require('../domain-utils').separatePort;
 | |
|   var proxyRoutes = [];
 | |
| 
 | |
|   var adminDomains = [
 | |
|     /\blocalhost\.admin\./
 | |
|   , /\blocalhost\.alpha\./
 | |
|   , /\badmin\.localhost\./
 | |
|   , /\balpha\.localhost\./
 | |
|   ];
 | |
| 
 | |
|   function moduleMatchesHost(req, mod) {
 | |
|     var host = separatePort(req.headers.host).host;
 | |
| 
 | |
|     return mod.domains.some(function (pattern) {
 | |
|       return domainMatches(pattern, host);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   function verifyHost(fullHost) {
 | |
|     var host = separatePort(fullHost).host;
 | |
| 
 | |
|     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 = separatePort(req.headers.host).port;
 | |
|     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) {
 | |
|         if (moduleMatchesHost(req, mod)) {
 | |
|           proxy.web(req, res);
 | |
|         } else {
 | |
|           next();
 | |
|         }
 | |
|       }
 | |
|     , ws: function (req, socket, head, next) {
 | |
|         if (moduleMatchesHost(req, mod)) {
 | |
|           proxy.ws(req, socket, head);
 | |
|         } else {
 | |
|           next();
 | |
|         }
 | |
|       }
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   function createRedirectRoute(mod) {
 | |
|     // Escape any characters that (can) have special meaning in regular expression
 | |
|     // but that aren't the special characters we have interest in.
 | |
|     var from = mod.from.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&');
 | |
|     // Then modify the characters we are interested in so they do what we want in
 | |
|     // the regular expression after being compiled.
 | |
|     from = from.replace(/\*/g, '(.*)');
 | |
|     var fromRe = new RegExp('^' + from + '/?$');
 | |
| 
 | |
|     return function (req, res, next) {
 | |
|       if (!moduleMatchesHost(req, mod)) {
 | |
|         next();
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       var match = fromRe.exec(req.url);
 | |
|       if (!match) {
 | |
|         next();
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       var to = mod.to;
 | |
|       match.slice(1).forEach(function (globMatch, index) {
 | |
|         to = to.replace(':'+(index+1), globMatch);
 | |
|       });
 | |
|       res.writeHead(mod.status || 301, { 'Location': to });
 | |
|       res.end();
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   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) {
 | |
|       if (moduleMatchesHost(req, mod)) {
 | |
|         getStaticApp(separatePort(req.headers.host).host)(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 === 'redirect') {
 | |
|       app.use(createRedirectRoute(mod));
 | |
|     }
 | |
|     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();
 | |
|     }
 | |
| 
 | |
|     var prs = proxyRoutes.slice();
 | |
|     function proxyWs() {
 | |
|       var proxyRoute = prs.shift();
 | |
|       if (!proxyRoute) {
 | |
|         socket.end();
 | |
|         return;
 | |
|       }
 | |
|       proxyRoute.ws(req, socket, head, proxyWs);
 | |
|     }
 | |
| 
 | |
|     proxyWs();
 | |
|   });
 | |
| 
 | |
|   return server;
 | |
| };
 |