implemented HTTP 301 redirect with glob matching

This commit is contained in:
tigerbot 2017-05-16 13:04:08 -06:00
parent 0047ae69f4
commit d16f857fca
6 changed files with 87 additions and 41 deletions

View File

@ -155,7 +155,7 @@ function readConfigAndRun(args) {
if (ports.length === 0) { if (ports.length === 0) {
// I don't think we want to prevent the rest of the app from running in // I don't think we want to prevent the rest of the app from running in
// this case like we do for TCP, do don't call reject. // this case like we do for TCP, so don't call reject.
console.warn('could not bind to the desired ports for DNS'); console.warn('could not bind to the desired ports for DNS');
Object.keys(failed).forEach(function (key) { Object.keys(failed).forEach(function (key) {
console.log('[error bind]', key, failed[key].code); console.log('[error bind]', key, failed[key].code);

View File

@ -22,6 +22,12 @@ http:
allow_insecure: false allow_insecure: false
primary_domain: localhost.foo.daplie.me primary_domain: localhost.foo.daplie.me
modules: modules:
- name: redirect
domains:
- localhost.beta.daplie.me
status: 301
from: /old/path/*/other/*
to: /path/new/:2/something/:1
- name: proxy - name: proxy
domains: domains:
- localhost.daplie.me - localhost.daplie.me

30
lib/domain-utils.js Normal file
View File

@ -0,0 +1,30 @@
'use strict';
module.exports.match = function (pattern, domainname) {
// Everything matches '*'
if (pattern === '*') {
return true;
}
if (/^\*./.test(pattern)) {
// get rid of the leading "*." to more easily check the servername against it
pattern = pattern.slice(2);
return pattern === domainname.slice(-pattern.length);
}
// pattern doesn't contains any wildcards, so exact match is required
return pattern === domainname;
};
module.exports.separatePort = function (fullHost) {
var match = /^(.*?)(:\d+)?$/.exec(fullHost);
if (match[2]) {
match[2] = match[2].replace(':', '');
}
return {
host: match[1]
, port: match[2]
};
};

View File

@ -1,17 +0,0 @@
'use strict';
module.exports.match = function (pattern, servername) {
// Everything matches '*'
if (pattern === '*') {
return true;
}
if (/^\*./.test(pattern)) {
// get rid of the leading "*." to more easily check the servername against it
pattern = pattern.slice(2);
return pattern === servername.slice(-pattern.length);
}
// pattern doesn't contains any wildcards, so exact match is required
return pattern === servername;
};

View File

@ -4,7 +4,8 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
var express = require('express'); var express = require('express');
var app = express(); var app = express();
var adminApp = require('./admin').create(deps, conf); var adminApp = require('./admin').create(deps, conf);
var domainMatches = require('../match-domain').match; var domainMatches = require('../domain-utils').match;
var separatePort = require('../domain-utils').separatePort;
var proxyRoutes = []; var proxyRoutes = [];
var adminDomains = [ var adminDomains = [
@ -14,8 +15,16 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
, /\balpha\.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) { function verifyHost(fullHost) {
var host = /^(.*?)(:\d+)?$/.exec(fullHost)[1]; var host = separatePort(fullHost).host;
if (host === 'localhost') { if (host === 'localhost') {
return fullHost.replace(host, 'localhost.daplie.me'); return fullHost.replace(host, 'localhost.daplie.me');
@ -50,7 +59,7 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
return; return;
} }
var port = (/:(\d+)$/.exec(req.headers.host) || [])[1]; var port = separatePort(req.headers.host).port;
if (!redirecters[port]) { if (!redirecters[port]) {
redirecters[port] = require('redirect-https')({ redirecters[port] = require('redirect-https')({
port: port port: port
@ -115,24 +124,14 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
return { return {
web: function (req, res, next) { web: function (req, res, next) {
var hostname = req.headers.host.split(':')[0]; if (moduleMatchesHost(req, mod)) {
var relevant = mod.domains.some(function (pattern) {
return domainMatches(pattern, hostname);
});
if (relevant) {
proxy.web(req, res); proxy.web(req, res);
} else { } else {
next(); next();
} }
} }
, ws: function (req, socket, head, next) { , ws: function (req, socket, head, next) {
var hostname = req.headers.host.split(':')[0]; if (moduleMatchesHost(req, mod)) {
var relevant = mod.domains.some(function (pattern) {
return domainMatches(pattern, hostname);
});
if (relevant) {
proxy.ws(req, socket, head); proxy.ws(req, socket, head);
} else { } else {
next(); next();
@ -141,6 +140,36 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
}; };
} }
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, { 'Location': to });
res.end();
};
}
function createStaticRoute(mod) { function createStaticRoute(mod) {
var getStaticApp, staticApp; var getStaticApp, staticApp;
if (/:hostname/.test(mod.root)) { if (/:hostname/.test(mod.root)) {
@ -160,13 +189,8 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
} }
return function (req, res, next) { return function (req, res, next) {
var hostname = req.headers.host.split(':')[0]; if (moduleMatchesHost(req, mod)) {
var relevant = mod.domains.some(function (pattern) { getStaticApp(separatePort(req.headers.host).host)(req, res, next);
return domainMatches(pattern, hostname);
});
if (relevant) {
getStaticApp(hostname)(req, res, next);
} else { } else {
next(); next();
} }
@ -183,6 +207,9 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
proxyRoutes.push(proxyRoute); proxyRoutes.push(proxyRoute);
app.use(proxyRoute.web); app.use(proxyRoute.web);
} }
else if (mod.name === 'redirect') {
app.use(createRedirectRoute(mod));
}
else if (mod.name === 'static') { else if (mod.name === 'static') {
app.use(createStaticRoute(mod)); app.use(createStaticRoute(mod));
} }

View File

@ -5,7 +5,7 @@ module.exports.create = function (deps, config, netHandler) {
var parseSni = require('sni'); var parseSni = require('sni');
var greenlock = require('greenlock'); var greenlock = require('greenlock');
var localhostCerts = require('localhost.daplie.me-certificates'); var localhostCerts = require('localhost.daplie.me-certificates');
var domainMatches = require('../match-domain').match; var domainMatches = require('../domain-utils').match;
function extractSocketProp(socket, propName) { function extractSocketProp(socket, propName) {
// remoteAddress, remotePort... ugh... https://github.com/nodejs/node/issues/8854 // remoteAddress, remotePort... ugh... https://github.com/nodejs/node/issues/8854