Skip to content
This repository was archived by the owner on May 14, 2024. It is now read-only.

Commit fadf3fc

Browse files
authored
Merge pull request #658 from wision/multi-servers
Client support multiple servers
2 parents 2a73ee7 + ed15ecf commit fadf3fc

File tree

4 files changed

+55
-20
lines changed

4 files changed

+55
-20
lines changed

docs/client.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ The code to create a new client looks like:
1414

1515
var ldap = require('ldapjs');
1616
var client = ldap.createClient({
17-
url: 'ldap://127.0.0.1:1389'
17+
url: ['ldap://127.0.0.1:1389', 'ldap://127.0.0.2:1389']
1818
});
1919

2020
You can use `ldap://` or `ldaps://`; the latter would connect over SSL (note
@@ -24,7 +24,7 @@ client is:
2424

2525
|Attribute |Description |
2626
|---------------|-----------------------------------------------------------|
27-
|url |A valid LDAP URL (proto/host/port only) |
27+
|url |A string or array of valid LDAP URL(s) (proto/host/port) |
2828
|socketPath |Socket path if using AF\_UNIX sockets |
2929
|log |A compatible logger instance (Default: no-op logger) |
3030
|timeout |Milliseconds client should let operations live for before timing out (Default: Infinity)|
@@ -34,6 +34,13 @@ client is:
3434
|strictDN |Force strict DN parsing for client methods (Default is true)|
3535
|reconnect |Try to reconnect when the connection gets lost (Default is false)|
3636

37+
### url
38+
This parameter takes a single connection string or an array of connection strings
39+
as an input. In case an array is provided, the client tries to connect to the
40+
servers in given order. To achieve random server strategy (e.g. to distribute
41+
the load among the servers), please shuffle the array before passing it as an
42+
argument.
43+
3744
### Note On Logger
3845

3946
A passed in logger is expected to conform to the [Bunyan](https://www.npmjs.com/package/bunyan)

lib/client/client.js

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,13 @@ function Client (options) {
110110
EventEmitter.call(this, options)
111111

112112
var self = this
113-
var _url
114-
if (options.url) { _url = url.parse(options.url) }
115-
this.host = _url ? _url.hostname : undefined
116-
this.port = _url ? _url.port : false
117-
this.secure = _url ? _url.secure : false
118-
this.url = _url
113+
this.urls = options.url ? [].concat(options.url).map(url.parse) : []
114+
this._nextServer = 0
115+
// updated in connectSocket() after each connect
116+
this.host = undefined
117+
this.port = undefined
118+
this.secure = undefined
119+
this.url = undefined
119120
this.tlsOptions = options.tlsOptions
120121
this.socketPath = options.socketPath || false
121122

@@ -792,6 +793,9 @@ Client.prototype.connect = function connect () {
792793

793794
// Establish basic socket connection
794795
function connectSocket (cb) {
796+
var server = self.urls[self._nextServer]
797+
self._nextServer = (self._nextServer + 1) % self.urls.length
798+
795799
cb = once(cb)
796800

797801
function onResult (err, res) {
@@ -820,16 +824,17 @@ Client.prototype.connect = function connect () {
820824
setupClient(cb)
821825
}
822826

823-
var port = (self.port || self.socketPath)
824-
if (self.secure) {
825-
socket = tls.connect(port, self.host, self.tlsOptions)
827+
var port = (server && server.port) || self.socketPath
828+
var host = server && server.hostname
829+
if (server && server.secure) {
830+
socket = tls.connect(port, host, self.tlsOptions)
826831
socket.once('secureConnect', onConnect)
827832
} else {
828-
socket = net.connect(port, self.host)
833+
socket = net.connect(port, host)
829834
socket.once('connect', onConnect)
830835
}
831836
socket.once('error', onResult)
832-
initSocket()
837+
initSocket(server)
833838

834839
// Setup connection timeout handling, if desired
835840
if (self.connectTimeout) {
@@ -844,9 +849,9 @@ Client.prototype.connect = function connect () {
844849
}
845850

846851
// Initialize socket events and LDAP parser.
847-
function initSocket () {
852+
function initSocket (url) {
848853
tracker = messageTrackerFactory({
849-
id: self.url ? self.url.href : self.socketPath,
854+
id: url ? url.href : self.socketPath,
850855
parser: new Parser({ log: log })
851856
})
852857

@@ -965,6 +970,13 @@ Client.prototype.connect = function connect () {
965970
self.emit('socketTimeout')
966971
socket.end()
967972
})
973+
974+
var server = self.urls[self._nextServer]
975+
if (server) {
976+
self.host = server.hostname
977+
self.port = server.port
978+
self.secure = server.secure
979+
}
968980
}
969981

970982
var retry
@@ -975,12 +987,15 @@ Client.prototype.connect = function connect () {
975987
maxDelay: this.reconnect.maxDelay
976988
})
977989
failAfter = this.reconnect.failAfter
990+
if (this.urls.length > 1 && failAfter) {
991+
failAfter *= this.urls.length
992+
}
978993
} else {
979994
retry = backoff.exponential({
980995
initialDelay: 1,
981996
maxDelay: 2
982997
})
983-
failAfter = 1
998+
failAfter = this.urls.length || 1
984999
}
9851000
retry.failAfter(failAfter)
9861001

lib/client/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module.exports = {
77
Client: Client,
88
createClient: function createClient (options) {
99
if (isObject(options) === false) throw TypeError('options (object) required')
10-
if (options.url && typeof options.url !== 'string') throw TypeError('options.url (string) required')
10+
if (options.url && typeof options.url !== 'string' && !Array.isArray(options.url)) throw TypeError('options.url (string|array) required')
1111
if (options.socketPath && typeof options.socketPath !== 'string') throw TypeError('options.socketPath must be a string')
1212
if ((options.url && options.socketPath) || !(options.url || options.socketPath)) throw TypeError('options.url ^ options.socketPath (String) required')
1313
if (!options.log) options.log = logger

test/client.test.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,10 +340,9 @@ tap.test('createClient', t => {
340340
t.throws(() => ldap.createClient(42), match)
341341
})
342342

343-
t.test('url must be a string', async t => {
344-
const match = /options\.url \(string\) required/
343+
t.test('url must be a string or array', async t => {
344+
const match = /options\.url \(string\|array\) required/
345345
t.throws(() => ldap.createClient({ url: {} }), match)
346-
t.throws(() => ldap.createClient({ url: [] }), match)
347346
t.throws(() => ldap.createClient({ url: 42 }), match)
348347
})
349348

@@ -379,6 +378,20 @@ tap.test('createClient', t => {
379378
}
380379
})
381380

381+
t.test('url array is correctly assigned', async t => {
382+
getPort().then(function (unusedPortNumber) {
383+
const client = ldap.createClient({
384+
url: [
385+
`ldap://127.0.0.1:${unusedPortNumber}`,
386+
`ldap://127.0.0.2:${unusedPortNumber}`
387+
],
388+
connectTimeout: 1
389+
})
390+
391+
t.equal(client.urls.length, 2)
392+
})
393+
})
394+
382395
// TODO: this test is really flaky. It would be better if we could validate
383396
// the options _withouth_ having to connect to a server.
384397
// t.test('attaches a child function to logger', async t => {

0 commit comments

Comments
 (0)