Skip to content

Commit

Permalink
feat(web-server): Allow running on https
Browse files Browse the repository at this point in the history
  • Loading branch information
KrekkieD committed Aug 4, 2015
1 parent 80f51c1 commit 1696c78
Show file tree
Hide file tree
Showing 14 changed files with 230 additions and 74 deletions.
33 changes: 33 additions & 0 deletions docs/config/01-configuration-file.md
Expand Up @@ -213,6 +213,23 @@ Additional information can be found in [plugins].
**Description:** Hostname to be used when capturing browsers.


## httpsServerOptions
**Type:** Object

**Default:** `{}`

**Description:** Options object to be used by Node's `https` class.

Object description can be found in the [NodeJS.org API docs](https://nodejs.org/api/tls.html#tls_tls_createserver_options_secureconnectionlistener)

**Example:**
```javascript
httpsServerOptions: {
key: fs.readFileSync('server.key', 'utf8'),
cert: fs.readFileSync('server.crt', 'utf8')
},
```

## logLevel
**Type:** Constant

Expand Down Expand Up @@ -283,6 +300,22 @@ but your interactive debugging does not.
Click <a href="preprocessors.html">here</a> for more information.


## protocol
**Type:** String

**Default:** `'http:'`

**Possible Values:**

* `http:`
* `https:`

**Description:** Protocol used for running the Karma webserver.

Determines the use of the Node `http` or `https` class.

Note: Using `'https:'` requires you to specify `httpsServerOptions`.

## proxies
**Type:** Object

Expand Down
10 changes: 10 additions & 0 deletions lib/config.js
Expand Up @@ -118,6 +118,14 @@ var normalizeConfig = function (config, configFilePath) {
// normalize urlRoot
config.urlRoot = normalizeUrlRoot(config.urlRoot)

// force protocol to end with ':'
config.protocol = (config.protocol || 'http').split(':')[0] + ':'
if (config.protocol.match(/https?:/) === null) {
log.warn('"%s" is not a supported protocol, defaulting to "http:"',
config.protocol)
config.protocol = 'http:'
}

if (config.proxies && config.proxies.hasOwnProperty(config.urlRoot)) {
log.warn('"%s" is proxied, you should probably change urlRoot to avoid conflicts',
config.urlRoot)
Expand Down Expand Up @@ -213,8 +221,10 @@ var Config = function () {

// DEFAULT CONFIG
this.frameworks = []
this.protocol = 'http:'
this.port = constant.DEFAULT_PORT
this.hostname = constant.DEFAULT_HOSTNAME
this.httpsServerConfig = {}
this.basePath = ''
this.files = []
this.exclude = []
Expand Down
4 changes: 2 additions & 2 deletions lib/executor.js
Expand Up @@ -10,8 +10,8 @@ var Executor = function (capturedBrowsers, config, emitter) {
var nonReady = []

if (!capturedBrowsers.length) {
log.warn('No captured browser, open http://%s:%s%s', config.hostname, config.port,
config.urlRoot)
log.warn('No captured browser, open %s//%s:%s%s', config.protocol, config.hostname,
config.port, config.urlRoot)
return false
}

Expand Down
6 changes: 3 additions & 3 deletions lib/launcher.js
Expand Up @@ -31,9 +31,9 @@ var Launcher = function (emitter, injector) {
return null
}

this.launch = function (names, hostname, port, urlRoot) {
this.launch = function (names, protocol, hostname, port, urlRoot) {
var browser
var url = 'http://' + hostname + ':' + port + urlRoot
var url = protocol + '//' + hostname + ':' + port + urlRoot

lastStartTime = Date.now()

Expand Down Expand Up @@ -92,7 +92,7 @@ var Launcher = function (emitter, injector) {
return browsers
}

this.launch.$inject = ['config.browsers', 'config.hostname', 'config.port', 'config.urlRoot']
this.launch.$inject = ['config.browsers', 'config.protocol', 'config.hostname', 'config.port', 'config.urlRoot']

this.kill = function (id, callback) {
var browser = getBrowserById(id)
Expand Down
7 changes: 4 additions & 3 deletions lib/middleware/runner.js
Expand Up @@ -12,7 +12,8 @@ var json = require('body-parser').json()

// TODO(vojta): disable when single-run mode
var createRunnerMiddleware = function (emitter, fileList, capturedBrowsers, reporter, executor,
/* config.hostname */ hostname, /* config.port */ port, /* config.urlRoot */ urlRoot, config) {
/* config.protocol */ protocol, /* config.hostname */ hostname, /* config.port */
port, /* config.urlRoot */ urlRoot, config) {
return function (request, response, next) {
if (request.url !== '/__run__' && request.url !== urlRoot + 'run') {
return next()
Expand All @@ -22,7 +23,7 @@ var createRunnerMiddleware = function (emitter, fileList, capturedBrowsers, repo
response.writeHead(200)

if (!capturedBrowsers.length) {
var url = 'http://' + hostname + ':' + port + urlRoot
var url = protocol + '//' + hostname + ':' + port + urlRoot

return response.end('No captured browser, open ' + url + '\n')
}
Expand Down Expand Up @@ -88,7 +89,7 @@ var createRunnerMiddleware = function (emitter, fileList, capturedBrowsers, repo
}

createRunnerMiddleware.$inject = ['emitter', 'fileList', 'capturedBrowsers', 'reporter', 'executor',
'config.hostname', 'config.port', 'config.urlRoot', 'config']
'config.protocol', 'config.hostname', 'config.port', 'config.urlRoot', 'config']

// PUBLIC API
exports.create = createRunnerMiddleware
2 changes: 1 addition & 1 deletion lib/reporter.js
Expand Up @@ -21,7 +21,7 @@ var createErrorFormatter = function (basePath, emitter, SourceMapConsumer) {
return null
}

var URL_REGEXP = new RegExp('(?:http:\\/\\/[^\\/]*)?\\/?' +
var URL_REGEXP = new RegExp('(?:https?:\\/\\/[^\\/]*)?\\/?' +
'(base|absolute)' + // prefix
'((?:[A-z]\\:)?[^\\?\\s\\:]*)' + // path
'(\\?\\w*)?' + // sha
Expand Down
4 changes: 2 additions & 2 deletions lib/server.js
Expand Up @@ -159,8 +159,8 @@ Server.prototype._start = function (config, launcher, preprocess, fileList, webS
}

webServer.listen(config.port, function () {
self.log.info('Karma v%s server started at http://%s:%s%s', constant.VERSION, config.hostname,
config.port, config.urlRoot)
self.log.info('Karma v%s server started at %s//%s:%s%s', constant.VERSION,
config.protocol, config.hostname, config.port, config.urlRoot)

if (config.browsers && config.browsers.length) {
self._injector.invoke(launcher.launch, launcher).forEach(function (browserLauncher) {
Expand Down
11 changes: 10 additions & 1 deletion lib/web-server.js
@@ -1,5 +1,6 @@
var fs = require('fs')
var http = require('http')
var https = require('https')
var path = require('path')
var connect = require('connect')
var Promise = require('bluebird')
Expand Down Expand Up @@ -29,6 +30,7 @@ var createCustomHandler = function (customFileHandlers, /* config.basePath */ ba
createCustomHandler.$inject = ['customFileHandlers', 'config.basePath']

var createWebServer = function (injector, emitter) {
var config = injector.get('config')
var serveStaticFile = common.createServeFile(fs, path.normalize(__dirname + '/../static'))
var serveFile = common.createServeFile(fs)
var filesPromise = new common.PromiseContainer()
Expand Down Expand Up @@ -61,7 +63,14 @@ var createWebServer = function (injector, emitter) {
common.serve404(response, request.url)
})

var server = http.createServer(handler)
var serverClass = http
var serverArguments = [handler]

if (config.protocol === 'https:') {
serverClass = https
serverArguments.unshift(config.httpsServerOptions || {})
}
var server = serverClass.createServer.apply(null, serverArguments)

server.on('upgrade', function (req, socket, head) {
log.debug('upgrade %s', req.url)
Expand Down
13 changes: 13 additions & 0 deletions test/unit/certificates/server.crt
@@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIICAzCCAWwCCQDlm49KXF45gzANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJB
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEQMA4GA1UEChMHR3J1bnRKUzEQMA4GA1UE
AxMHMC4wLjAuMDAeFw0xNDAyMTkyMzE1NDRaFw0xNTAyMTkyMzE1NDRaMEYxCzAJ
BgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMRAwDgYDVQQKEwdHcnVudEpT
MRAwDgYDVQQDEwcwLjAuMC4wMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCm
ipCqKyQ6aJJiVMvXZVoTw9sEC5dKFA35n15r9fG565/Zj8LVg/kgt79am1bnF+/H
F880f8kfDsgEaAC1qzo8XU8yqt+UoFOB2Ncw76g6B6ZiuC2R1uHyD/46sYtMejy3
n8EcTk9jNmNlglF6Ig6/hWcz+0XH6QjJT0lAM06tswIDAQABMA0GCSqGSIb3DQEB
BQUAA4GBADnTBlN7+Aa8zj2zsUBSUv9w7iYut3ZDvrEY+IJt8EurwA6+Q7rQqVsY
an5ztiEESriWvqNIfvWb+Yekhv9sISJFMfApVbimmT6QseQcFEIlRNW5cfukHQVH
9dBI7upQO2vN7N9ABo4a3aBANMBxIvCnE+adiqNOTJF/8qkiAFY9
-----END CERTIFICATE-----
15 changes: 15 additions & 0 deletions test/unit/certificates/server.key
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCmipCqKyQ6aJJiVMvXZVoTw9sEC5dKFA35n15r9fG565/Zj8LV
g/kgt79am1bnF+/HF880f8kfDsgEaAC1qzo8XU8yqt+UoFOB2Ncw76g6B6ZiuC2R
1uHyD/46sYtMejy3n8EcTk9jNmNlglF6Ig6/hWcz+0XH6QjJT0lAM06tswIDAQAB
AoGATqG34hCSf11mWDUPNXjuCcz8eLF8Ugab/pMngrPR2OWOSKue4y73jmITYBVd
96iOlqMAOxpmfFp/R81PIHdi++Bax1NfSBT8tK0U7HHzkbHEXyvHiBSug78Y14h8
Y/NMZXEvVapY7lapr5ZgOSf2rcKOlceMRsoohl6bGc+55BECQQDPZTw5WxDDe7/W
oYzHy7abLw+A92cP8A6qlwXBik9ko6jtYXvoI454OIr6RsHoFPU9bUkx5G1fvOUZ
J3sxfxMZAkEAzZJEwcvmxHizX/2NZZ8LvVyWGpao07bBcAEvDXDZFOZqKUujukOe
iilQD6JZDJTmW9RJmOgdQKeL9ZaTlX3MqwJASMJrbnPUXcB8fQAQM8f0OF06QzSI
o77EZnS1QEEVuWjxStZ4ceiHgwXTPBq2zIUNxI8irq5E8OGEPl7riWHbgQJARzqL
QGsaRrFb1cLRH4kAVFikWgoh7VnBpMGEQC/9x9QerLhcvsl3QYAXEZO7LzTYrLDd
33Ft0V08jZfjA0VXiQJAOwX6glfDKf79AK1sifFQc/v0Yu87LIOAwp0zLlsnO0Q9
xQV3TdjlNQebfTG+Uw1tmbcCb2wcGFfD199IHpAzIA==
-----END RSA PRIVATE KEY-----
22 changes: 22 additions & 0 deletions test/unit/config.spec.js
Expand Up @@ -226,6 +226,28 @@ describe('config', () => {
expect(config.client.useIframe).to.not.be.undefined
expect(config.client.args).to.not.be.undefined
})

it('should validate and format the protocol', () => {

var config = normalizeConfigWithDefaults({})
expect(config.protocol).to.equal('http:')

config = normalizeConfigWithDefaults({ protocol: 'http' })
expect(config.protocol).to.equal('http:')

config = normalizeConfigWithDefaults({ protocol: 'http:' })
expect(config.protocol).to.equal('http:')

config = normalizeConfigWithDefaults({ protocol: 'https' })
expect(config.protocol).to.equal('https:')

config = normalizeConfigWithDefaults({ protocol: 'https:' })
expect(config.protocol).to.equal('https:')

config = normalizeConfigWithDefaults({ protocol: 'unsupported:' })
expect(config.protocol).to.equal('http:')

})
})

describe('normalizeConfig', () => {
Expand Down
18 changes: 9 additions & 9 deletions test/unit/launcher.spec.js
Expand Up @@ -80,7 +80,7 @@ describe('launcher', () => {

describe('launch', () => {
it('should inject and start all browsers', () => {
l.launch(['Fake'], 'localhost', 1234, '/root/')
l.launch(['Fake'], 'http:', 'localhost', 1234, '/root/')

var browser = FakeBrowser._instances.pop()
expect(browser.start).to.have.been.calledWith('http://localhost:1234/root/')
Expand All @@ -89,15 +89,15 @@ describe('launcher', () => {
})

it('should allow launching a script', () => {
l.launch(['/usr/local/bin/special-browser'], 'localhost', 1234, '/')
l.launch(['/usr/local/bin/special-browser'], 'http:', 'localhost', 1234, '/')

var script = ScriptBrowser._instances.pop()
expect(script.start).to.have.been.calledWith('http://localhost:1234/')
expect(script.name).to.equal('/usr/local/bin/special-browser')
})

it('should use the non default host', () => {
l.launch(['Fake'], 'whatever', 1234, '/root/')
l.launch(['Fake'], 'http:', 'whatever', 1234, '/root/')

var browser = FakeBrowser._instances.pop()
expect(browser.start).to.have.been.calledWith('http://whatever:1234/root/')
Expand All @@ -106,7 +106,7 @@ describe('launcher', () => {

describe('restart', () => {
it('should restart the browser', () => {
l.launch(['Fake'], 'localhost', 1234, '/root/')
l.launch(['Fake'], 'http:', 'localhost', 1234, '/root/')
var browser = FakeBrowser._instances.pop()

var returnedValue = l.restart(lastGeneratedId)
Expand All @@ -115,7 +115,7 @@ describe('launcher', () => {
})

it('should return false if the browser was not launched by launcher (manual)', () => {
l.launch([], 'localhost', 1234, '/')
l.launch([], 'http:', 'localhost', 1234, '/')
expect(l.restart('manual-id')).to.equal(false)
})
})
Expand Down Expand Up @@ -151,7 +151,7 @@ describe('launcher', () => {

describe('killAll', () => {
it('should kill all running processe', () => {
l.launch(['Fake', 'Fake'], 'localhost', 1234)
l.launch(['Fake', 'Fake'], 'http:', 'localhost', 1234)
l.killAll()

var browser = FakeBrowser._instances.pop()
Expand All @@ -164,7 +164,7 @@ describe('launcher', () => {
it('should call callback when all processes killed', () => {
var exitSpy = sinon.spy()

l.launch(['Fake', 'Fake'], 'localhost', 1234)
l.launch(['Fake', 'Fake'], 'http:', 'localhost', 1234)
l.killAll(exitSpy)

expect(exitSpy).not.to.have.been.called
Expand Down Expand Up @@ -195,7 +195,7 @@ describe('launcher', () => {

describe('areAllCaptured', () => {
it('should return true if only if all browsers captured', () => {
l.launch(['Fake', 'Fake'], 'localhost', 1234)
l.launch(['Fake', 'Fake'], 'http:', 'localhost', 1234)

expect(l.areAllCaptured()).to.equal(false)

Expand All @@ -209,7 +209,7 @@ describe('launcher', () => {

describe('onExit', () => {
it('should kill all browsers', done => {
l.launch(['Fake', 'Fake'], 'localhost', 1234, '/', 0, 1)
l.launch(['Fake', 'Fake'], 'http:', 'localhost', 1234, '/', 0, 1)

emitter.emitAsync('exit').then(done)

Expand Down
2 changes: 1 addition & 1 deletion test/unit/middleware/runner.spec.js
Expand Up @@ -47,7 +47,7 @@ describe('middleware.runner', () => {
config = {client: {}, basePath: '/'}

handler = createRunnerMiddleware(emitter, fileListMock, capturedBrowsers,
new MultReporter([mockReporter]), executor, 'localhost', 8877, '/', config)
new MultReporter([mockReporter]), executor, 'http:', 'localhost', 8877, '/', config)
})

it('should trigger test run and stream the reporter', (done) => {
Expand Down

0 comments on commit 1696c78

Please sign in to comment.