Socket
Socket
Sign inDemoInstall

rate-limiter-flexible

Package Overview
Dependencies
Maintainers
1
Versions
163
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rate-limiter-flexible - npm Package Compare versions

Comparing version 0.15.2 to 0.15.3

yarn-error.log

2

INSURANCE_STRATEGY.md

@@ -26,3 +26,3 @@ ## Insurance Strategy

It may block or allow some action depending on balancing approach.
4. Any limiter `RateLimiterRedis`, `RateLimiterMongo` can be used as insurance
4. Any limiter `RateLimiterRedis`, `RateLimiterMongo`, etc can be used as insurance

@@ -29,0 +29,0 @@ ```javascript

@@ -22,8 +22,14 @@ const RateLimiterStoreAbstract = require('./RateLimiterStoreAbstract');

this._tableCreated = false;
this.client.query(`CREATE DATABASE IF NOT EXISTS ${this.dbName};${this._getCreateTableStmt()}`, (err) => {
if (err) {
throw err;
this.client.query(`CREATE DATABASE IF NOT EXISTS ${this.dbName};`, (errDb) => {
if (errDb) {
throw errDb;
} else {
this._tableCreated = true;
this._clearExpiredHourAgo();
this.client.query(this._getCreateTableStmt(), (err) => {
if (err) {
throw err;
} else {
this._tableCreated = true;
this._clearExpiredHourAgo();
}
});
}

@@ -35,3 +41,3 @@ });

this._clearExpiredTimeoutId = setTimeout(() => {
const expire = new Date(Date.now() - 3600000);
const expire = Date.now() - 3600000;
this.client.query(`DELETE FROM ${this.tableName} WHERE expire < ?`, [expire], () => {

@@ -46,5 +52,5 @@ this._clearExpiredHourAgo();

return `CREATE TABLE IF NOT EXISTS ${this.tableName} (` +
'`key` varchar(255) NOT NULL,' +
'`points` int(9) NOT NULL default 0,' +
'`expire` datetime NOT NULL,' +
'`key` VARCHAR(255) CHARACTER SET utf8 NOT NULL,' +
'`points` INT(9) NOT NULL default 0,' +
'`expire` BIGINT UNSIGNED NOT NULL,' +
'PRIMARY KEY (`key`)' +

@@ -72,9 +78,3 @@ ') ENGINE = INNODB;';

const res = new RateLimiterRes();
let row;
if (result.length === 1) {
[row] = result;
} else {
const [, , rows] = result;
[row] = rows;
}
const [row] = result;

@@ -85,3 +85,3 @@ res.isFirstInDuration = changedPoints === row.points;

res.remainingPoints = Math.max(this.points - res.consumedPoints, 0);
res.msBeforeNext = Math.max(new Date(row.expire).getTime() - Date.now(), 0);
res.msBeforeNext = Math.max(row.expire - Date.now(), 0);

@@ -91,2 +91,82 @@ return res;

_upsertTransaction(isPool, conn, resolve, reject, key, points, msDuration, forceExpire) {
conn.query('BEGIN', (errBegin) => {
if (errBegin) {
conn.rollback(() => {
if (isPool) {
conn.release();
}
});
return reject(errBegin);
}
const dateNow = Date.now();
const newExpire = dateNow + msDuration;
let q;
let values;
if (forceExpire) {
q = `INSERT INTO ?? VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE
points = ?,
expire = ?;`;
values = [
this.tableName, key, points, newExpire,
points,
newExpire,
];
} else {
q = `INSERT INTO ?? VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE
points = IF(expire <= ?, ?, points + (?)),
expire = IF(expire <= ?, ?, expire);`;
values = [
this.tableName, key, points, newExpire,
dateNow, points, points,
dateNow, newExpire,
];
}
conn.query(q, values, (errUpsert) => {
if (errUpsert) {
conn.rollback(() => {
if (isPool) {
conn.release();
}
});
return reject(errUpsert);
}
conn.query('SELECT points, expire FROM ?? WHERE `key` = ?;', [this.tableName, key], (errSelect, res) => {
if (errSelect) {
conn.rollback(() => {
if (isPool) {
conn.release();
}
});
return reject(errSelect);
}
conn.query('COMMIT', (err) => {
if (err) {
conn.rollback(() => {
if (isPool) {
conn.release();
}
});
return reject(err);
}
if (isPool) {
conn.release();
}
resolve(res);
});
});
});
});
}
_upsert(key, points, msDuration, forceExpire = false) {

@@ -98,29 +178,14 @@ if (!this._tableCreated) {

return new Promise((resolve, reject) => {
const dateNow = new Date();
const newExpire = new Date(Date.now() + msDuration);
const expireQ = forceExpire
? ' @expire '
: ' IF(expire < @now, @expire, expire) ';
const q = `
SET @changedPoints = ?, @expire = ?, @now = ?;
INSERT INTO ?? VALUES (?, @changedPoints, @expire)
ON DUPLICATE KEY UPDATE
points = @changedPoints := IF(expire < @now, @changedPoints, points + (@changedPoints)),
expire = @expire := ${expireQ};
SELECT @changedPoints points, @expire expire;`;
// Pool support
if (typeof this.client.getConnection === 'function') {
this.client.getConnection((errConn, conn) => {
if (errConn) {
return reject(errConn);
}
this.client.query(
q,
[
points, newExpire, dateNow,
this.tableName, key,
],
(err, res) => {
if (err) {
reject(err);
} else {
resolve(res);
}
} // eslint-disable-line
);
this._upsertTransaction(true, conn, resolve, reject, key, points, msDuration, forceExpire);
});
} else {
this._upsertTransaction(false, this.client, resolve, reject, key, points, msDuration, forceExpire);
}
});

@@ -139,3 +204,3 @@ }

q,
[this.tableName, rlKey, new Date()],
[this.tableName, rlKey, Date.now()],
(err, res) => {

@@ -142,0 +207,0 @@ if (err) {

## RateLimiterMySQL
Note: It isn't recommended to use it with more than 200-300 limited actions per second.
It supports `mysql2` and `mysql` single connection and pool.
It supports `mysql2` and `mysql` node packages.
**Note**: It takes 50-150 ms per request on more than 1000 concurrent requests per second
MySQL connection have to be created with allowed `multipleStatementes`.
By default, RateLimiterMySQL creates `rtlmtrflx` database and separate table by `keyPrefix` for every limiter.

@@ -15,4 +13,39 @@

`RateLimiterMySQL` throws error on limiter creation, if database or table can NOT be created.
Limits data, which expired more than an hour ago, are removed every 5 minutes by `setTimeout`.
Connection to MySQL takes milliseconds, so any method of rate limiter is rejected with Error, until connection is established
### Usage
```javascript
const mysql = require('mysql2');
const {RateLimiterMySQL} = require('rate-limiter-flexible');
const pool = mysql.createPool({
connectionLimit : 100,
host: 'localhost',
user: 'root',
password: 'secret',
});
const opts = {
storeClient: pool,
dbName: 'mydb',
tableName: 'mytable', // all limiters store data in one table
points: 5, // Number of points
duration: 1, // Per second(s)
};
const rateLimiter = new RateLimiterMySQL(opts);
rateLimiter.consume(key)
.then((rateLimiterRes) => {
// Allowed
})
.catch((rej) => {
// Blocked
});
```
### Benchmark

@@ -22,3 +55,3 @@

Endpoint is limited by `RateLimiterMySQL` with config:
Endpoint is limited by `RateLimiterMySQL` with config for 500 random keys:

@@ -28,3 +61,3 @@ ```javascript

storeClient: mysql,
points: 20, // Number of points
points: 4, // Number of points
duration: 1, // Per second(s)

@@ -42,12 +75,12 @@ });

Statistics Avg Stdev Max
Reqs/sec 994.24 174.40 1562.24
Latency 11.10ms 7.71ms 88.65ms
Reqs/sec 1000.96 250.22 2171.97
Latency 20.88ms 17.01ms 141.73ms
Latency Distribution
50% 8.00ms
75% 14.09ms
90% 22.66ms
95% 28.47ms
99% 43.90ms
50% 12.94ms
75% 28.33ms
90% 48.01ms
95% 59.89ms
99% 85.00ms
HTTP codes:
1xx - 0, 2xx - 14967, 3xx - 0, 4xx - 15031, 5xx - 0
1xx - 0, 2xx - 24684, 3xx - 0, 4xx - 5322, 5xx - 0
```

@@ -59,12 +92,12 @@

Statistics Avg Stdev Max
Reqs/sec 995.90 305.88 6329.55
Latency 6.96ms 8.64ms 165.23ms
Reqs/sec 1002.28 299.86 2669.58
Latency 14.59ms 6.13ms 102.96ms
Latency Distribution
50% 5.69ms
75% 6.58ms
90% 7.87ms
95% 9.73ms
99% 44.62ms
50% 12.91ms
75% 16.84ms
90% 20.58ms
95% 25.60ms
99% 38.66ms
HTTP codes:
1xx - 0, 2xx - 27099, 3xx - 0, 4xx - 2884, 5xx - 0
1xx - 0, 2xx - 24647, 3xx - 0, 4xx - 5357, 5xx - 0
```
{
"name": "rate-limiter-flexible",
"version": "0.15.2",
"version": "0.15.3",
"description": "Flexible API rate limiter backed by Redis for distributed node.js applications",

@@ -5,0 +5,0 @@ "main": "index.js",

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

* [RateLimiterMongo](#ratelimitermongo)
* [RateLimiterMySQL](#ratelimitermysql)
* [RateLimiterMySQL](https://github.com/animir/node-rate-limiter-flexible/blob/master/MYSQL.md)
* [RateLimiterPostgreSQL](https://github.com/animir/node-rate-limiter-flexible/blob/master/POSTGRES.md)

@@ -62,4 +62,4 @@ * [RateLimiterCluster](#ratelimitercluster)

```text
5. MySQL 6.96 ms (with connection pool 100)
6. PostgreSQL 7.48 ms (with connection pool max 100)
5. PostgreSQL 7.48 ms (with connection pool max 100)
6. MySQL 14.59 ms (with connection pool 100)
```

@@ -355,35 +355,2 @@

### RateLimiterMySQL
It supports `mysql2` and `mysql` node packages.
MySQL connection have to be created with allowed `multipleStatementes`.
Limits data, which expired more than an hour ago, are removed every 5 minutes by `setTimeout`.
[Read more about RateLimiterMySQL here](https://github.com/animir/node-rate-limiter-flexible/blob/master/MYSQL.md)
```javascript
const mysql = require('mysql2');
const client = mysql.createConnection({
host : 'localhost',
user : 'root',
password : 'secret',
multipleStatements: true // it is required by limiter
});
const opts = {
storeClient: client,
dbName: 'mydb',
tableName: 'mytable', // all limiters store data in one table
points: 5, // Number of points
duration: 1, // Per second(s)
};
const rateLimiter = new RateLimiterMySQL(opts);
// Usage is the same as for RateLimiterRedis
```
Connection to MySQL takes milliseconds, so any method of rate limiter is rejected with Error, until connection is established
### RateLimiterCluster

@@ -390,0 +357,0 @@

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc