Skip to content

Commit

Permalink
Simplified host detection, added support for "x-cloudcms-tenant-host"…
Browse files Browse the repository at this point in the history
… header to force to a specific host domain name (CDN support)
  • Loading branch information
uzquiano committed Jul 26, 2017
1 parent 4c2cd7c commit 25545ac
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 94 deletions.
209 changes: 133 additions & 76 deletions middleware/host/host.js
@@ -1,5 +1,6 @@
var path = require('path');
var util = require('../../util/util');
var path = require("path");
var util = require("../../util/util");
var dns = require("dns");

/**
* Sets req.domainHost onto request.
Expand All @@ -9,6 +10,7 @@ var util = require('../../util/util');
*/
exports = module.exports = function()
{
/*
var push = function(candidates, text)
{
if (text)
Expand All @@ -28,6 +30,67 @@ exports = module.exports = function()
}
}
};
*/

/*
var cnameCache = {};
var CNAME_EXPIRATION_TIME_MS = 1000 * 60 * 5; // five minutes
var resolveCNameAddress = function(req, hostname, callback)
{
var now = new Date().getTime();
var entry = cnameCache[hostname];
if (entry)
{
if (entry.expiration < now) {
delete cnameCache[hostname];
entry = null;
}
else if (entry.address === "NULL")
{
// support for null sentinel
return callback();
}
else
{
return callback(null, entry.address);
}
}
_resolveCNameAddress(req, hostname, function(err, address) {
// mark null sentinel if not found
if (err || !address) {
address = "NULL";
}
cnameCache[hostname] = {
"address": address,
"expiration": now + CNAME_EXPIRATION_TIME_MS
};
return callback(null, address);
});
};
var _resolveCNameAddress = function(req, hostname, callback)
{
dns.resolveCname(hostname, function(err, addresses) {
if (err) {
return callback(err);
}
var address = null;
if (addresses && addresses.length > 0) {
address = addresses[0];
}
callback(null, address);
});
};
*/

var r = {};

Expand All @@ -38,97 +101,91 @@ exports = module.exports = function()

return function(req, res, next) {

var _virtualHost = process.env.CLOUDCMS_VIRTUAL_HOST;
if (!_virtualHost)
/*
// easy way to locally invalidate the cname cache
if (req.query.invalidate) {
delete cnameCache[req.hostname];
}
*/

var handleCompletion = function(req, res, next, _virtualHost)
{
// see if we can process for a virtual host domain
var virtualHostDomain = process.env.CLOUDCMS_VIRTUAL_HOST_DOMAIN;
if (virtualHostDomain)
{
// collect all of the candidates
var candidates = [];
// base case
req.domainHost = req.hostname;
req.virtualHost = process.env.CLOUDCMS_STANDALONE_HOST;

// X-FORWARDED-HOST
var xForwardedHost = null;
if (req.header("X-Forwarded-Host"))
{
xForwardedHost = req.header("X-Forwarded-Host");
}
else if (req.header("x-forwarded-host"))
{
xForwardedHost = req.header("x-forwarded-host");
}
else if (req.header("X-FORWARDED-HOST"))
{
xForwardedHost = req.header("X-FORWARDED-HOST");
}
push(candidates, xForwardedHost);
// strip out port if it's somehow on host
if (_virtualHost && _virtualHost.indexOf(":") > -1)
{
_virtualHost = _virtualHost.substring(0, _virtualHost.indexOf(":"));
}

// CUSTOM HOST HEADER
if (process.configuration && process.configuration.host)
{
if (process.configuration.host.hostHeader)
{
var customHost = req.header[process.configuration.host.hostHeader];
push(candidates, customHost);
}
}
// strip out cdr from first "/" if it's somehow on host
if (_virtualHost && _virtualHost.indexOf("/") > -1)
{
_virtualHost = _virtualHost.substring(_virtualHost.indexOf("/"));
}

// REQ.HOSTNAME
push(candidates, req.hostname);
// virtual mode
if (_virtualHost)
{
req.virtualHost = _virtualHost;
}

// find the one that is for our virtualHostDomain
for (var x = 0; x < candidates.length; x++)
{
// keep only those that are subdomains of our intended parent virtualHostDomain (i.e. "cloudcms.net")
if (candidates[x].toLowerCase().indexOf(virtualHostDomain) > -1)
{
_virtualHost = candidates[x];
break;
}
}
//console.log("Resolved host: " + host);
// virtualHost is the host that we manage on disk
// multiple real-world hosts might map into the same virtual host
// for example, "abc.cloudcms.net and "def.cloudcms.net" could connect to Cloud CMS as a different tenant
// process.env.CLOUDCMS_STANDALONE_HOST means that gitana.json is provided manually, no virtualized connections

// if none, take first one that is not an IP address
if (!_virtualHost)
{
if (candidates.length > 0)
{
for (var i = 0; i < candidates.length; i++)
{
if (!util.isIPAddress(candidates[i]))
{
_virtualHost = candidates[i];
break;
}
}
}
}
return next();
};

// strip out port if it's somehow on host
if (_virtualHost && _virtualHost.indexOf(":") > -1)
var _virtualHost = process.env.CLOUDCMS_VIRTUAL_HOST;
if (!_virtualHost)
{
// CUSTOM HOST HEADER
if (process.configuration && process.configuration.host)
{
if (process.configuration.host.hostHeader)
{
_virtualHost = _virtualHost.substring(0, _virtualHost.indexOf(":"));
_virtualHost = req.header[process.configuration.host.hostHeader];
}
}
}
if (!_virtualHost)
{
// support for host mapping
// this makes it easy for customers to set up a CDN with a custom header to identify the tenant
// i.e. x-cloudcms-tenant-host = mytenant.cloudcms.net
var forceVirtualHost = req.header("x-cloudcms-tenant-host");
if (forceVirtualHost)
{
_virtualHost = forceVirtualHost;
}
}

// base case
req.domainHost = req.hostname;
req.virtualHost = process.env.CLOUDCMS_STANDALONE_HOST;

// virtual mode
if (_virtualHost)
{
req.virtualHost = _virtualHost;
return handleCompletion(req, res, next, _virtualHost);
}

// virtualHost is the host that we manage on disk
// multiple real-world hosts might map into the same virtual host
// for example, "abc.cloudcms.net and "def.cloudcms.net" could connect to Cloud CMS as a different tenant
// process.env.CLOUDCMS_STANDALONE_HOST means that gitana.json is provided manually, no virtualized connections
_virtualHost = req.hostname;

/*
// check if there is a cname entry for this host
resolveCNameAddress(req, _virtualHost, function(err, address) {
if (address) {
console.log("Resolved host: " + _virtualHost + " to address: " + address);
_virtualHost = address;
}
handleCompletion(req, res, next, _virtualHost);
});
*/

handleCompletion(req, res, next, _virtualHost);

next();
};
};

Expand Down
33 changes: 16 additions & 17 deletions middleware/proxy/proxy.js
Expand Up @@ -322,15 +322,18 @@ exports = module.exports = function()
// copy domain host into "x-cloudcms-domainhost"
if (req.domainHost)
{
req.headers["x-cloudcms-domainhost"] = req.domainHost;
req.headers["x-cloudcms-domainhost"] = req.domainHost; // this could be "localhost"
}

// copy virtual host into "x-cloudcms-virtualhost"
if (req.virtualHost)
{
req.headers["x-cloudcms-virtualhost"] = req.virtualHost;
req.headers["x-cloudcms-virtualhost"] = req.virtualHost; // this could be "root.cloudcms.net" or "abc.cloudcms.net"
}

//console.log("req.domainHost = " + req.domainHost);
//console.log("req.virtualHost = " + req.virtualHost);

// copy deployment descriptor info
if (req.descriptor)
{
Expand Down Expand Up @@ -361,15 +364,21 @@ exports = module.exports = function()
}
}

// set optional "x-cloudcms-origin" header
var cloudcmsOrigin = null;
if (req.virtualHost)
{
cloudcmsOrigin = req.virtualHost;
}
if (cloudcmsOrigin)
{
req.headers["x-cloudcms-origin"] = cloudcmsOrigin;
}


// determine the domain to set the "host" header on the proxied call
// this is what we pass to the API server
var domainName = req.domainHost;
if (req.virtualHost) {
domainName = req.virtualHost;
cloudcmsOrigin = req.virtualHost;
}
var cookieDomain = req.domainHost;

// if the incoming request is coming off of a CNAME entry that is maintained elsewhere (and they're just
// forwarding the CNAME request to our machine), then we try to detect this...
Expand Down Expand Up @@ -405,16 +414,6 @@ exports = module.exports = function()
// keep alive
req.headers["connection"] = "keep-alive";

// set optional "x-cloudcms-origin" header
if (cloudcmsOrigin)
{
req.headers["x-cloudcms-origin"] = cloudcmsOrigin;
}

// determine domain to use for cookie rewrites
// assume cookieDomain proxyHostHeader
var cookieDomain = domainName;

// allow forced cookie domains
var forcedCookieDomain = req.headers["cloudcmscookiedomain"];
if (!forcedCookieDomain)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -6,7 +6,7 @@
},
"name": "cloudcms-server",
"description": "Cloud CMS Application Server Module",
"version": "0.8.411",
"version": "0.8.412",
"repository": {
"type": "git",
"url": "git://github.com/gitana/cloudcms-server.git"
Expand Down

0 comments on commit 25545ac

Please sign in to comment.