Skip to content

Commit

Permalink
fix: upgrade http-proxy module for bug fixes
Browse files Browse the repository at this point in the history
The http-proxy module was significantly outdated, and was missing many
bugfixes to support node newer than 0.10, as we as a refactor and
simplification of their codebase.

This commit upgrades the http-proxy dependency, and refactors the proxy
middleware to utilize the new API.

`parseProxyConfig` (an internal function) now takes the proxy
configuration object and converts it into an array of "ProxyRecord"
objects, sorted by path. A ProxyRecord object contains:

  * `host` The remote proxy host
  * `port` The remote proxy port
  * `baseUrl` The remote proxy path
  * `path` The local URL path (that triggers the rewrite)
  * `https` Boolean to determine if the remote connection will be made
    with SSL.
  * `proxy` The instance of http-proxy.

This change was necessitated by the removal of http-proxy's
RoutingProxy API, and the desire to only create one proxy instance per
configuration item.

The middleware simply determines if the current request matches a known
proxy path, and calls the proxy's `.web` and `.ws` methods with the
current request and response objects.

The relevant unit tests were rewritten in light of these changes.
  • Loading branch information
terinjokes committed Mar 11, 2015
1 parent 7b1ef5e commit 09c75fe
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 149 deletions.
126 changes: 63 additions & 63 deletions lib/middleware/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@ var url = require('url');
var httpProxy = require('http-proxy');

var log = require('../logger').create('proxy');
var _ = require('../helper')._;

var parseProxyConfig = function(proxies, config) {
var proxyConfig = {};
var endsWithSlash = function(str) {
return str.substr(-1) === '/';
};

if (!proxies) {
return proxyConfig;
return [];
}

Object.keys(proxies).forEach(function(proxyPath) {
var proxyUrl = proxies[proxyPath];
return _.sortBy(_.map(proxies, function(proxyUrl, proxyPath) {
var proxyDetails = url.parse(proxyUrl);
var pathname = proxyDetails.pathname;

Expand All @@ -31,75 +30,74 @@ var parseProxyConfig = function(proxies, config) {
proxyPath += '/';
}

if (pathname === '/' && !endsWithSlash(proxyUrl)) {
if (pathname === '/' && !endsWithSlash(proxyUrl)) {
pathname = '';
}

proxyConfig[proxyPath] = {
host: proxyDetails.hostname,
port: proxyDetails.port,
baseProxyUrl: pathname,
https: proxyDetails.protocol === 'https:'
};

if (!proxyConfig[proxyPath].port) {
if (!proxyConfig[proxyPath].host) {
proxyConfig[proxyPath].host = config.hostname;
proxyConfig[proxyPath].port = config.port;
var hostname = proxyDetails.hostname || config.hostname;
var port = proxyDetails.port|| config.port ||
(proxyDetails.protocol === 'https:' ? '443' : '80');
var https = proxyDetails.protocol === 'https:';

var proxy = new httpProxy.createProxyServer({
target: {
host: hostname,
port: port,
https: https
},
xfwd: true,
secure: config.proxyValidateSSL
});

proxy.on('error', function proxyError(err, req, res) {
if (err.code === 'ECONNRESET' && req.socket.destroyed) {
log.debug('failed to proxy %s (browser hung up the socket)', req.url);
} else {
proxyConfig[proxyPath].port = proxyConfig[proxyPath].https ? '443' : '80';
log.warn('failed to proxy %s (%s)', req.url, err.message);
}
}
});

return proxyConfig;
res.destroy();
});

return {
path: proxyPath,
baseUrl: pathname,
host: hostname,
port: port,
https: https,
proxy: proxy
};
}), 'path').reverse();
};


/**
* Returns a handler which understands the proxies and its redirects, along with the proxy to use
* @param proxy A http-proxy.RoutingProxy object with the proxyRequest method
* @param proxies a map of routes to proxy url
* @param proxies An array of proxy record objects
* @param urlRoot The URL root that karma is mounted on
* @return {Function} handler function
*/
var createProxyHandler = function(proxy, proxyConfig, proxyValidateSSL, urlRoot, config) {
var proxies = parseProxyConfig(proxyConfig, config);
var proxiesList = Object.keys(proxies).sort().reverse();

if (!proxiesList.length) {
var createProxyHandler = function(proxies, urlRoot) {
if (!proxies.length) {
var nullProxy = function createNullProxy(request, response, next) {
return next();
};
nullProxy.upgrade = function upgradeNullProxy() {
};
nullProxy.upgrade = function upgradeNullProxy() {};
return nullProxy;
}

proxy.on('proxyError', function(err, req) {
if (err.code === 'ECONNRESET' && req.socket.destroyed) {
log.debug('failed to proxy %s (browser hung up the socket)', req.url);
} else {
log.warn('failed to proxy %s (%s)', req.url, err.message);
}
});

var middleware = function createProxy(request, response, next) {
for (var i = 0; i < proxiesList.length; i++) {
if (request.url.indexOf(proxiesList[i]) === 0) {
var proxiedUrl = proxies[proxiesList[i]];

log.debug('proxying request - %s to %s:%s', request.url, proxiedUrl.host, proxiedUrl.port);
request.url = request.url.replace(proxiesList[i], proxiedUrl.baseProxyUrl);
proxy.proxyRequest(request, response, {
host: proxiedUrl.host,
port: proxiedUrl.port,
target: {https: proxiedUrl.https, rejectUnauthorized: proxyValidateSSL}
});
return;
}
var proxyRecord = _.find(proxies, function(p) {
return request.url.indexOf(p.path) === 0;
});

if (!proxyRecord) {
return next();
}

return next();
log.debug('proxying request - %s to %s:%s', request.url, proxyRecord.host, proxyRecord.port);
request.url = request.url.replace(proxyRecord.path, proxyRecord.baseUrl);
proxyRecord.proxy.web(request, response);
};

middleware.upgrade = function upgradeProxy(request, socket, head) {
Expand All @@ -108,22 +106,24 @@ var createProxyHandler = function(proxy, proxyConfig, proxyValidateSSL, urlRoot,
log.debug('NOT upgrading proxyWebSocketRequest %s', request.url);
return;
}
for (var i = 0; i < proxiesList.length; i++) {
if (request.url.indexOf(proxiesList[i]) === 0) {
var proxiedUrl = proxies[proxiesList[i]];
log.debug('upgrade proxyWebSocketRequest %s to %s:%s',
request.url, proxiedUrl.host, proxiedUrl.port);
proxy.proxyWebSocketRequest(request, socket, head,
{host: proxiedUrl.host, port: proxiedUrl.port});
}

var proxyRecord = _.find(proxies, function(p) {
return request.url.indexOf(p.path) === 0;
});

if (!proxyRecord) {
return;
}

log.debug('upgrade proxyWebSocketRequest %s to %s:%s',
request.url, proxyRecord.host, proxyRecord.port);
request.url = request.url.replace(proxyRecord.path, proxyRecord.baseUrl);
proxyRecord.proxy.ws(request, socket, head);
};

return middleware;
};

exports.create = function(/* config */ config, /* config.proxies */ proxies,
/* config.proxyValidateSSL */ validateSSL) {
return createProxyHandler(new httpProxy.RoutingProxy({changeOrigin: true}),
proxies, validateSSL, config.urlRoot, config);
exports.create = function(/* config */ config, /* config.proxies */ proxies) {
return createProxyHandler(parseProxyConfig(proxies, config), config.urlRoot);
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@
"chokidar": ">=0.8.2",
"glob": "~3.2.7",
"minimatch": "~0.2",
"http-proxy": "~0.10",
"http-proxy": "~1.8.1",
"optimist": "~0.6.0",
"rimraf": "~2.2.5",
"q": "~0.9.7",
Expand All @@ -184,6 +184,7 @@
"LiveScript": "~1.2.0",
"chai": "~1.9.1",
"chai-as-promised": "~4.1.0",
"chai-subset": "~0.3.0",
"coffee-errors": "~0.8.6",
"coffee-script": "~1.7.1",
"cucumber": "^0.4.7",
Expand Down
Loading

0 comments on commit 09c75fe

Please sign in to comment.