2012-04-27 03:23:43 +00:00
|
|
|
// Copyright 2012 Mark Cavage, Inc. All rights reserved.
|
|
|
|
|
|
|
|
var assert = require('assert');
|
|
|
|
var EventEmitter = require('events').EventEmitter;
|
|
|
|
var util = require('util');
|
|
|
|
|
|
|
|
var pooling = require('pooling');
|
|
|
|
|
|
|
|
var ConnectionError = require('../errors').ConnectionError;
|
|
|
|
var BindResponse = require('../messages').BindResponse;
|
|
|
|
|
|
|
|
var Client = require('./client');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///--- Globals
|
|
|
|
|
|
|
|
var STD_OPS = [
|
|
|
|
'add',
|
|
|
|
'del',
|
|
|
|
'modify',
|
|
|
|
'modifyDN'
|
|
|
|
];
|
|
|
|
|
|
|
|
var RETURN_VAL_OPS = [
|
|
|
|
'compare',
|
|
|
|
'exop'
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///--- Internal Functions
|
|
|
|
|
|
|
|
function createPool(options) {
|
|
|
|
assert.ok(options);
|
|
|
|
|
|
|
|
return pooling.createPool({
|
|
|
|
checkInterval: options.checkInterval,
|
|
|
|
log: options.log,
|
2013-02-26 05:11:52 +00:00
|
|
|
name: 'ldapjs_' + (options.url || options.socketPath).replace(/[:/]/g, '_'),
|
2012-04-27 03:23:43 +00:00
|
|
|
max: options.maxConnections,
|
|
|
|
maxIdleTime: options.maxIdleTime,
|
|
|
|
|
2012-04-27 18:02:49 +00:00
|
|
|
create: function createClient(callback) {
|
2012-04-27 03:23:43 +00:00
|
|
|
var client = new Client(options);
|
|
|
|
|
2012-07-09 12:23:53 +00:00
|
|
|
client.on('error', function (err) {
|
|
|
|
client.removeAllListeners('connect');
|
2012-04-27 03:23:43 +00:00
|
|
|
return callback(err);
|
|
|
|
});
|
|
|
|
|
|
|
|
client.once('connect', function onConnect() {
|
|
|
|
client.removeAllListeners('error');
|
|
|
|
|
|
|
|
if (!options.bindDN || !options.bindCredentials)
|
|
|
|
return callback(null, client);
|
|
|
|
|
|
|
|
function bindCallback(err, res) {
|
|
|
|
if (err)
|
|
|
|
return callback(err, null);
|
|
|
|
|
|
|
|
return callback(null, client);
|
|
|
|
}
|
|
|
|
|
|
|
|
return client.bind(options.bindDN,
|
|
|
|
options.bindCredentials,
|
|
|
|
options.bindControls || [],
|
|
|
|
bindCallback);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
check: function check(client, callback) {
|
|
|
|
// just do a root dse search
|
|
|
|
client.search('', '(objectclass=*)', function (err, res) {
|
|
|
|
if (err)
|
|
|
|
return callback(err);
|
|
|
|
|
|
|
|
res.on('error', function (e) {
|
2012-07-13 14:01:31 +00:00
|
|
|
client.removeAllListeners('end');
|
2012-07-10 22:40:34 +00:00
|
|
|
callback(e);
|
2012-04-27 03:23:43 +00:00
|
|
|
});
|
|
|
|
|
2012-07-10 22:40:34 +00:00
|
|
|
res.on('end', function () {
|
2012-07-13 14:01:31 +00:00
|
|
|
client.removeAllListeners('error');
|
2012-07-10 22:40:34 +00:00
|
|
|
callback(null);
|
2012-04-27 03:23:43 +00:00
|
|
|
});
|
2012-09-20 19:38:46 +00:00
|
|
|
|
|
|
|
return undefined;
|
2012-04-27 03:23:43 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
destroy: function destroy(client) {
|
|
|
|
client.unbind(function () {});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///--- API
|
|
|
|
|
|
|
|
function ClientPool(options) {
|
|
|
|
assert.ok(options);
|
|
|
|
EventEmitter.call(this, options);
|
|
|
|
|
|
|
|
this.log = options.log.child({clazz: 'ClientPool'}, true);
|
|
|
|
this.options = {
|
|
|
|
bindDN: options.bindDN,
|
|
|
|
bindCredentials: options.bindCredentials,
|
|
|
|
bindControls: options.bindControls || [],
|
|
|
|
checkInterval: options.checkInterval,
|
|
|
|
connectTimeout: (options.connectTimeout || 0),
|
|
|
|
maxIdleTime: options.maxIdleTime,
|
|
|
|
maxConnections: options.maxConnections,
|
|
|
|
log: options.log,
|
|
|
|
socketPath: options.socketPath,
|
|
|
|
timeout: (options.timeout || 0),
|
2013-03-15 12:23:43 +00:00
|
|
|
url: options.url,
|
|
|
|
tlsOptions: options.tlsOptions
|
2012-04-27 03:23:43 +00:00
|
|
|
};
|
|
|
|
this.pool = createPool(options);
|
|
|
|
}
|
|
|
|
util.inherits(ClientPool, EventEmitter);
|
|
|
|
module.exports = ClientPool;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
STD_OPS.forEach(function (op) {
|
|
|
|
ClientPool.prototype[op] = function clientProxy() {
|
|
|
|
var args = Array.prototype.slice.call(arguments);
|
|
|
|
var cb = args.pop();
|
|
|
|
if (typeof (cb) !== 'function')
|
|
|
|
throw new TypeError('callback (Function) required');
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
return this.pool.acquire(function onAcquire(err, client) {
|
|
|
|
if (err)
|
|
|
|
return cb(err);
|
|
|
|
|
|
|
|
args.push(function proxyCallback(err, res) {
|
|
|
|
self.pool.release(client);
|
|
|
|
return cb(err, res);
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
return Client.prototype[op].apply(client, args);
|
|
|
|
} catch (e) {
|
|
|
|
self.pool.release(client);
|
|
|
|
return cb(e);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
RETURN_VAL_OPS.forEach(function (op) {
|
|
|
|
ClientPool.prototype[op] = function clientProxy() {
|
|
|
|
var args = Array.prototype.slice.call(arguments);
|
|
|
|
var cb = args.pop();
|
|
|
|
if (typeof (cb) !== 'function')
|
|
|
|
throw new TypeError('callback (Function) required');
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
return this.pool.acquire(function onAcquire(poolErr, client) {
|
|
|
|
if (poolErr)
|
|
|
|
return cb(poolErr);
|
|
|
|
|
|
|
|
args.push(function proxyCallback(err, val, res) {
|
|
|
|
self.pool.release(client);
|
|
|
|
return cb(err, val, res);
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
return Client.prototype[op].apply(client, args);
|
|
|
|
} catch (e) {
|
|
|
|
self.pool.release(client);
|
|
|
|
return cb(e);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
ClientPool.prototype.search = function search(base, opts, controls, callback) {
|
|
|
|
if (typeof (controls) === 'function') {
|
|
|
|
callback = controls;
|
|
|
|
controls = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
return this.pool.acquire(function onAcquire(err, client) {
|
|
|
|
if (err)
|
|
|
|
return callback(err);
|
|
|
|
|
|
|
|
// This is largely in existence for search requests
|
|
|
|
client.timeout = self.timeout || client.timeout;
|
|
|
|
|
|
|
|
|
|
|
|
return client.search(base, opts, controls, function (err, res) {
|
|
|
|
function cleanup() {
|
|
|
|
self.pool.release(client);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
cleanup();
|
|
|
|
return callback(err, res);
|
|
|
|
}
|
|
|
|
res.on('error', cleanup);
|
|
|
|
res.on('end', cleanup);
|
|
|
|
|
|
|
|
return callback(null, res);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
ClientPool.prototype.abandon = function abandon(msgid, controls, callback) {
|
|
|
|
if (typeof (controls) === 'function') {
|
|
|
|
callback = controls;
|
|
|
|
controls = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
this.log.error({
|
|
|
|
messageID: msgid
|
|
|
|
}, 'Abandon is not supported with connection pooling. Ignoring.');
|
|
|
|
return callback(null);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
ClientPool.prototype.bind = function bind(dn, creds, controls, callback) {
|
|
|
|
if (typeof (controls) === 'function') {
|
|
|
|
callback = controls;
|
|
|
|
controls = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
self.options.bindDN = null;
|
|
|
|
self.options.bindCredentials = null;
|
|
|
|
self.options.bindControls = null;
|
|
|
|
|
|
|
|
return this.pool.shutdown(function () {
|
|
|
|
self.pool = createPool(self.options);
|
|
|
|
|
|
|
|
return self.pool.acquire(function onAcquire(err, client) {
|
|
|
|
if (err)
|
|
|
|
return callback(err);
|
|
|
|
|
|
|
|
return client.bind(dn, creds, controls, function (err, res) {
|
|
|
|
self.pool.release(client);
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
return callback(err, res);
|
|
|
|
|
|
|
|
self.options.bindDN = dn;
|
|
|
|
self.options.bindCredentials = creds;
|
|
|
|
self.options.bindControls = controls;
|
|
|
|
|
|
|
|
return callback(null, res);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
ClientPool.prototype.unbind = function unbind(callback) {
|
|
|
|
return this.pool.shutdown(callback);
|
|
|
|
};
|