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.1.0 to 0.2.0

.idea/inspectionProfiles/Project_Default.xml

70

lib/RateLimiter.js
const RateLimiterRes = require('./RateLimiterRes');
const afterConsume = function(resolve, reject, rlKey, results) {
const [resSet, consumed, resTtlMs] = results;
const res = new RateLimiterRes();
let isFirstInDuration = resSet === 'OK';
res.remainingPoints = Math.max(this.points - consumed, 0);
if (resTtlMs === -1) { // If rlKey created by incrby() not by set()
isFirstInDuration = true;
res.msBeforeNext = this.duration;
this.redis.expire(rlKey, this.duration);
} else {
res.msBeforeNext = resTtlMs;
}
if (consumed > this.points) {
reject(res);
} else {
if (this.execEvenly && res.msBeforeNext > 0 && !isFirstInDuration) {
const delay = Math.ceil(res.msBeforeNext / (res.remainingPoints + 2));
setTimeout(resolve, delay, res);
} else {
resolve(res);
}
}
};
class RateLimiter {

@@ -10,2 +36,3 @@ /**

* duration: 1, // Per seconds
* execEvenly: false, // Execute allowed actions evenly over duration
* }

@@ -17,2 +44,3 @@ */

this.duration = opts.duration || 1;
this.execEvenly = typeof opts.execEvenly === 'undefined' ? false : Boolean(opts.execEvenly);
}

@@ -27,6 +55,6 @@

* @param key
* @param points
* @param pointsToConsume
* @returns {Promise<any>}
*/
consume(key, points = 1) {
consume(key, pointsToConsume = 1) {
return new Promise((resolve, reject) => {

@@ -36,25 +64,9 @@ const rlKey = RateLimiter.getKey(key);

.set(rlKey, 0, 'EX', this.duration, 'NX')
.incrby(rlKey, points)
.incrby(rlKey, pointsToConsume)
.pttl(rlKey)
.exec((err, results) => {
const res = new RateLimiterRes();
if (err) {
reject(new Error('Redis Client error'));
} else {
const [, consumed, resTtlMs] = results;
res.points = Math.max(this.points - consumed, 0);
if (resTtlMs === -1) {
res.msBeforeNext = this.duration;
this.redis.expire(rlKey, this.duration);
} else {
res.msBeforeNext = resTtlMs;
}
if (consumed > this.points) {
reject(res);
} else {
resolve(res);
}
afterConsume.call(this, resolve, reject, rlKey, results);
}

@@ -67,3 +79,10 @@ });

const rlKey = RateLimiter.getKey(key);
this.redis.incrby(rlKey, points);
return new Promise((resolve, reject) => {
this.redis.incrby(rlKey, points, (err, value) => {
if (err) {
reject(err);
}
resolve(value);
});
});
}

@@ -73,3 +92,10 @@

const rlKey = RateLimiter.getKey(key);
this.redis.incrby(rlKey, -points);
return new Promise((resolve, reject) => {
this.redis.incrby(rlKey, -points, (err, value) => {
if (err) {
reject(err);
}
resolve(value);
});
});
}

@@ -76,0 +102,0 @@ }

@@ -7,6 +7,8 @@ const expect = require('chai').expect;

describe('RateLimiter with fixed window', () => {
it('consume 1 point', () => {
describe('RateLimiter with fixed window', function() {
this.timeout(5000);
it('consume 1 point', (done) => {
const testKey = 'consume1';
const rateLimiter = new RateLimiter(redisMockClient, {points: 2, duration: 10});
const rateLimiter = new RateLimiter(redisMockClient, {points: 2, duration: 5});
rateLimiter.consume(testKey)

@@ -17,44 +19,100 @@ .then(() => {

expect(consumedPoints).to.equal('1');
done();
}
})
})
.catch((err) => {
done(err);
});
});
it('can not consume more than maximum points', () => {
it('can not consume more than maximum points', (done) => {
const testKey = 'consume2';
const rateLimiter = new RateLimiter(redisMockClient, {points: 1, duration: 10});
const rateLimiter = new RateLimiter(redisMockClient, {points: 1, duration: 5});
rateLimiter.consume(testKey, 2)
.then(() => {})
.catch((rejRes) => {
expect(rejRes.msBeforeNext > 0).to.equal(true);
expect(rejRes.msBeforeNext >= 0).to.equal(true);
done();
})
.catch((err) => {
done(err);
});
});
it('makes penalty', () => {
// !!! 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 RateLimiter(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('makes penalty', (done) => {
const testKey = 'penalty1';
const rateLimiter = new RateLimiter(redisMockClient, {points: 3, duration: 10});
const rateLimiter = new RateLimiter(redisMockClient, {points: 3, duration: 5});
rateLimiter.consume(testKey)
.then(() => {
rateLimiter.penalty(testKey);
redisMockClient.get(RateLimiter.getKey(testKey), (err, consumedPoints) => {
if (!err) {
expect(consumedPoints).to.equal('2');
}
})
rateLimiter.penalty(testKey)
.then(() => {
redisMockClient.get(RateLimiter.getKey(testKey), (err, consumedPoints) => {
if (!err) {
expect(consumedPoints).to.equal('2');
done();
}
})
})
.catch((err) => {
done(err);
});
})
.catch((err) => {
done(err);
});
});
it('reward points', () => {
it('reward points', (done) => {
const testKey = 'penalty2';
const rateLimiter = new RateLimiter(redisMockClient, {points: 1, duration: 10});
const rateLimiter = new RateLimiter(redisMockClient, {points: 1, duration: 5});
rateLimiter.consume(testKey)
.then(() => {
rateLimiter.reward(testKey);
redisMockClient.get(RateLimiter.getKey(testKey), (err, consumedPoints) => {
if (!err) {
expect(consumedPoints).to.equal('0');
}
})
rateLimiter.reward(testKey)
.then(() => {
redisMockClient.get(RateLimiter.getKey(testKey), (err, consumedPoints) => {
if (!err) {
expect(consumedPoints).to.equal('0');
done();
}
})
})
.catch((err) => {
done(err);
});
})
.catch((err) => {
done(err);
});
});
});
module.exports = class RateLimiterRes {
constructor() {
this._msBeforeNext = 0;
this._points = 0;
this._msBeforeNext = 0; // Milliseconds before next action
this._remainingPoints = 0; // Remaining points in current duration
}

@@ -16,10 +16,10 @@

get points() {
return this._points;
get remainingPoints() {
return this._remainingPoints;
}
set points(p) {
this._points = p;
set remainingPoints(p) {
this._remainingPoints = p;
return this;
}
};

@@ -11,3 +11,3 @@ const expect = require('chai').expect;

it('setup defaults on construct', () => {
expect(rateLimiterRes.msBeforeNext === 0 && rateLimiterRes.points === 0).to.be.true;
expect(rateLimiterRes.msBeforeNext === 0 && rateLimiterRes.remainingPoints === 0).to.be.true;
});

@@ -21,5 +21,5 @@

it('points set and get', () => {
rateLimiterRes.points = 4;
expect(rateLimiterRes.points).to.equal(4);
rateLimiterRes.remainingPoints = 4;
expect(rateLimiterRes.remainingPoints).to.equal(4);
});
});
{
"name": "rate-limiter-flexible",
"version": "0.1.0",
"version": "0.2.0",
"description": "Flexible API rate limiter backed by Redis for distributed node.js applications",

@@ -8,2 +8,3 @@ "main": "index.js",

"test": "./node_modules/istanbul/lib/cli.js cover ./node_modules/.bin/_mocha lib/**/**.test.js",
"debug-test": "mocha --inspect-brk lib/**/**.test.js",
"coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls"

@@ -10,0 +11,0 @@ },

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

## Options
* `points` Maximum number of points can be consumed over duration
* `duration` Number of seconds before points are reset
* `execEvenly` Delay action to be executed evenly over duration
First action in duration is executed without delay.
All next allowed actions in current duration are delayed by formula `msBeforeDurationEnd / (remainingPoints + 2)`
It allows to cut off load peaks.
Note: it isn't recommended to use it for long duration, as it may delay action for too long
## API

@@ -68,3 +78,3 @@

msBeforeNext: 250, // Number of milliseconds before next action can be done
points: 0 // Number of left points in current duration
remainingPoints: 0 // Number of remaining points in current duration
}

@@ -88,2 +98,4 @@ ````

Note: Depending on time penalty may go to next durations
Doesn't return anything

@@ -95,2 +107,4 @@

Note: Depending on time reward may go to next durations
Doesn't return anything
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