thalassa-http-client
Version:
A simple http client for Thalassa that doesn't require ZeroMQ
280 lines (237 loc) • 7.79 kB
JavaScript
var os = require('os')
, util = require('util')
, ip = require('ip')
, request = require('request')
, registrations = require('thalassa-registrations')
, EventEmitter = require('events').EventEmitter
;
/**
* `Client` constructor
* @constructor
*
* @param {number} [opts.apiport=9000] - Port number of Thalassa HTTP API
* @param {String} [opts.host=127.0.0.1] - Thalassa host
* @param {number} [opts.updateFreq=20000] - How often to check into registrations to Thalassa server
* @param {number} [opts.updateTimeout=2500] - How long to wait for a registration request to respond
* @param {String} [opts.mode=http]
*/
var Client = module.exports = function (opts) {
if (typeof opts !== 'object') opts = {};
this.log = (typeof opts.log === 'function') ? opts.log : function (){};
EventEmitter.call(this);
this.APIPORT = opts.apiport || 9000;
this.HOST = opts.host || '127.0.0.1';
this.UPDATE_FREQ = opts.updateFreq || 20000;
this.TIMEOUT = opts.updateTimeout || 2500;
this.SEC_TO_EXPIRE = opts.secsToExpire || 60;
this.MODE = opts.mode || 'http';
this.MY_IP = ip.address();
this.isOn = false;
this.intents = [];
this.pending = {};
};
util.inherits(Client, EventEmitter);
/**
* Create a new registration
*
* @param {String} name - Name of the service (preferably require('./package.json').name)
* @param {String} version - Version of the service (preferably require('./package.json').version)
* @param {String} port - Port this registration is bound to that clients can call
* @param {Object} meta - Any additional meta data (key, value) to include
*/
Client.prototype.register = function(name, version, port, meta) {
var self = this;
var reg = {
name: name,
version: version,
host: self.MY_IP,
port: port,
meta: meta || {}
};
if (!reg.meta.hostname) {
reg.meta.hostname = reg.host;
}
if (!reg.meta.secondsToExpire) {
reg.meta.secondsToExpire = this.SEC_TO_EXPIRE;
}
reg.meta.hostname = os.hostname();
reg.meta.pid = process.pid;
reg.meta.registered = Date.now();
var intent = registrations.create(reg);
self.intents.push(intent);
if (self.isOn) {
self._sendUpdate(intent);
}
};
/**
* Unregister a previously registered registration
*
* @param {String} name - Name of the service (preferably require('./package.json').name)
* @param {String} version - Version of the service (preferably require('./package.json').version)
* @param {String} port - Port this registration is bound to that clients can call
*/
Client.prototype.unregister = function(name, version, port) {
var self = this;
var host = self.MY_IP;
var reg = registrations.create({
name: name,
version: version,
host: host,
port: port,
});
//
// filter out the unwanted registration intent
//
self.intents = self.intents.filter(function (intent) {
return reg.id !== intent.id;
});
//
// explicitely delete the registration so the offline notifications happen immediately
//
self.del(name, version, host, port);
};
/**
* Explicitly delete a registration
*
* @param {String} name - Name of the service
* @param {String} version - Version of the service
* @param {String} port - Port of the registration
* @param {String} host - Host (ip) of the registration
*/
Client.prototype.del = function (name, version, host, port, cb) {
var self = this;
var uri = util.format('http://%s:%s/registrations/%s/%s/%s/%s', self.HOST, self.APIPORT, name, version, host, port);
request({
uri: uri,
json: true,
method: 'DELETE'
},
function (error, response, body) {
if (error) self.log('error', 'Thalassa:Client.del', error);
if (response && response.statusCode !== 200 && response.statusCode !== 404) {
self.log('error', 'Thalassa:Client.del unexpected response ' + response.statusCode);
error = new Error("del unexpected response " + response.statusCode);
}
if (typeof cb === 'function') cb(error);
});
};
/**
* Start polling, periodically checking the registrations into the Thalassa server
*/
Client.prototype.start = function() {
var self = this;
if (!self.isOn) {
self.isOn = true;
self._startUpdateInterval();
}
};
/**
* Stop polling registrations
*/
Client.prototype.stop = function() {
var self = this;
self.isOn = false;
clearInterval(self._updateInterval);
};
/**
* Close: stop polling
*/
Client.prototype.close = function() {
var self = this;
this.stop();
};
/**
* Find all `Registration`s over HTTP for `name` and `version` if provided.
*
* @param {String} [name]
* @param {String} [version]
* @param {getRegistrationsCallback} cb - Callback cb(error, registrations)
*
* @callback getRegistrationsCallback
* @param {Error} error
* @param {Registrations[]} registrations
*/
Client.prototype.getRegistrations = function(name, version, cb) {
var self = this;
var path;
if (typeof name === 'function') {
cb = name;
path = '/registrations';
}
else if (typeof version === 'function') {
cb = version;
path = util.format('/registrations/%s', name);
}
else if (typeof cb === 'function') {
path = util.format('/registrations/%s/%s', name, version);
}
var uri = util.format('http://%s:%s%s', self.HOST, self.APIPORT, path);
self.log('debug', 'Thalassa:Client.getRegistrations uri: ' + uri);
request({
uri: uri,
json: true
},
function (error, response, body) {
if (error) self.log('error', 'Thalassa:Client.getRegistrations', error);
if (response && response.statusCode !== 200 && response.statusCode !== 404) {
self.log('error', 'Thalassa:Client.getRegistrations unexpected response ' + response.statusCode);
error = new Error("getRegistrations unexpected response " + response.statusCode);
}
if (error) return cb(error);
var regs = (response.statusCode !== 200) ? [] : body;
cb(null, regs);
});
};
Client.prototype._startUpdateInterval = function() {
var self = this;
update();
self._updateInterval = setInterval(update, self.UPDATE_FREQ);
function update () {
self.intents.forEach(self._sendUpdate.bind(self));
}
};
Client.prototype._sendUpdate = function (intent) {
if (this.MODE === 'http') {
this._sendHTTPUpdate(intent);
}
else {
this.log('error', 'Thalassa:Client._sendUpdate: unsupported mode ' + this.mode);
}
};
Client.prototype._sendHTTPUpdate = function (intent) {
// TODO batch multiple requests?
var self = this;
var uri = util.format('http://%s:%s/registrations/%s/%s/%s/%s', self.HOST, self.APIPORT, intent.name, intent.version, intent.host, intent.port);
var startTime = Date.now();
//
// If the last call is still pending, don't add fuel to the fire
//
if (self.pending[intent.id]) {
self.log('error', 'Thalassa:Client._sendHTTPUpdate last call still pending! (skipping): ' + intent.id);
return ;
}
self.pending[intent.id] = true;
request({
uri: uri,
method: 'POST',
json: intent.meta,
timeout: self.TIMEOUT
},
function (error, response, body) {
self.pending[intent.id] = false;
// Check for an unsuccessful response, in case, for example we call the wrong
// host and it returns a 404, etc.
if (!error && response.statusCode !== 200) {
error = new Error(util.format('unexpected response statusCode %s from %s',
response.statusCode, uri));
}
if (error) {
self.log('error', 'Thalassa:Client._sendUpdate', error);
self.emit('updateFailed', error);
}
else {
self.emit('updateSuccessful');
self.log('debug', util.format('Thalassa:Client._sendUpdate (%s) [%s] %s', response.statusCode, Date.now() - startTime, uri));
}
});
};