limitation
Version:
An efficient rate limiter with several backend, including a Kademlia DHT.
136 lines (122 loc) • 4.2 kB
JavaScript
;
var P = require('bluebird');
var kad = P.promisifyAll(require('wikimedia-kad-fork'));
var DecayingCounterStore = require('./decaying_counter_store');
function KadBackend(options) {
if (!options.listen) {
options.listen = { address: 'localhost', port: 3050 };
}
if (!options.listen.address) { options.listen.address = 'localhost'; }
if (!options.listen.port) { options.listen.port = 3050; }
if (options.minValue === undefined) {
options.minValue = 0.1;
}
this._options = options;
this._onMasterPort = false;
this._running = false;
this._dht = null;
this._storage = null;
}
KadBackend.prototype.setup = function() {
var self = this;
if (!self._onMasterPort) {
var masterPort = self._options.listen.port;
return self._setupTransport(self._options.listen)
.then(function(transport) {
self._onMasterPort = transport._contact.port === masterPort;
// Schedule a re-connect
if (!self._onMasterPort) {
setTimeout(self.setup.bind(self), self._getRandomizedInterval(60));
if (self._dht) {
// Already connected, but can't switch to master port. Do
// not replace the current transport instance.
transport.close();
return;
}
}
// TODO: Support forwarding logs via options.log
var logger = new kad.Logger(0, 'kad-example' + Math.random());
self._storage = new DecayingCounterStore(self._options);
self._dht = kad.Node({
transport: transport,
logger: logger,
storage: self._storage,
});
self._options.seeds.forEach(function(seed) {
if (typeof seed === 'string') {
seed = {
address: seed,
port: 3050,
};
}
if (transport._contact.port !== seed.port
|| transport._contact.address !== seed.address) {
self._dht.connect(seed);
}
});
return self;
})
.catch(function(err) {
console.log('Error during DHT setup', err);
});
}
};
KadBackend.prototype.stop = function() {
if (this._storage) { this._storage.stop(); }
};
KadBackend.prototype._setupTransport = function(listen, retries) {
var self = this;
if (retries === 0) {
return P.reject();
}
return new P(function(resolve) {
if (retries) {
// Retry on a random port
listen = {
address: listen.address,
port: 1024 + Math.floor(Math.random() * 63000),
};
}
var transport = new kad.transports.UDP(
kad.contacts.AddressPortContact(listen));
transport.once('error', function() {
if (retries === undefined) {
retries = 5;
} else {
retries--;
}
resolve(self._setupTransport(listen, retries));
});
transport.once('ready', function() {
resolve(transport);
});
})
.then(function(transport) {
// Set up a permanent error handler, to avoid low-level errors being
// converted into (uncaught) async exceptions.
transport.on('error', function(e) {
// console.log(e);
});
return transport;
});
};
/**
* Randomize the configured interval slightly
*/
KadBackend.prototype._getRandomizedInterval = function(multiplier) {
var interval = this._options.interval * (multiplier || 1);
return interval + (Math.random() - 0.5) * interval * 0.1;
};
/**
* Update counters
* @param {string} key
* @param {number} value
* @return {Promise<value>} Current counter value
*/
KadBackend.prototype.put = function(key, value) {
return this._dht.putAsync(key, value);
};
KadBackend.prototype.get = function(key, value) {
return this._dht.getAsync(key);
};
module.exports = KadBackend;