rate-limiter-flexible
Advanced tools
Comparing version 0.3.0 to 0.4.0
const RateLimiterInterface = require('./RateLimiterInterface'); | ||
const RateLimiterRes = require('./RateLimiterRes'); | ||
const RateLimiterMemory = require('./RateLimiterMemory'); | ||
const BlockedKeys = require('./component/BlockedKeys'); | ||
@@ -34,2 +35,8 @@ const handleRedisError = function(funcName, resolve, reject, rlKey, pointsToConsume) { | ||
if (consumed > this.points) { | ||
// Block key for this.blockDuration seconds | ||
if (this.blockOnPointsConsumed > 0 && consumed >= this.blockOnPointsConsumed) { | ||
this._blockedKeys.add(rlKey, this.blockDuration); | ||
res.msBeforeNext = this.msBlockDuration; | ||
} | ||
reject(res); | ||
@@ -61,3 +68,6 @@ } else { | ||
this.redis = opts.redis; | ||
this.blockOnPointsConsumed = opts.blockOnPointsConsumed; | ||
this.blockDuration = opts.blockDuration; | ||
this.inMemoryLimiter = opts.inMemoryLimiter; | ||
this._blockedKeys = new BlockedKeys(); | ||
} | ||
@@ -76,2 +86,28 @@ | ||
get blockOnPointsConsumed() { | ||
return this._blockOnPointsConsumed; | ||
} | ||
set blockOnPointsConsumed(value) { | ||
this._blockOnPointsConsumed = value ? parseInt(value) : 0; | ||
if (this.blockOnPointsConsumed > 0 && this.points >= this.blockOnPointsConsumed) { | ||
throw new Error('blockOnPointsConsumed option must be more than points option'); | ||
} | ||
} | ||
get blockDuration() { | ||
return this._blockDuration; | ||
} | ||
get msBlockDuration() { | ||
return this._blockDuration * 1000; | ||
} | ||
set blockDuration(value) { | ||
this._blockDuration = value ? parseInt(value) : 0; | ||
if (this.blockDuration > 0 && this.blockOnPointsConsumed === 0) { | ||
throw new Error('blockOnPointsConsumed option must be set up'); | ||
} | ||
} | ||
get inMemoryLimiter() { | ||
@@ -97,2 +133,10 @@ return this._rateLimiterMemory; | ||
const rlKey = RateLimiterInterface.getKey(key); | ||
if (this.blockOnPointsConsumed > 0) { | ||
const msBeforeBlockExpires = this._blockedKeys.msBeforeExpire(rlKey); | ||
if (msBeforeBlockExpires > 0) { | ||
return reject(new RateLimiterRes(0, msBeforeBlockExpires)); | ||
} | ||
} | ||
this.redis.multi() | ||
@@ -137,2 +181,6 @@ .set(rlKey, 0, 'EX', this.duration, 'NX') | ||
} | ||
reset(key) { | ||
} | ||
} | ||
@@ -139,0 +187,0 @@ |
@@ -7,6 +7,6 @@ const expect = require('chai').expect; | ||
this.timeout(5000); | ||
let redisMockClient; | ||
const redisMockClient = redisMock.createClient(); | ||
beforeEach(() => { | ||
redisMockClient = redisMock.createClient(); | ||
beforeEach((done) => { | ||
redisMockClient.flushall(done); | ||
}); | ||
@@ -117,2 +117,82 @@ | ||
}); | ||
it('block key when block options set up', (done) => { | ||
const testKey = 'block'; | ||
const rateLimiter = new RateLimiterRedis({ | ||
redis: redisMockClient, | ||
points: 1, | ||
duration: 5, | ||
blockOnPointsConsumed: 2, | ||
blockDuration: 10 | ||
}); | ||
rateLimiter.consume(testKey) | ||
.then(() => { | ||
rateLimiter.consume(testKey) | ||
.then(() => { | ||
}) | ||
.catch((rejRes) => { | ||
// msBeforeNext more than 5000, so key was blocked | ||
expect(rejRes.msBeforeNext > 5000 && rejRes.remainingPoints === 0).to.equal(true); | ||
done(); | ||
}); | ||
}) | ||
.catch((rejRes) => { | ||
done(rejRes); | ||
}); | ||
}); | ||
it('expire blocked key', (done) => { | ||
const testKey = 'block'; | ||
const rateLimiter = new RateLimiterRedis({ | ||
redis: redisMockClient, | ||
points: 1, | ||
duration: 1, | ||
blockOnPointsConsumed: 2, | ||
blockDuration: 2, | ||
}); | ||
// It blocks on the first consume as consumed points more than available | ||
rateLimiter.consume(testKey, 2) | ||
.then(() => { | ||
}) | ||
.catch((rejRes) => { | ||
setTimeout(() => { | ||
rateLimiter.consume(testKey) | ||
.then((res) => { | ||
// Block expired | ||
expect(res.msBeforeNext <= 1000 && res.remainingPoints === 0).to.equal(true); | ||
done(); | ||
}) | ||
.catch((rejRes) => { | ||
done(rejRes); | ||
}); | ||
}, 2001); | ||
}); | ||
}); | ||
it('throws error when blockOnPointsConsumed is not set, but blockDuration is set', (done) => { | ||
try { | ||
const rateLimiter = new RateLimiterRedis({ | ||
redis: redisMockClient, | ||
blockDuration: 2, | ||
}); | ||
} catch (err) { | ||
expect(err instanceof Error).to.equal(true); | ||
done(); | ||
} | ||
}); | ||
it('throws error when blockOnPointsConsumed less or equal to points', (done) => { | ||
try { | ||
const rateLimiter = new RateLimiterRedis({ | ||
redis: redisMockClient, | ||
points: 2, | ||
blockOnPointsConsumed: 2, | ||
}); | ||
} catch (err) { | ||
expect(err instanceof Error).to.equal(true); | ||
done(); | ||
} | ||
}); | ||
}); |
{ | ||
"name": "rate-limiter-flexible", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "Flexible API rate limiter backed by Redis for distributed node.js applications", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
[![Build Status](https://travis-ci.org/animir/node-rate-limiter-flexible.png)](https://travis-ci.org/animir/node-rate-limiter-flexible) | ||
[![Coverage Status](https://coveralls.io/repos/animir/node-rate-limiter-flexible/badge.svg?branch=master)](https://coveralls.io/r/animir/node-rate-limiter-flexible?branch=master) | ||
[![node version][node-image]][node-url] | ||
[node-image]: https://img.shields.io/badge/node.js-%3E=_6.0-green.svg?style=flat-square | ||
[node-url]: http://nodejs.org/download/ | ||
## node-rate-limiter-flexible | ||
Flexible rate limiter with Redis as broker allows to control requests rate in cluster or distributed environment. | ||
Flexible rate limiter and DDoS protector with Redis as broker allows to control requests rate in cluster or distributed environment. | ||
It uses fixed window to limit requests. | ||
Advantages: | ||
Advantages: | ||
* block strategy against really powerful DDoS attacks (like 100k requests per sec) [Read about it and benchmarking here](https://github.com/animir/node-rate-limiter-flexible/blob/master/BLOCK_STRATEGY.md) | ||
* backed on native Promises | ||
@@ -52,2 +57,3 @@ * actions can be done evenly over duration window to cut off picks | ||
Note: Performance will be much better on real servers, as for this benchmark everything was launched on one machine | ||
@@ -80,2 +86,4 @@ ## Installation | ||
execEvenly: false, | ||
blockOnPointsConsumed: 10, // If 10 points consumed in current duration | ||
blockDuration: 30, // block for 30 seconds in current process memory | ||
inMemoryLimiter: new RateLimiterMemory( // It will be used only on Redis error as insurance | ||
@@ -135,3 +143,5 @@ { | ||
* `points` `Default: 4` Maximum number of points can be consumed over duration | ||
* `duration` `Default: 1` Number of seconds before points are reset | ||
* `duration` `Default: 1` Number of seconds before points are reset | ||
* `execEvenly` `Default: false` Delay action to be executed evenly over duration | ||
@@ -142,6 +152,16 @@ First action in duration is executed without delay. | ||
Note: it isn't recommended to use it for long duration, as it may delay action for too long | ||
* `inMemoryLimiter` `Default: undefined` RateLimiterMemory object to store limits in process memory, when Redis comes up with any error. | ||
* `blockOnPointsConsumed` `Default: 0` Against DDoS attacks. Blocked key isn't checked by requesting Redis. | ||
Blocking works in **current process memory**. | ||
Redis is quite fast, however, it may be significantly slowed down on dozens of thousands requests. | ||
* `blockDuration` `Default: 0` Block key for `blockDuration` seconds, | ||
if `blockOnPointsConsumed` or more points are consumed | ||
* `inMemoryLimiter` `Default: undefined` RateLimiterMemory object to store limits in process memory, | ||
when Redis comes up with any error. | ||
Be careful when use it in cluster or in distributed app. | ||
It may result to floating number of allowed actions. | ||
If an action with a same `key` is launched on one worker several times in sequence, limiter will reach out of points soon. | ||
If an action with a same `key` is launched on one worker several times in sequence, | ||
limiter will reach out of points soon. | ||
Omit it if you want strictly use Redis and deal with errors from it | ||
@@ -169,2 +189,3 @@ | ||
* rejected when there is no points to be consumed, where reject reason `rejRes` is `RateLimiterRes` object | ||
* rejected when key is blocked (if block strategy is set up), where reject reason `rejRes` is `RateLimiterRes` object | ||
@@ -171,0 +192,0 @@ Arguments: |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
80766
26
821
206
0