Skip to content

Commit

Permalink
feat(server): add listen address option so that IPv6 and loopback int…
Browse files Browse the repository at this point in the history
…erfaces can be used


Fixes #2477
  • Loading branch information
grifball authored and dignifiedquire committed Dec 11, 2016
1 parent 6763719 commit 8e5bee6
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 4 deletions.
6 changes: 6 additions & 0 deletions docs/config/01-configuration-file.md
Expand Up @@ -368,6 +368,12 @@ Please note just about all frameworks in Karma require an additional plugin/fram

Additional information can be found in [plugins].

## listenAddress
**Type:** String

**Default:** `'0.0.0.0' or LISTEN_ADDR`

**Description:** Address that the server will listen on. Change to 'localhost' to only listen to the loopback, or '::' to listen on all IPv6 interfaces

## hostname
**Type:** String
Expand Down
24 changes: 24 additions & 0 deletions lib/config.js
Expand Up @@ -280,6 +280,7 @@ var Config = function () {
this.frameworks = []
this.protocol = 'http:'
this.port = constant.DEFAULT_PORT
this.listenAddress = constant.DEFAULT_LISTEN_ADDR
this.hostname = constant.DEFAULT_HOSTNAME
this.httpsServerConfig = {}
this.basePath = ''
Expand Down Expand Up @@ -372,6 +373,15 @@ var parseConfig = function (configFilePath, cliOptions) {
}

var config = new Config()

// save and reset hostname and listenAddress so we can detect if the user
// changed them
var defaultHostname = config.hostname
config.hostname = null
var defaultListenAddress = config.listenAddress
config.listenAddress = null

// add the user's configuration in
config.set(cliOptions)

try {
Expand All @@ -384,6 +394,20 @@ var parseConfig = function (configFilePath, cliOptions) {
// merge the config from config file and cliOptions (precedence)
config.set(cliOptions)

// if the user changed listenAddress, but didn't set a hostname, warn them
if (config.hostname === null && config.listenAddress !== null) {
log.warn('ListenAddress was set to %s but hostname was left as the default: ' +
'%s. If your browsers fail to connect, consider changing the hostname option.',
config.listenAddress, defaultHostname)
}
// restore values that weren't overwritten by the user
if (config.hostname === null) {
config.hostname = defaultHostname
}
if (config.listenAddress === null) {
config.listenAddress = defaultListenAddress
}

// configure the logger as soon as we can
logger.setup(config.logLevel, config.colors, config.loggers)

Expand Down
1 change: 1 addition & 0 deletions lib/constants.js
Expand Up @@ -7,6 +7,7 @@ exports.VERSION = pkg.version

exports.DEFAULT_PORT = process.env.PORT || 9876
exports.DEFAULT_HOSTNAME = process.env.IP || 'localhost'
exports.DEFAULT_LISTEN_ADDR = process.env.LISTEN_ADDR || '0.0.0.0'

// log levels
exports.LOG_DISABLE = 'OFF'
Expand Down
6 changes: 3 additions & 3 deletions lib/server.js
Expand Up @@ -160,7 +160,7 @@ Server.prototype._start = function (config, launcher, preprocess, fileList,
if (e.code === 'EADDRINUSE') {
self.log.warn('Port %d in use', config.port)
config.port++
webServer.listen(config.port)
webServer.listen(config.port, config.listenAddress)
} else {
throw e
}
Expand All @@ -171,9 +171,9 @@ Server.prototype._start = function (config, launcher, preprocess, fileList,
self._injector.invoke(watcher.watch)
}

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

self.emit('listening', config.port)
if (config.browsers && config.browsers.length) {
Expand Down
23 changes: 22 additions & 1 deletion test/unit/server.spec.js
Expand Up @@ -27,6 +27,7 @@ describe('server', () => {
{frameworks: [],
port: 9876,
autoWatch: true,
listenAddress: '127.0.0.1',
hostname: 'localhost',
urlRoot: '/',
browsers: ['fake'],
Expand Down Expand Up @@ -77,7 +78,13 @@ describe('server', () => {
webServerOnError = handler
}
},
listen: sinon.spy((port, callback) => {
listen: sinon.spy((port, arg2, arg3) => {
var callback = null
if (typeof arg2 === 'function') {
callback = arg2
} else if (typeof arg3 === 'function') {
callback = arg3
}
callback && callback()
}),
removeAllListeners: () => {},
Expand Down Expand Up @@ -135,6 +142,20 @@ describe('server', () => {
expect(server._injector.invoke).to.have.been.calledWith(mockLauncher.launch, mockLauncher)
})

it('should listen on the listenAddress in the config', () => {
server._start(mockConfig, mockLauncher, null, mockFileList, browserCollection, mockExecutor, doneSpy)

expect(mockWebServer.listen).not.to.have.been.called
expect(webServerOnError).not.to.be.null

expect(mockConfig.listenAddress).to.be.equal('127.0.0.1')

fileListOnResolve()

expect(mockWebServer.listen).to.have.been.calledWith(9876, '127.0.0.1')
expect(mockConfig.listenAddress).to.be.equal('127.0.0.1')
})

it('should try next port if already in use', () => {
server._start(mockConfig, mockLauncher, null, mockFileList, browserCollection, mockExecutor, doneSpy)

Expand Down

0 comments on commit 8e5bee6

Please sign in to comment.