Skip to content

Commit

Permalink
feat(proxy): add https proxy support
Browse files Browse the repository at this point in the history
This enables Karma to proxy to a https server. We still need to enable Karma itself to serve on
https.

See #293
  • Loading branch information
Grummle authored and vojtajina committed Jun 17, 2013
1 parent 6473f99 commit be878dc
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 21 deletions.
7 changes: 7 additions & 0 deletions docs/config/01-configuration-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ See [config/files] for more information.
},
```

## proxyValidateSSL
**Type:** Boolean

**Default:** `true`

**Description:** Should https proxies error on invalid SSL cert.

## reportSlowerThan
**Type:** Number

Expand Down
1 change: 1 addition & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ var parseConfig = function(configFilePath, cliOptions) {
browsers: [],
captureTimeout: 60000,
proxies: {},
proxyValidateSSL: true,
preprocessors: {'**/*.coffee': 'coffee'},
urlRoot: '/',
reportSlowerThan: 0,
Expand Down
17 changes: 13 additions & 4 deletions lib/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,14 @@ var parseProxyConfig = function(proxies) {

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

if (!proxyConfig[proxyPath].port) {
proxyConfig[proxyPath].port = proxyConfig[proxyPath].https ? '443' : '80';
}
});

return proxyConfig;
Expand All @@ -51,7 +56,7 @@ var parseProxyConfig = function(proxies) {
* @param proxies a map of routes to proxy url
* @return {Function} handler function
*/
var createProxyHandler = function(proxy, proxyConfig) {
var createProxyHandler = function(proxy, proxyConfig, proxyValidateSSL) {
var proxies = parseProxyConfig(proxyConfig);
var proxiesList = Object.keys(proxies).sort().reverse();

Expand All @@ -75,7 +80,11 @@ var createProxyHandler = function(proxy, proxyConfig) {

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});
proxy.proxyRequest(request, response, {
host: proxiedUrl.host,
port: proxiedUrl.port,
target: { https: proxiedUrl.https, rejectUnauthorized: proxyValidateSSL }
});
return;
}
}
Expand Down
10 changes: 5 additions & 5 deletions lib/web-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,10 @@ var createSourceFileHandler = function(promiseContainer, adapterFolder, baseFold


var createHandler = function(promiseContainer, staticFolder, adapterFolder, baseFolder, proxyFn,
proxies, urlRoot, customFileHandlers, customScriptTypes) {
proxies, urlRoot, customFileHandlers, customScriptTypes, proxyValidateSSL) {
var karmaSrcHandler = createKarmaSourceHandler(promiseContainer, staticFolder,
adapterFolder, baseFolder, urlRoot, customFileHandlers, customScriptTypes);
var proxiedPathsHandler = proxy.createProxyHandler(proxyFn, proxies);
var proxiedPathsHandler = proxy.createProxyHandler(proxyFn, proxies, proxyValidateSSL);
var sourceFileHandler = createSourceFileHandler(promiseContainer, adapterFolder, baseFolder);

return function(request, response) {
Expand All @@ -210,7 +210,7 @@ var createHandler = function(promiseContainer, staticFolder, adapterFolder, base


exports.createWebServer = function (baseFolder, proxies, urlRoot,
customFileHandlers, customScriptTypes) {
customFileHandlers, customScriptTypes, proxyValidateSSL) {
var staticFolder = path.normalize(__dirname + '/../static');
var adapterFolder = path.normalize(__dirname + '/../adapter');

Expand All @@ -221,7 +221,7 @@ exports.createWebServer = function (baseFolder, proxies, urlRoot,
var server = http.createServer(createHandler(promiseContainer,
helper.normalizeWinPath(staticFolder), helper.normalizeWinPath(adapterFolder), baseFolder,
new httpProxy.RoutingProxy({changeOrigin: true}), proxies, urlRoot, customFileHandlers,
customScriptTypes));
customScriptTypes, proxyValidateSSL));

server.updateFilesPromise = function(promise) {
promiseContainer.promise = promise;
Expand All @@ -231,4 +231,4 @@ exports.createWebServer = function (baseFolder, proxies, urlRoot,
};

exports.createWebServer.$inject = ['config.basePath', 'config.proxies', 'config.urlRoot',
'customFileHandlers', 'customScriptTypes'];
'customFileHandlers', 'customScriptTypes', 'config.proxyValidateSSL'];
74 changes: 65 additions & 9 deletions test/unit/proxy.spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -26,37 +26,73 @@ describe 'proxy', ->


it 'should proxy requests', (done) ->
proxy = m.createProxyHandler mockProxy, {'/proxy': 'http://localhost:9000'}
proxy = m.createProxyHandler mockProxy, {'/proxy': 'http://localhost:9000'}, true
proxy new httpMock.ServerRequest('/proxy/test.html'), response, nextSpy

expect(nextSpy).not.to.have.been.called
expect(requestedUrl).to.equal '/test.html'
expect(actualOptions).to.deep.equal {host: 'localhost', port: '9000'}
expect(actualOptions).to.deep.equal {
host: 'localhost',
port: '9000',
target:{https:false, rejectUnauthorized:true}
}
done()

it 'should enable https', (done) ->
proxy = m.createProxyHandler mockProxy, {'/proxy': 'https://localhost:9000'}, true
proxy new httpMock.ServerRequest('/proxy/test.html'), response, nextSpy

expect(nextSpy).not.to.have.been.called
expect(requestedUrl).to.equal '/test.html'
expect(actualOptions).to.deep.equal {
host: 'localhost',
port: '9000',
target:{https:true, rejectUnauthorized:true}
}
done()

it 'disable ssl validation', (done) ->
proxy = m.createProxyHandler mockProxy, {'/proxy': 'https://localhost:9000'}, false
proxy new httpMock.ServerRequest('/proxy/test.html'), response, nextSpy

expect(nextSpy).not.to.have.been.called
expect(requestedUrl).to.equal '/test.html'
expect(actualOptions).to.deep.equal {
host: 'localhost',
port: '9000',
target:{https:true, rejectUnauthorized:false}
}
done()

it 'should support multiple proxies', ->
proxy = m.createProxyHandler mockProxy, {
'/proxy': 'http://localhost:9000'
'/static': 'http://gstatic.com'
}
}, true
proxy new httpMock.ServerRequest('/static/test.html'), response, nextSpy

expect(nextSpy).not.to.have.been.called
expect(requestedUrl).to.equal '/test.html'
expect(actualOptions).to.deep.equal {host: 'gstatic.com', port: '80'}

expect(actualOptions).to.deep.equal {
host: 'gstatic.com',
port: '80',
target:{https:false, rejectUnauthorized:true}
}

it 'should handle nested proxies', ->
proxy = m.createProxyHandler mockProxy, {
'/sub': 'http://localhost:9000'
'/sub/some': 'http://gstatic.com/something'
}
}, true
proxy new httpMock.ServerRequest('/sub/some/Test.html'), response, nextSpy

expect(nextSpy).not.to.have.been.called
expect(requestedUrl).to.equal '/something/Test.html'
expect(actualOptions).to.deep.equal {host: 'gstatic.com', port: '80'}
expect(actualOptions).to.deep.equal {
host: 'gstatic.com',
port: '80',
target:{https:false, rejectUnauthorized:true}
}


it 'should call next handler if the path is not proxied', ->
Expand All @@ -77,17 +113,37 @@ describe 'proxy', ->
proxy = {'/base/': 'http://localhost:8000/'}
parsedProxyConfig = m.parseProxyConfig proxy
expect(parsedProxyConfig).to.deep.equal {
'/base/': {host: 'localhost', port: '8000', baseProxyUrl: '/'}
'/base/': {host: 'localhost', port: '8000', baseProxyUrl: '/', https:false}
}

it 'should set defualt http port', ->
proxy = {'/base/': 'http://localhost/'}
parsedProxyConfig = m.parseProxyConfig proxy
expect(parsedProxyConfig).to.deep.equal {
'/base/': {host: 'localhost', port: '80', baseProxyUrl: '/', https:false}
}

it 'should set defualt https port', ->
proxy = {'/base/': 'https://localhost/'}
parsedProxyConfig = m.parseProxyConfig proxy
expect(parsedProxyConfig).to.deep.equal {
'/base/': {host: 'localhost', port: '443', baseProxyUrl: '/', https:true}
}


it 'should handle proxy configs with paths', ->
proxy = {'/base': 'http://localhost:8000/proxy'}
parsedProxyConfig = m.parseProxyConfig proxy
expect(parsedProxyConfig).to.deep.equal {
'/base': {host: 'localhost', port: '8000', baseProxyUrl: '/proxy'}
'/base': {host: 'localhost', port: '8000', baseProxyUrl: '/proxy', https:false}
}

it 'should determine protocol', ->
proxy = {'/base':'https://localhost:8000'}
parsedProxyConfig = m.parseProxyConfig proxy
expect(parsedProxyConfig).to.deep.equal {
'/base': {host: 'localhost', port: '8000', baseProxyUrl: '', https: true}
}

it 'should handle empty proxy config', ->
expect(m.parseProxyConfig {}).to.deep.equal({})
14 changes: 11 additions & 3 deletions test/unit/web-server.spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe 'web-server', ->
servedFiles defaultFiles
handler = m.createHandler promiseContainer, staticFolderPath, adapterFolderPath, baseFolder,
mockProxy, {'/_karma_/': 'http://localhost:9000', '/base/': 'http://localhost:1000'},
'/_karma_/', [], []
'/_karma_/', [], [], true
actualOptions = {}
response = new responseMock()
nextSpy = sinon.spy()
Expand Down Expand Up @@ -109,15 +109,23 @@ describe 'web-server', ->

it 'should delegate to proxy after checking for karma files', (done) ->
response.once 'end', ->
expect(actualOptions).to.deep.equal {host: 'localhost', port: '9000'}
expect(actualOptions).to.deep.equal {
host: 'localhost',
port: '9000',
target:{https:false, rejectUnauthorized:true}
}
done()

handler new httpMock.ServerRequest('/_karma_/not_client.html'), response


it 'should delegate to proxy after checking for source files', (done) ->
response.once 'end', ->
expect(actualOptions).to.deep.equal {host: 'localhost', port: '1000'}
expect(actualOptions).to.deep.equal {
host: 'localhost',
port: '1000',
target:{https:false, rejectUnauthorized:true}
}
done()

handler new httpMock.ServerRequest('/base/not_client.html'), response
Expand Down

0 comments on commit be878dc

Please sign in to comment.