diff --git a/README.md b/README.md index 5dd1d98..847036e 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,10 @@ Some copy/paste examples on Wiki: * [RLWrapperBlackAndWhite](https://github.com/animir/node-rate-limiter-flexible/wiki/Black-and-White-lists) Black and White lists * [RateLimiterQueue](https://github.com/animir/node-rate-limiter-flexible/wiki/RateLimiterQueue) Rate limiter with FIFO queue +### Changelog + +See [releases](https://github.com/animir/node-rate-limiter-flexible/releases) for detailed changelog. + ## Basic Options * **points** @@ -159,7 +163,7 @@ Some copy/paste examples on Wiki: * [tableName](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#tablename) Table/collection. * [clearExpiredByTimeout](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#clearexpiredbytimeout) For MySQL and PostgreSQL. -Cut off load picks: +Smooth out traffic picks: * [execEvenly](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#execevenly) * [execEvenlyMinDelayMs](https://github.com/animir/node-rate-limiter-flexible/wiki/Options#execevenlymindelayms) @@ -218,3 +222,5 @@ It has to implement at least 4 methods: * `_delete` deletes all key related data and returns `true` on deleted, `false` if key is not found. All other methods depends on store. See `RateLimiterRedis` or `RateLimiterPostgres` for example. + +Note: all changes should be covered by tests. diff --git a/lib/RateLimiterQueue.js b/lib/RateLimiterQueue.js index bc5786b..c70b27f 100644 --- a/lib/RateLimiterQueue.js +++ b/lib/RateLimiterQueue.js @@ -1,12 +1,46 @@ const RateLimiterQueueError = require('./component/RateLimiterQueueError') const MAX_QUEUE_SIZE = 4294967295; +const KEY_DEFAULT = 'limiter'; module.exports = class RateLimiterQueue { + constructor(limiterFlexible, opts = { + maxQueueSize: MAX_QUEUE_SIZE, + }) { + this._queueLimiters = { + KEY_DEFAULT: new RateLimiterQueueInternal(limiterFlexible, opts) + }; + this._limiterFlexible = limiterFlexible; + this._maxQueueSize = opts.maxQueueSize + } + + getTokensRemaining(key = KEY_DEFAULT) { + if (this._queueLimiters[key]) { + return this._queueLimiters[key].getTokensRemaining() + } else { + return Promise.resolve(this._limiterFlexible.points) + } + } + + removeTokens(tokens, key = KEY_DEFAULT) { + if (!this._queueLimiters[key]) { + this._queueLimiters[key] = new RateLimiterQueueInternal( + this._limiterFlexible, { + key, + maxQueueSize: this._maxQueueSize, + }) + } + + return this._queueLimiters[key].removeTokens(tokens) + } +}; + +class RateLimiterQueueInternal { constructor(limiterFlexible, opts = { maxQueueSize: MAX_QUEUE_SIZE, + key: KEY_DEFAULT, }) { - this._key = 'limiter'; + this._key = opts.key; this._waitTimeout = null; this._queue = []; this._limiterFlexible = limiterFlexible; @@ -90,4 +124,4 @@ module.exports = class RateLimiterQueue { } }); } -}; +} diff --git a/package.json b/package.json index 9617498..2523363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rate-limiter-flexible", - "version": "2.1.3", + "version": "2.1.4", "description": "Node.js rate limiter by key and protection from DDoS and Brute-Force attacks in process Memory, Redis, MongoDb, Memcached, MySQL, PostgreSQL, Cluster or PM", "main": "index.js", "scripts": { diff --git a/test/RateLimiterQueue.test.js b/test/RateLimiterQueue.test.js index 91813a4..c9830ad 100644 --- a/test/RateLimiterQueue.test.js +++ b/test/RateLimiterQueue.test.js @@ -102,4 +102,28 @@ describe('RateLimiterQueue with FIFO queue', function RateLimiterQueueTest() { }); }); }); + + it('getTokensRemaining returns maximum if internal limiter by key does not exist', (done) => { + const rlMemory = new RateLimiterMemory({ points: 23, duration: 1 }); + const rlQueue = new RateLimiterQueue(rlMemory); + rlQueue.getTokensRemaining('test') + .then((tokensRemaining) => { + expect(tokensRemaining).to.equal(23); + done(); + }); + }); + + it('creates internal instance by key and removes tokens from it', (done) => { + const rlMemory = new RateLimiterMemory({ points: 2, duration: 1 }); + const rlQueue = new RateLimiterQueue(rlMemory); + rlQueue.removeTokens(1, 'customkey') + .then((remainingTokens) => { + expect(remainingTokens).to.equal(1); + rlQueue.getTokensRemaining() + .then((defaultTokensRemaining) => { + expect(defaultTokensRemaining).to.equal(2); + done(); + }); + }); + }); });