Socket
Socket
Sign inDemoInstall

rate-limiter-flexible

Package Overview
Dependencies
0
Maintainers
1
Versions
163
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.4.1 to 0.5.0

lib/RateLimiterAbstract.js

2

lib/RateLimiterMemory.js

@@ -1,2 +0,2 @@

const RateLimiterInterface = require('./RateLimiterInterface');
const RateLimiterInterface = require('./RateLimiterAbstract');
const MemoryStorage = require('./component/MemoryStorage/MemoryStorage');

@@ -3,0 +3,0 @@ const RateLimiterRes = require('./RateLimiterRes');

@@ -1,11 +0,10 @@

const RateLimiterInterface = require('./RateLimiterInterface');
const RateLimiterAbstract = require('./RateLimiterAbstract');
const RateLimiterRes = require('./RateLimiterRes');
const RateLimiterMemory = require('./RateLimiterMemory');
const BlockedKeys = require('./component/BlockedKeys');
const handleRedisError = function(funcName, resolve, reject, rlKey, pointsToConsume) {
if (!(this.inMemoryLimiter instanceof RateLimiterMemory)) {
const handleRedisError = function(funcName, resolve, reject, key, pointsToConsume) {
if (!(this.insuranceLimiter instanceof RateLimiterAbstract)) {
reject(new Error('Redis Client error'));
} else {
this.inMemoryLimiter.consume(rlKey, pointsToConsume)
this.insuranceLimiter[funcName](key, pointsToConsume)
.then((res) => {

@@ -52,3 +51,3 @@ resolve(res);

class RateLimiterRedis extends RateLimiterInterface {
class RateLimiterRedis extends RateLimiterAbstract {
/**

@@ -58,3 +57,3 @@ *

* Defaults {
* ... see other in RateLimiterInterface
* ... see other in RateLimiterAbstract
*

@@ -71,3 +70,3 @@ * redis: RedisClient

this.blockDuration = opts.blockDuration;
this.inMemoryLimiter = opts.inMemoryLimiter;
this.insuranceLimiter = opts.insuranceLimiter;
this._blockedKeys = new BlockedKeys();

@@ -113,11 +112,11 @@ }

get inMemoryLimiter() {
return this._rateLimiterMemory;
get insuranceLimiter() {
return this._insuranceLimiter;
}
set inMemoryLimiter(value) {
if (typeof value !== 'undefined' && !(value instanceof RateLimiterMemory)) {
throw new Error('inMemoryLimiter must be instance of RateLimiterMemory');
set insuranceLimiter(value) {
if (typeof value !== 'undefined' && !(value instanceof RateLimiterAbstract)) {
throw new Error('insuranceLimiter must be instance of RateLimiterAbstract');
}
this._rateLimiterMemory = value;
this._insuranceLimiter = value;
}

@@ -133,3 +132,3 @@

return new Promise((resolve, reject) => {
const rlKey = RateLimiterInterface.getKey(key);
const rlKey = RateLimiterAbstract.getKey(key);

@@ -149,3 +148,3 @@ if (this.blockOnPointsConsumed > 0) {

if (err) {
handleRedisError.call(this, resolve, reject, rlKey, pointsToConsume);
handleRedisError.call(this, 'consume', resolve, reject, key, pointsToConsume);
} else {

@@ -159,7 +158,7 @@ afterConsume.call(this, resolve, reject, rlKey, results);

penalty(key, points = 1) {
const rlKey = RateLimiterInterface.getKey(key);
const rlKey = RateLimiterAbstract.getKey(key);
return new Promise((resolve, reject) => {
this.redis.incrby(rlKey, points, (err, value) => {
if (err) {
handleRedisError.call(this, resolve, reject, rlKey, points);
handleRedisError.call(this, 'penalty', resolve, reject, key, points);
} else {

@@ -173,7 +172,7 @@ resolve(value);

reward(key, points = 1) {
const rlKey = RateLimiterInterface.getKey(key);
const rlKey = RateLimiterAbstract.getKey(key);
return new Promise((resolve, reject) => {
this.redis.incrby(rlKey, -points, (err, value) => {
if (err) {
handleRedisError.call(this, resolve, reject, rlKey, points);
handleRedisError.call(this, 'reward', resolve, reject, key, points);
} else {

@@ -185,6 +184,2 @@ resolve(value);

}
reset(key) {
}
}

@@ -191,0 +186,0 @@

@@ -9,2 +9,29 @@ const expect = require('chai').expect;

// emulate closed RedisClient
class RedisClient {
multi() {
const multi = redisMockClient.multi();
multi.exec = (cb) => {
cb(new Error('closed'), [])
}
return multi;
}
};
const redisClientClosedRaw = new RedisClient();
const redisClientClosed = new Proxy(redisClientClosedRaw, {
get: (func, name) => {
if( name in redisClientClosedRaw ) {
return redisClientClosedRaw[name];
}
return function() {
const args = [].slice.call(arguments);
const cb = args.pop();
cb(Error('closed'));
}
}
});
beforeEach((done) => {

@@ -42,31 +69,28 @@ redisMockClient.flushall(done);

// !!! Uncomment when redis-mock bug fixed
// https://github.com/yeahoffline/redis-mock/pull/67/commits/d1936e5260da8bde252d55e93f01b8f6008de322
//
// it('consume evenly over duration', (done) => {
// const testKey = 'consumeEvenly';
// const rateLimiter = new RateLimiterRedis({redis: redisMockClient, points: 2, duration: 5, execEvenly: true});
// rateLimiter.consume(testKey)
// .then(() => {
// const timeFirstConsume = Date.now();
// rateLimiter.consume(testKey)
// .then(() => {
// /* Second consume should be delayed more than 2 seconds
// Explanation:
// 1) consume at 0ms, remaining duration = 4444ms
// 2) delayed consume for (4444 / (0 + 2)) ~= 2222ms, where 2 is a fixed value
// , because it mustn't delay in the beginning and in the end of duration
// 3) consume after 2222ms by timeout
// */
// expect(Date.now() - timeFirstConsume > 2000).to.equal(true);
// done();
// })
// .catch((err) => {
// done(err);
// });
// })
// .catch((err) => {
// done(err);
// });
// });
it('consume evenly over duration', (done) => {
const testKey = 'consumeEvenly';
const rateLimiter = new RateLimiterRedis({redis: redisMockClient, points: 2, duration: 5, execEvenly: true});
rateLimiter.consume(testKey)
.then(() => {
const timeFirstConsume = Date.now();
rateLimiter.consume(testKey)
.then(() => {
/* Second consume should be delayed more than 2 seconds
Explanation:
1) consume at 0ms, remaining duration = 4444ms
2) delayed consume for (4444 / (0 + 2)) ~= 2222ms, where 2 is a fixed value
, because it mustn't delay in the beginning and in the end of duration
3) consume after 2222ms by timeout
*/
expect(Date.now() - timeFirstConsume > 2000).to.equal(true);
done();
})
.catch((err) => {
done(err);
});
})
.catch((err) => {
done(err);
});
});

@@ -198,2 +222,100 @@ it('makes penalty', (done) => {

it('throws error on RedisClient error', (done) => {
const testKey = 'rediserror';
const redisClientClosed = new RedisClient();
const rateLimiter = new RateLimiterRedis({
redis: redisClientClosed,
});
rateLimiter.consume(testKey)
.then(() => {
})
.catch((rejRes) => {
expect(rejRes instanceof Error).to.equal(true);
done();
});
});
it('consume using insuranceLimiter when RedisClient error', (done) => {
const testKey = 'rediserror2';
const redisClientClosed = new RedisClient();
const rateLimiter = new RateLimiterRedis({
redis: redisClientClosed,
points: 1,
duration: 1,
insuranceLimiter: new RateLimiterRedis({
points: 2,
duration: 2,
redis: redisMockClient
})
});
// Consume from insurance limiter with different options
rateLimiter.consume(testKey)
.then((res) => {
expect(res.remainingPoints === 1 && res.msBeforeNext > 1000).to.equal(true);
done();
})
.catch((rejRes) => {done(rejRes)});
});
it('penalty using insuranceLimiter when RedisClient error', (done) => {
const testKey = 'rediserror3';
const rateLimiter = new RateLimiterRedis({
redis: redisClientClosed,
points: 1,
duration: 1,
insuranceLimiter: new RateLimiterRedis({
points: 2,
duration: 2,
redis: redisMockClient
})
});
rateLimiter.penalty(testKey)
.then((res) => {
redisMockClient.get(RateLimiterRedis.getKey(testKey), (err, consumedPoints) => {
if (!err) {
expect(consumedPoints).to.equal('1');
done();
}
})
})
.catch((rejRes) => {done(rejRes)});
});
it('reward using insuranceLimiter when RedisClient error', (done) => {
const testKey = 'rediserror4';
const rateLimiter = new RateLimiterRedis({
redis: redisClientClosed,
points: 1,
duration: 1,
insuranceLimiter: new RateLimiterRedis({
points: 2,
duration: 2,
redis: redisMockClient
})
});
rateLimiter.consume(testKey, 2)
.then((res) => {
rateLimiter.reward(testKey)
.then((res) => {
redisMockClient.get(RateLimiterRedis.getKey(testKey), (err, consumedPoints) => {
if (!err) {
expect(consumedPoints).to.equal('1');
done();
}
})
})
.catch((rejRes) => {done(rejRes)});
})
.catch((rejRes) => {done(rejRes)});
});
});
{
"name": "rate-limiter-flexible",
"version": "0.4.1",
"version": "0.5.0",
"description": "Flexible API rate limiter backed by Redis for distributed node.js applications",

@@ -36,4 +36,4 @@ "main": "index.js",

"mocha": "^5.1.1",
"redis-mock": "^0.21.0"
"redis-mock": "animir/redis-mock"
}
}

@@ -21,3 +21,3 @@ [![Build Status](https://travis-ci.org/animir/node-rate-limiter-flexible.png)](https://travis-ci.org/animir/node-rate-limiter-flexible)

* no prod dependencies
* Redis errors don't result to broken app if `inMemoryLimiter` set up
* Redis errors don't result to broken app if `insuranceLimiter` set up
* useful `penalty` and `reward` methods to change limits on some results of an action

@@ -88,3 +88,5 @@

blockDuration: 30, // block for 30 seconds in current process memory
inMemoryLimiter: new RateLimiterMemory( // It will be used only on Redis error as insurance
// It will be used only on Redis error as insurance
// Can be any implemented limiter like RateLimiterMemory or RateLimiterRedis extended from RateLimiterAbstract
insuranceLimiter: new RateLimiterMemory(
{

@@ -104,5 +106,7 @@ points: 1, // 1 is fair if you have 5 workers and 1 cluster

// Depending on results it allows to fine
rateLimiterRedis.penalty(remoteAddress, 3);
rateLimiterRedis.penalty(remoteAddress, 3)
.then((remainingPoints) => {});
// or rise number of points for current duration
rateLimiterRedis.reward(remoteAddress, 2);
.then((remainingPoints) => {});
})

@@ -112,3 +116,3 @@ .catch((rejRes) => {

// Some Redis error
// Never happen if `inMemoryLimiter` set up
// Never happen if `insuranceLimiter` set up
// Decide what to do with it in other case

@@ -132,3 +136,3 @@ } else {

{
points: 1, // 1 is fair if you have 5 workers and 1 cluster
points: 1, // 1 is fair if you have 5 workers and 1 cluster, all workers will limit it to 5 in sum
duration: 5,

@@ -162,5 +166,6 @@ execEvenly: false,

* `inMemoryLimiter` `Default: undefined` RateLimiterMemory object to store limits in process memory,
* `insuranceLimiter` `Default: undefined` Instance of RateLimiterAbstract extended object to store limits,
when Redis comes up with any error.
Be careful when use it in cluster or in distributed app.
Additional RateLimiterRedis or RateLimiterMemory can be used as insurance.
Be careful when use RateLimiterMemory in cluster or in distributed app.
It may result to floating number of allowed actions.

@@ -203,3 +208,3 @@ If an action with a same `key` is launched on one worker several times in sequence,

Returns Promise
Returns Promise, where result is consumed points in current duration

@@ -212,2 +217,2 @@ ### rateLimiter.reward(key, points = 1)

Returns Promise
Returns Promise, where result is consumed points in current duration

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc