zoodubbo
Version:
A Javascript module for Node.js to connect Dubbo service by node-zookeeper-client.
349 lines (317 loc) • 8.79 kB
JavaScript
/**
* Created by Corey600 on 2016/6/15.
*/
'use strict';
// require core modules
var url = require('url');
var querystring = require('querystring');
// require thirdpart modules
var debug = require('debug');
// require custom modules
var createPool = require('./pool');
var Codec = require('./codec');
var Register = require('./register');
var RandomLoadBalance = require('./loadbalance/RandomLoadBalance');
// print debug info
var error = debug('zoodubbo:error');
var warn = debug('zoodubbo:warn');
/**
* Constructor of Invoker.
*
* @param {Client|String|Array} zk // The ZD instance or the uri of providers.
* @param {Object} opt
* {
* path: String // The path of service node.
* version: String, // The version of service.
* timeout: Number, // Timeout in milliseconds, defaults to 60 seconds.
* }
* @returns {Invoker}
* @constructor
*/
function Invoker(zk, opt) {
if (!(this instanceof Invoker)) return new Invoker(zk, opt);
var option = opt || {};
this._path = option.path;
this._dubbo = option.dubbo;
this._version = option.version;
this._timeout = option.timeout;
this._poolMax = option.poolMax;
this._poolMin = option.poolMin;
this._providers = null; // Array
this._configurators = null; // Object
this._loadBalance = new RandomLoadBalance();
this._parseProviders = this._parseProviders.bind(this);
this._parseConfigurators = this._parseConfigurators.bind(this);
if ('string' === typeof zk) {
this._uris = [zk];
} else if (Array.isArray(zk)) {
this._uris = zk;
} else if (zk) {
this._registerProviders = new Register(zk, '/dubbo/' + this._path + '/providers');
this._registerConfigurators = new Register(zk, '/dubbo/' + this._path + '/configurators');
this._registerProviders.on('change', this._parseProviders);
this._registerConfigurators.on('change', this._parseConfigurators);
}
}
/**
* Excute the method
*
* @param {String} method
* @param {Array|Object} args
* {
* args: Array
* timeout: number
* }
* @param {Function} [cb]
* @public
*/
Invoker.prototype.excute = function (method, args, cb) {
var option = args;
if (Array.isArray(option)) {
option = { args: option };
}
option.method = method;
var self = this;
if ('function' !== typeof cb) {
return new Promise(function (resolve, reject) {
try {
return self._excute(option, function (err, data) {
if (err) {
return reject(err);
}
return resolve(data);
});
} catch (err) {
return reject(err);
}
});
}
try {
return self._excute(option, cb);
} catch (err) {
return cb(err);
}
};
/**
* Excute the method.
*
* @param option
* {
* method: String
* args: Array
* timeout: number
* }
* @param {Function} cb
* @private
*/
Invoker.prototype._excute = function (option, cb) {
var self = this;
var method = option.method;
var args = option.args;
var reConnect = false;
var codec = new Codec();
var fetchData = function (err, provider) {
if (err) {
cb(err);
return;
}
if (!~provider.methods.indexOf(method)) {
cb(new Error('Can\'t find the method:' + method
+ ' form provider: ' + provider.host + ':' + provider.port
+ ', please check it!'));
return;
}
var pool = provider.pool;
var buffer = codec.encodeRequest(self, provider, method, args);
var resource = pool.acquire();
resource.then(function (client) {
client.once('error', function (e) {
error(err);
client.removeAllListeners('data');
pool.release(client);
if (!reConnect) {
reConnect = true;
setTimeout(function () {
self._getChildren(fetchData);
}, 1000);
} else {
cb(e);
}
});
client.on('data', function (chunk) {
if (codec.pushChunk(chunk)) {
client.removeAllListeners('data');
pool.release(client);
codec.decodeResponse(cb);
}
});
codec.clearChunk();
client.write(buffer);
}).catch(function (e) {
cb(e);
});
};
return self._getChildren(fetchData);
};
/**
* 获取注册中心 Providers 和 Configurators 信息
*
* @param cb
* @private
*/
Invoker.prototype._getChildren = function (cb) {
var self = this;
Promise.all([
this._getProviders(),
this._getConfigurators()
])
.then(function () {
if (!self._providers || 0 >= self._providers.length) {
throw new Error('Can\'t find children from the node: ' + self._path +
' ,please check the path!');
}
var providers = [];
self._providers.forEach(function (item) {
var configurator = self._configurators[item.host + ':' + item.port];
providers.push(Object.assign({}, item, configurator));
});
var provider;
provider = self._loadBalance.select(providers);
if (!provider.pool) {
provider.pool = createPool(provider.host, provider.port, {
max: self._poolMax,
min: self._poolMin
});
}
cb(false, provider);
})
.catch(function (err) {
cb(err);
});
};
/**
* 获取注册中心 Providers 信息
*
* @returns {Promise}
* @private
*/
Invoker.prototype._getProviders = function () {
var self = this;
return new Promise(function (resolve, reject) {
if (self._uris) {
self._parseProviders(self._uris);
self._uris = null;
}
if (self._registerProviders && self._registerProviders.isUnwatched()) {
self._registerProviders.getChildren(function (err, uris) {
if (err) return reject(err);
var providers;
try {
providers = self._parseProviders(uris);
} catch (e) {
return reject(e);
}
return resolve(providers);
});
} else {
resolve(self._providers);
}
});
};
/**
* 获取注册中心 Configurators 信息
*
* @returns {Promise}
* @private
*/
Invoker.prototype._getConfigurators = function () {
var self = this;
return new Promise(function (resolve, reject) {
if (self._registerConfigurators && self._registerConfigurators.isUnwatched()) {
self._registerConfigurators.getChildren(function (err, uris) {
if (err) return reject(err);
var configurators;
try {
configurators = self._parseConfigurators(uris);
} catch (e) {
return reject(e);
}
return resolve(configurators);
});
} else {
resolve(self._configurators);
}
});
};
/**
* 解析 Providers 的 URIs
*
* @param uris
* @returns {Array}
* @private
*/
Invoker.prototype._parseProviders = function (uris) {
var self = this;
var providers = [];
(uris || []).forEach(function (uri) {
var provider;
try {
var parsed = url.parse(uri);
provider = querystring.parse(parsed.query);
provider.methods = provider.methods.split(',');
provider.host = parsed.hostname;
if (parsed.port) provider.port = parseInt(parsed.port, 10);
if (provider.weight) provider.weight = parseInt(provider.weight, 10);
if (provider.warmup) provider.warmup = parseInt(provider.warmup, 10);
if (provider.timestamp) provider.timestamp = parseInt(provider.timestamp, 10);
(self._providers || []).forEach(function (item) {
if (item.host === provider.host && item.port === provider.port) {
provider.pool = item.pool;
} else if (item.pool) {
item.pool.drain().then(function () {
item.pool.clear();
});
}
});
} catch (err) {
warn('Parse children of providers error. uri: ' + uri);
return;
}
providers.push(provider);
});
self._providers = providers;
return providers;
};
/**
* 解析 Configurators 的 URIs
*
* @param uris
* @returns {Object}
* @private
*/
Invoker.prototype._parseConfigurators = function (uris) {
var configurators = {};
(uris || []).forEach(function (uri) {
var configurator;
try {
var parsed = url.parse(uri);
configurator = querystring.parse(parsed.query);
configurator.host = parsed.hostname;
if (parsed.port) configurator.port = parseInt(parsed.port, 10);
if (configurator.weight) configurator.weight = parseInt(configurator.weight, 10);
if (!configurator.enabled || 'false' === configurator.enabled) return;
} catch (err) {
warn('Parse children of configurators error. uri: ' + uri);
return;
}
configurators[configurator.host + ':' + configurator.port] = configurator;
});
this._configurators = configurators;
return configurators;
};
/**
* Expose `Invoker`.
*
* @type {Invoker}
*/
module.exports = Invoker;