@heroku/no-kafka
Version:
Apache Kafka 0.9 client for Node.JS
198 lines (161 loc) • 5.33 kB
JavaScript
'use strict';
var net = require('net');
var Promise = require('./bluebird-configured');
var NoKafkaConnectionError = require('./errors').NoKafkaConnectionError;
var tls = require('tls');
function Connection(options) {
// options
this.host = options.host || '127.0.0.1';
this.port = options.port || 9092;
this.ssl = options.ssl || false;
// internal state
this.connected = false;
this.buffer = new Buffer(256 * 1024);
this.offset = 0;
this.queue = [];
}
module.exports = Connection;
Connection.prototype.equal = function (host, port) {
return this.host === host && this.port === port;
};
Connection.prototype.server = function () {
return this.host + ':' + this.port;
};
Connection.prototype.connect = function (timeout) {
var self = this;
if (self.connected) {
return Promise.resolve();
}
if (self.connecting) {
return self.connecting;
}
self.connecting = Promise.race([
new Promise(function (resolve, reject) {
setTimeout(function () {
reject(new NoKafkaConnectionError(self.server(), 'Connection timeout'));
}, timeout || 3000);
}),
new Promise(function (resolve, reject) {
if (self.socket) {
self.socket.destroy();
}
// Docs on TLS Sockets are inaccurate
// https://github.com/nodejs/node/issues/3963
if (self.ssl) {
self.socket = tls.connect({
port: self.port,
host: self.host,
key: self.ssl.clientCertKey,
cert: self.ssl.clientCert,
rejectUnauthorized: self.ssl.rejectUnauthorized || false,
isServer: false
});
} else {
self.socket = net.connect({
port: self.port,
host: self.host
});
}
self.socket.once('connect', function () {
self.connected = true;
resolve();
});
self.socket.on('end', function () {
self._disconnect(new NoKafkaConnectionError(self.server(), 'Kafka server has closed connection'));
});
self.socket.on('error', function (err) {
var _err = new NoKafkaConnectionError(self.server(), err.toString());
reject(_err);
self._disconnect(_err);
});
self.socket.on('data', self._receive.bind(self));
})
])
.finally(function () {
self.connecting = false;
});
return self.connecting;
};
// Private disconnect method, this is what the 'end' and 'error'
// events call directly to make sure internal state is maintained
Connection.prototype._disconnect = function (err) {
if (!this.connected) {
return;
}
this.socket.end();
this.connected = false;
this.queue.forEach(function (t) {
t.reject(err);
});
this.queue = [];
};
Connection.prototype._growBuffer = function (newLength) {
var _b = new Buffer(newLength);
this.buffer.copy(_b, 0, 0, this.offset);
this.buffer = _b;
};
Connection.prototype.close = function () {
var err = new NoKafkaConnectionError(this, 'Connection closed');
err._kafka_connection_closed = true;
this._disconnect(err);
};
/**
* Send a request to Kafka
*
* @param {Buffer} data request message
* @param {Boolean} noresponse if the server wont send any response to this request
* @return {Promise} Promise resolved with a Kafka response message
*/
Connection.prototype.send = function (data, noresponse) {
var self = this, buffer = new Buffer(4 + data.length);
buffer.writeInt32BE(data.length, 0);
data.copy(buffer, 4);
function _send() {
return new Promise(function (resolve, reject) {
self.queue.push({
resolve: resolve,
reject: reject
});
self.socket.write(buffer);
if (noresponse === true) {
self.queue.shift().resolve();
}
});
}
if (!self.connected) {
return self.connect().then(function () {
return _send();
});
}
return _send();
};
Connection.prototype._receive = function (data) {
var length;
if (!this.connected) {
return;
}
if (this.offset) {
if (this.buffer.length < data.length + this.offset) {
this._growBuffer(data.length + this.offset);
}
data.copy(this.buffer, this.offset);
this.offset += data.length;
data = this.buffer.slice(0, this.offset);
}
length = data.length < 4 ? 0 : data.readInt32BE(0);
if (data.length < 4 + length) {
if (this.offset === 0) {
if (this.buffer.length < 4 + length) {
this._growBuffer(4 + length);
}
data.copy(this.buffer);
this.offset += data.length;
}
return;
}
this.offset = 0;
this.queue.shift().resolve(data.slice(4, length + 4));
if (data.length > 4 + length) {
this._receive(data.slice(length + 4));
}
};