Merge pull request #658 from wision/multi-servers
Client support multiple servers
This commit is contained in:
commit
fadf3fcc64
|
@ -14,7 +14,7 @@ The code to create a new client looks like:
|
|||
|
||||
var ldap = require('ldapjs');
|
||||
var client = ldap.createClient({
|
||||
url: 'ldap://127.0.0.1:1389'
|
||||
url: ['ldap://127.0.0.1:1389', 'ldap://127.0.0.2:1389']
|
||||
});
|
||||
|
||||
You can use `ldap://` or `ldaps://`; the latter would connect over SSL (note
|
||||
|
@ -24,7 +24,7 @@ client is:
|
|||
|
||||
|Attribute |Description |
|
||||
|---------------|-----------------------------------------------------------|
|
||||
|url |A valid LDAP URL (proto/host/port only) |
|
||||
|url |A string or array of valid LDAP URL(s) (proto/host/port) |
|
||||
|socketPath |Socket path if using AF\_UNIX sockets |
|
||||
|log |A compatible logger instance (Default: no-op logger) |
|
||||
|timeout |Milliseconds client should let operations live for before timing out (Default: Infinity)|
|
||||
|
@ -34,6 +34,13 @@ client is:
|
|||
|strictDN |Force strict DN parsing for client methods (Default is true)|
|
||||
|reconnect |Try to reconnect when the connection gets lost (Default is false)|
|
||||
|
||||
### url
|
||||
This parameter takes a single connection string or an array of connection strings
|
||||
as an input. In case an array is provided, the client tries to connect to the
|
||||
servers in given order. To achieve random server strategy (e.g. to distribute
|
||||
the load among the servers), please shuffle the array before passing it as an
|
||||
argument.
|
||||
|
||||
### Note On Logger
|
||||
|
||||
A passed in logger is expected to conform to the [Bunyan](https://www.npmjs.com/package/bunyan)
|
||||
|
|
|
@ -110,12 +110,13 @@ function Client (options) {
|
|||
EventEmitter.call(this, options)
|
||||
|
||||
var self = this
|
||||
var _url
|
||||
if (options.url) { _url = url.parse(options.url) }
|
||||
this.host = _url ? _url.hostname : undefined
|
||||
this.port = _url ? _url.port : false
|
||||
this.secure = _url ? _url.secure : false
|
||||
this.url = _url
|
||||
this.urls = options.url ? [].concat(options.url).map(url.parse) : []
|
||||
this._nextServer = 0
|
||||
// updated in connectSocket() after each connect
|
||||
this.host = undefined
|
||||
this.port = undefined
|
||||
this.secure = undefined
|
||||
this.url = undefined
|
||||
this.tlsOptions = options.tlsOptions
|
||||
this.socketPath = options.socketPath || false
|
||||
|
||||
|
@ -792,6 +793,9 @@ Client.prototype.connect = function connect () {
|
|||
|
||||
// Establish basic socket connection
|
||||
function connectSocket (cb) {
|
||||
var server = self.urls[self._nextServer]
|
||||
self._nextServer = (self._nextServer + 1) % self.urls.length
|
||||
|
||||
cb = once(cb)
|
||||
|
||||
function onResult (err, res) {
|
||||
|
@ -820,16 +824,17 @@ Client.prototype.connect = function connect () {
|
|||
setupClient(cb)
|
||||
}
|
||||
|
||||
var port = (self.port || self.socketPath)
|
||||
if (self.secure) {
|
||||
socket = tls.connect(port, self.host, self.tlsOptions)
|
||||
var port = (server && server.port) || self.socketPath
|
||||
var host = server && server.hostname
|
||||
if (server && server.secure) {
|
||||
socket = tls.connect(port, host, self.tlsOptions)
|
||||
socket.once('secureConnect', onConnect)
|
||||
} else {
|
||||
socket = net.connect(port, self.host)
|
||||
socket = net.connect(port, host)
|
||||
socket.once('connect', onConnect)
|
||||
}
|
||||
socket.once('error', onResult)
|
||||
initSocket()
|
||||
initSocket(server)
|
||||
|
||||
// Setup connection timeout handling, if desired
|
||||
if (self.connectTimeout) {
|
||||
|
@ -844,9 +849,9 @@ Client.prototype.connect = function connect () {
|
|||
}
|
||||
|
||||
// Initialize socket events and LDAP parser.
|
||||
function initSocket () {
|
||||
function initSocket (url) {
|
||||
tracker = messageTrackerFactory({
|
||||
id: self.url ? self.url.href : self.socketPath,
|
||||
id: url ? url.href : self.socketPath,
|
||||
parser: new Parser({ log: log })
|
||||
})
|
||||
|
||||
|
@ -965,6 +970,13 @@ Client.prototype.connect = function connect () {
|
|||
self.emit('socketTimeout')
|
||||
socket.end()
|
||||
})
|
||||
|
||||
var server = self.urls[self._nextServer]
|
||||
if (server) {
|
||||
self.host = server.hostname
|
||||
self.port = server.port
|
||||
self.secure = server.secure
|
||||
}
|
||||
}
|
||||
|
||||
var retry
|
||||
|
@ -975,12 +987,15 @@ Client.prototype.connect = function connect () {
|
|||
maxDelay: this.reconnect.maxDelay
|
||||
})
|
||||
failAfter = this.reconnect.failAfter
|
||||
if (this.urls.length > 1 && failAfter) {
|
||||
failAfter *= this.urls.length
|
||||
}
|
||||
} else {
|
||||
retry = backoff.exponential({
|
||||
initialDelay: 1,
|
||||
maxDelay: 2
|
||||
})
|
||||
failAfter = 1
|
||||
failAfter = this.urls.length || 1
|
||||
}
|
||||
retry.failAfter(failAfter)
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ module.exports = {
|
|||
Client: Client,
|
||||
createClient: function createClient (options) {
|
||||
if (isObject(options) === false) throw TypeError('options (object) required')
|
||||
if (options.url && typeof options.url !== 'string') throw TypeError('options.url (string) required')
|
||||
if (options.url && typeof options.url !== 'string' && !Array.isArray(options.url)) throw TypeError('options.url (string|array) required')
|
||||
if (options.socketPath && typeof options.socketPath !== 'string') throw TypeError('options.socketPath must be a string')
|
||||
if ((options.url && options.socketPath) || !(options.url || options.socketPath)) throw TypeError('options.url ^ options.socketPath (String) required')
|
||||
if (!options.log) options.log = logger
|
||||
|
|
|
@ -340,10 +340,9 @@ tap.test('createClient', t => {
|
|||
t.throws(() => ldap.createClient(42), match)
|
||||
})
|
||||
|
||||
t.test('url must be a string', async t => {
|
||||
const match = /options\.url \(string\) required/
|
||||
t.test('url must be a string or array', async t => {
|
||||
const match = /options\.url \(string\|array\) required/
|
||||
t.throws(() => ldap.createClient({ url: {} }), match)
|
||||
t.throws(() => ldap.createClient({ url: [] }), match)
|
||||
t.throws(() => ldap.createClient({ url: 42 }), match)
|
||||
})
|
||||
|
||||
|
@ -379,6 +378,20 @@ tap.test('createClient', t => {
|
|||
}
|
||||
})
|
||||
|
||||
t.test('url array is correctly assigned', async t => {
|
||||
getPort().then(function (unusedPortNumber) {
|
||||
const client = ldap.createClient({
|
||||
url: [
|
||||
`ldap://127.0.0.1:${unusedPortNumber}`,
|
||||
`ldap://127.0.0.2:${unusedPortNumber}`
|
||||
],
|
||||
connectTimeout: 1
|
||||
})
|
||||
|
||||
t.equal(client.urls.length, 2)
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: this test is really flaky. It would be better if we could validate
|
||||
// the options _withouth_ having to connect to a server.
|
||||
// t.test('attaches a child function to logger', async t => {
|
||||
|
|
Loading…
Reference in New Issue