Skip to content

Commit 09c75fe

Browse files
committed
fix: upgrade http-proxy module for bug fixes
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.
1 parent 7b1ef5e commit 09c75fe

4 files changed

Lines changed: 239 additions & 149 deletions

File tree

lib/middleware/proxy.js

Lines changed: 63 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,18 @@ var url = require('url');
22
var httpProxy = require('http-proxy');
33

44
var log = require('../logger').create('proxy');
5+
var _ = require('../helper')._;
56

67
var parseProxyConfig = function(proxies, config) {
7-
var proxyConfig = {};
88
var endsWithSlash = function(str) {
99
return str.substr(-1) === '/';
1010
};
1111

1212
if (!proxies) {
13-
return proxyConfig;
13+
return [];
1414
}
1515

16-
Object.keys(proxies).forEach(function(proxyPath) {
17-
var proxyUrl = proxies[proxyPath];
16+
return _.sortBy(_.map(proxies, function(proxyUrl, proxyPath) {
1817
var proxyDetails = url.parse(proxyUrl);
1918
var pathname = proxyDetails.pathname;
2019

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

34-
if (pathname === '/' && !endsWithSlash(proxyUrl)) {
33+
if (pathname === '/' && !endsWithSlash(proxyUrl)) {
3534
pathname = '';
3635
}
3736

38-
proxyConfig[proxyPath] = {
39-
host: proxyDetails.hostname,
40-
port: proxyDetails.port,
41-
baseProxyUrl: pathname,
42-
https: proxyDetails.protocol === 'https:'
43-
};
44-
45-
if (!proxyConfig[proxyPath].port) {
46-
if (!proxyConfig[proxyPath].host) {
47-
proxyConfig[proxyPath].host = config.hostname;
48-
proxyConfig[proxyPath].port = config.port;
37+
var hostname = proxyDetails.hostname || config.hostname;
38+
var port = proxyDetails.port|| config.port ||
39+
(proxyDetails.protocol === 'https:' ? '443' : '80');
40+
var https = proxyDetails.protocol === 'https:';
41+
42+
var proxy = new httpProxy.createProxyServer({
43+
target: {
44+
host: hostname,
45+
port: port,
46+
https: https
47+
},
48+
xfwd: true,
49+
secure: config.proxyValidateSSL
50+
});
51+
52+
proxy.on('error', function proxyError(err, req, res) {
53+
if (err.code === 'ECONNRESET' && req.socket.destroyed) {
54+
log.debug('failed to proxy %s (browser hung up the socket)', req.url);
4955
} else {
50-
proxyConfig[proxyPath].port = proxyConfig[proxyPath].https ? '443' : '80';
56+
log.warn('failed to proxy %s (%s)', req.url, err.message);
5157
}
52-
}
53-
});
5458

55-
return proxyConfig;
59+
res.destroy();
60+
});
61+
62+
return {
63+
path: proxyPath,
64+
baseUrl: pathname,
65+
host: hostname,
66+
port: port,
67+
https: https,
68+
proxy: proxy
69+
};
70+
}), 'path').reverse();
5671
};
5772

5873

5974
/**
6075
* Returns a handler which understands the proxies and its redirects, along with the proxy to use
61-
* @param proxy A http-proxy.RoutingProxy object with the proxyRequest method
62-
* @param proxies a map of routes to proxy url
76+
* @param proxies An array of proxy record objects
77+
* @param urlRoot The URL root that karma is mounted on
6378
* @return {Function} handler function
6479
*/
65-
var createProxyHandler = function(proxy, proxyConfig, proxyValidateSSL, urlRoot, config) {
66-
var proxies = parseProxyConfig(proxyConfig, config);
67-
var proxiesList = Object.keys(proxies).sort().reverse();
68-
69-
if (!proxiesList.length) {
80+
var createProxyHandler = function(proxies, urlRoot) {
81+
if (!proxies.length) {
7082
var nullProxy = function createNullProxy(request, response, next) {
7183
return next();
7284
};
73-
nullProxy.upgrade = function upgradeNullProxy() {
74-
};
85+
nullProxy.upgrade = function upgradeNullProxy() {};
7586
return nullProxy;
7687
}
7788

78-
proxy.on('proxyError', function(err, req) {
79-
if (err.code === 'ECONNRESET' && req.socket.destroyed) {
80-
log.debug('failed to proxy %s (browser hung up the socket)', req.url);
81-
} else {
82-
log.warn('failed to proxy %s (%s)', req.url, err.message);
83-
}
84-
});
85-
8689
var middleware = function createProxy(request, response, next) {
87-
for (var i = 0; i < proxiesList.length; i++) {
88-
if (request.url.indexOf(proxiesList[i]) === 0) {
89-
var proxiedUrl = proxies[proxiesList[i]];
90-
91-
log.debug('proxying request - %s to %s:%s', request.url, proxiedUrl.host, proxiedUrl.port);
92-
request.url = request.url.replace(proxiesList[i], proxiedUrl.baseProxyUrl);
93-
proxy.proxyRequest(request, response, {
94-
host: proxiedUrl.host,
95-
port: proxiedUrl.port,
96-
target: {https: proxiedUrl.https, rejectUnauthorized: proxyValidateSSL}
97-
});
98-
return;
99-
}
90+
var proxyRecord = _.find(proxies, function(p) {
91+
return request.url.indexOf(p.path) === 0;
92+
});
93+
94+
if (!proxyRecord) {
95+
return next();
10096
}
10197

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

105103
middleware.upgrade = function upgradeProxy(request, socket, head) {
@@ -108,22 +106,24 @@ var createProxyHandler = function(proxy, proxyConfig, proxyValidateSSL, urlRoot,
108106
log.debug('NOT upgrading proxyWebSocketRequest %s', request.url);
109107
return;
110108
}
111-
for (var i = 0; i < proxiesList.length; i++) {
112-
if (request.url.indexOf(proxiesList[i]) === 0) {
113-
var proxiedUrl = proxies[proxiesList[i]];
114-
log.debug('upgrade proxyWebSocketRequest %s to %s:%s',
115-
request.url, proxiedUrl.host, proxiedUrl.port);
116-
proxy.proxyWebSocketRequest(request, socket, head,
117-
{host: proxiedUrl.host, port: proxiedUrl.port});
118-
}
109+
110+
var proxyRecord = _.find(proxies, function(p) {
111+
return request.url.indexOf(p.path) === 0;
112+
});
113+
114+
if (!proxyRecord) {
115+
return;
119116
}
117+
118+
log.debug('upgrade proxyWebSocketRequest %s to %s:%s',
119+
request.url, proxyRecord.host, proxyRecord.port);
120+
request.url = request.url.replace(proxyRecord.path, proxyRecord.baseUrl);
121+
proxyRecord.proxy.ws(request, socket, head);
120122
};
121123

122124
return middleware;
123125
};
124126

125-
exports.create = function(/* config */ config, /* config.proxies */ proxies,
126-
/* config.proxyValidateSSL */ validateSSL) {
127-
return createProxyHandler(new httpProxy.RoutingProxy({changeOrigin: true}),
128-
proxies, validateSSL, config.urlRoot, config);
127+
exports.create = function(/* config */ config, /* config.proxies */ proxies) {
128+
return createProxyHandler(parseProxyConfig(proxies, config), config.urlRoot);
129129
};

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@
167167
"chokidar": ">=0.8.2",
168168
"glob": "~3.2.7",
169169
"minimatch": "~0.2",
170-
"http-proxy": "~0.10",
170+
"http-proxy": "~1.8.1",
171171
"optimist": "~0.6.0",
172172
"rimraf": "~2.2.5",
173173
"q": "~0.9.7",
@@ -184,6 +184,7 @@
184184
"LiveScript": "~1.2.0",
185185
"chai": "~1.9.1",
186186
"chai-as-promised": "~4.1.0",
187+
"chai-subset": "~0.3.0",
187188
"coffee-errors": "~0.8.6",
188189
"coffee-script": "~1.7.1",
189190
"cucumber": "^0.4.7",

0 commit comments

Comments
 (0)