diff --git a/docs/Reference.md b/docs/Reference.md index f4ecd3b23..ebb63445b 100755 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -264,17 +264,21 @@ the sole connection where: - `id` - a unique connection identifier (using the format '{hostname}:{pid}:{now base36}'). - `created` - the connection creation timestamp. - `started` - the connection start timestamp (`0` when stopped). -- `port` - the port the connection was configured to (before - [`server.start()`](#serverstartcallback)) or bound to (after - [`server.start()`](#serverstartcallback)). -- `host` - the host name the connection was configured to (defaults to `'0.0.0.0'` if no host was - provided). +- `port` - the connection port based on the following rules: + - `undefined` when no port is configured or set to `0` and the server has not been started. + - the configured port value when set before the server has been started. + - the actual port assigned when no port is configured or set to `0` after the server has been + started. +- `host` - the host name the connection was configured to. Defaults to the operating system + hostname when available, otherwise `'localhost'`. +- `address` - the active IP address the connection was bound to after starting. Set to `undefined` + until the server has been started or when using a non TCP port (e.g. UNIX domain socket). - `protocol` - the protocol used: - `'http'` - HTTP. - `'https'` - HTTPS. - `'socket'` - UNIX domain socket or Windows named pipe. - `uri` - a string representing the connection (e.g. 'http://example.com:8080' or - 'socket:/unix/domain/socket/path'). + 'socket:/unix/domain/socket/path'). Only available when `info.port` is available. When the server contains more than one connection, each [`server.connections`](#serverconnections) array member provides its own `connection.info`. @@ -701,10 +705,14 @@ cache.set('norway', { capital: 'oslo' }, function (err) { ### `server.connection([options])` Adds an incoming server connection where: -- `host` - the hostname or IP address. Defaults to `0.0.0.0` which means any available network - interface. Set to `127.0.0.1` or `localhost` to restrict connection to only those coming from - the same machine. -- `port` - the TCP port the connection is listening to. Defaults to an ephemeral port (`0`) which +- `host` - the public hostname or IP address. Used only to set `server.info.host` and + `server.info.uri`. If not configured, defaults to the operating system hostname and if not + available, to `'localhost'`. +- `address` - sets the host name or IP address the connection will listen on. If not configured, + defaults to `host` if present, otherwise to all available network interfaces (i.e. `'0.0.0.0'`). + Set to `127.0.0.1` or `localhost` to restrict connection to only those coming from the same + machine. +- `port` - the TCP port the connection will listen to. Defaults to an ephemeral port (`0`) which uses an available port when the server is started (and assigned to `server.info.port`). If `port` is a string containing a '/' character, it is used as a UNIX domain socket path and if it starts with '\\.\pipe' as a Windows named pipe. diff --git a/lib/connection.js b/lib/connection.js index 8edb3858e..ea30fe9ae 100755 --- a/lib/connection.js +++ b/lib/connection.js @@ -43,19 +43,17 @@ exports = module.exports = internals.Connection = function (server, options) { this.settings.port = 0; } + this.type = (typeof this.settings.port === 'string' ? 'socket' : 'tcp'); + if (this.type === 'socket') { + this.settings.port = (this.settings.port.indexOf('/') !== -1 ? Path.resolve(this.settings.port) : this.settings.port.toLowerCase()); + } + if (this.settings.autoListen === undefined) { this.settings.autoListen = true; } Hoek.assert(this.settings.autoListen || !this.settings.port, 'Cannot specify port when autoListen is false'); - - // Connection properties - - this._port = this.settings.port; - this.type = (typeof this._port === 'string' ? 'socket' : 'tcp'); - if (this.type === 'socket') { - this._port = (this._port.indexOf('/') !== -1 ? Path.resolve(this._port) : this._port.toLowerCase()); - } + Hoek.assert(this.settings.autoListen || !this.settings.address, 'Cannot specify address when autoListen is false'); // Connection facilities @@ -94,13 +92,15 @@ exports = module.exports = internals.Connection = function (server, options) { this.info = { created: now, started: 0, - host: this._hostname(), - port: this._port, - protocol: this.type === 'tcp' ? (this.settings.tls ? 'https' : 'http') : this.type + host: this.settings.host || Os.hostname() || 'localhost', + protocol: this.type === 'tcp' ? (this.settings.tls ? 'https' : 'http') : this.type, + id: Os.hostname() + ':' + process.pid + ':' + now.toString(36) }; - this.info.uri = this.info.protocol + ':' + (this.type === 'tcp' ? '//' + this.info.host + ':' + this.info.port : this.info.port); - this.info.id = Os.hostname() + ':' + process.pid + ':' + now.toString(36); + if (this.settings.port) { + this.info.port = this.settings.port; + this.info.uri = this.info.protocol + ':' + (this.type === 'tcp' ? '//' + this.info.host + ':' + this.info.port : this.info.port); + } }; Hoek.inherits(internals.Connection, Events.EventEmitter); @@ -114,11 +114,11 @@ internals.Connection.prototype._init = function () { this.listener.once('listening', function () { - // Update the host, port, and uri with active values + // Update the address, port, and uri with active values if (self.type === 'tcp') { var address = self.listener.address(); - self.info.host = self._hostname(address.address); + self.info.address = address.address; self.info.port = address.port; self.info.uri = self.info.protocol + '://' + self.info.host + ':' + self.info.port; } @@ -140,12 +140,6 @@ internals.Connection.prototype._init = function () { }; -internals.Connection.prototype._hostname = function (address) { - - return this.settings.host || address || Os.hostname() || 'localhost'; -}; - - internals.Connection.prototype._start = function (callback) { if (this._started) { @@ -159,13 +153,12 @@ internals.Connection.prototype._start = function (callback) { return process.nextTick(callback); } - if (this.type !== 'tcp' || - !this.settings.host) { - - this.listener.listen(this._port, callback); + if (this.type !== 'tcp') { + this.listener.listen(this.settings.port, callback); } else { - this.listener.listen(this._port, this.settings.host, callback); + var address = this.settings.address || this.settings.host || '0.0.0.0'; + this.listener.listen(this.settings.port, address, callback); } }; diff --git a/lib/plugin.js b/lib/plugin.js index 974f14a6f..834296b03 100755 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -213,7 +213,7 @@ internals.Plugin.prototype.register = function (plugins /*, [options], callback for (var i = 0, il = selection.connections.length; i < il; ++i) { var connection = selection.connections[i]; - Hoek.assert(item.multiple || !connection._registrations[item.name], 'Plugin', item.name, 'already registered in:', connection.info.uri); + Hoek.assert(item.multiple || !connection._registrations[item.name], 'Plugin', item.name, 'already registered in:', connection.info.host + ':' + connection.settings.port); connection._registrations[item.name] = item; } diff --git a/lib/schema.js b/lib/schema.js index 0bdb36b5f..3c4d4991e 100755 --- a/lib/schema.js +++ b/lib/schema.js @@ -193,7 +193,8 @@ internals.server = Joi.object({ internals.connection = internals.connectionBase.keys({ autoListen: Joi.boolean(), - host: Joi.string().hostname().allow(null), + host: Joi.string().hostname(), + address: Joi.string().hostname(), labels: internals.labels, listener: Joi.any(), port: Joi.alternatives([ diff --git a/lib/server.js b/lib/server.js index 802251f86..0add90052 100755 --- a/lib/server.js +++ b/lib/server.js @@ -103,7 +103,7 @@ internals.Server.prototype.connection = function (options) { settings.routes.cors = Hoek.applyToDefaults(this._settings.connections.routes.cors || Defaults.cors, settings.routes.cors); settings.routes.security = Hoek.applyToDefaults(this._settings.connections.routes.security || Defaults.security, settings.routes.security); - settings = Schema.assert('connection', settings); // Applies validation changes (type cast) + settings = Schema.assert('connection', settings); // Applies validation changes (type cast) var connection = new Connection(this, settings); this.connections.push(connection); @@ -133,7 +133,7 @@ internals.Server.prototype.start = function (callback) { var connection = dependency.connections[s]; for (var d = 0, dl = dependency.deps.length; d < dl; ++d) { var dep = dependency.deps[d]; - Hoek.assert(connection._registrations[dep], 'Plugin', dependency.plugin, 'missing dependency', dep, 'in connection:', connection.info.uri); + Hoek.assert(connection._registrations[dep], 'Plugin', dependency.plugin, 'missing dependency', dep, 'in connection:', connection.info.host + ':' + connection.settings.port); } } } diff --git a/test/connection.js b/test/connection.js index 7d6030c76..a66d8b055 100755 --- a/test/connection.js +++ b/test/connection.js @@ -70,7 +70,7 @@ describe('Connection', function () { done(); }); - it('defaults to 0.0.0.0 or :: when no host is provided', function (done) { + it('defaults address to 0.0.0.0 or :: when no host is provided', function (done) { var server = new Hapi.Server(); server.connection(); @@ -81,7 +81,19 @@ describe('Connection', function () { expectedBoundAddress = '::'; } - expect(server.info.host).to.equal(expectedBoundAddress); + expect(server.info.address).to.equal(expectedBoundAddress); + done(); + }); + }); + + it('uses address when present instead of host', function (done) { + + var server = new Hapi.Server(); + server.connection({ host: 'no.such.domain.hapi', address: 'localhost' }); + server.start(function () { + + expect(server.info.host).to.equal('no.such.domain.hapi'); + expect(server.info.address).to.equal('127.0.0.1'); done(); }); }); @@ -93,7 +105,6 @@ describe('Connection', function () { server.connection({ port: port }); expect(server.connections[0].type).to.equal('socket'); - expect(server.connections[0]._port).to.equal(port); server.start(function () { @@ -116,7 +127,6 @@ describe('Connection', function () { server.connection({ port: port }); expect(server.connections[0].type).to.equal('socket'); - expect(server.connections[0]._port).to.equal(port); server.start(function () { @@ -265,8 +275,9 @@ describe('Connection', function () { expectedBoundAddress = '::'; } - expect(server.info.host).to.equal(expectedBoundAddress); - expect(server.info.port).to.not.equal(0); + expect(server.info.host).to.equal(Os.hostname()); + expect(server.info.address).to.equal(expectedBoundAddress); + expect(server.info.port).to.be.a.number().and.above(1); server.stop(); done(); }); @@ -300,9 +311,9 @@ describe('Connection', function () { }; var server = new Hapi.Server(); - server.connection(); + server.connection({ port: '8000' }); expect(server.info.host).to.equal('localhost'); - expect(server.info.uri).to.equal('http://localhost:' + server.info.port); + expect(server.info.uri).to.equal('http://localhost:8000'); done(); }); diff --git a/test/plugin.js b/test/plugin.js index cd47fe9c9..5702d24b2 100755 --- a/test/plugin.js +++ b/test/plugin.js @@ -1,5 +1,6 @@ // Load modules +var Os = require('os'); var Path = require('path'); var Boom = require('boom'); var CatboxMemory = require('catbox-memory'); @@ -317,14 +318,14 @@ describe('Plugin', function () { }; var server = new Hapi.Server(); - server.connection(); + server.connection({ host: 'example.com' }); server.register(test, function (err) { expect(err).to.not.exist(); expect(function () { server.register(test, function (err) { }); - }).to.throw('Plugin test already registered in: ' + server.info.uri); + }).to.throw('Plugin test already registered in: example.com:0'); done(); }); @@ -1096,7 +1097,7 @@ describe('Plugin', function () { expect(function () { server.start(); - }).to.throw('Plugin test missing dependency none in connection: ' + server.info.uri); + }).to.throw('Plugin test missing dependency none in connection: ' + Os.hostname() + ':0'); done(); }); }); @@ -1104,13 +1105,13 @@ describe('Plugin', function () { it('fails to register multiple plugins with dependencies', function (done) { var server = new Hapi.Server(); - server.connection(); + server.connection({ port: 80, host: 'localhost' }); server.register([internals.plugins.deps1, internals.plugins.deps3], function (err) { expect(function () { server.start(); - }).to.throw('Plugin deps1 missing dependency deps2 in connection: ' + server.info.uri); + }).to.throw('Plugin deps1 missing dependency deps2 in connection: localhost:80'); done(); }); }); @@ -1176,13 +1177,13 @@ describe('Plugin', function () { }; var server = new Hapi.Server(); - server.connection(); + server.connection({ port: 80, host: 'localhost' }); server.register(a, function (err) { expect(function () { server.start(); - }).to.throw('Plugin b missing dependency c in connection: ' + server.info.uri); + }).to.throw('Plugin b missing dependency c in connection: localhost:80'); done(); }); });