using cartodb-redis in rate limits
This commit is contained in:
parent
755cc6cf4a
commit
9cd4e85768
@ -1,24 +1,3 @@
|
|||||||
const RATE_LIMIT_REDIS_DB = 8;
|
|
||||||
const getRateLimitLuaScript = `
|
|
||||||
local results = {}
|
|
||||||
local resultsCounter = 0
|
|
||||||
|
|
||||||
local limits = {}
|
|
||||||
local limitsArray = redis.call("LRANGE", KEYS[1], 0, -1)
|
|
||||||
|
|
||||||
for i, v in ipairs(limitsArray) do
|
|
||||||
local rest = i % 3
|
|
||||||
if rest ~= 0 then
|
|
||||||
limits[rest] = v
|
|
||||||
else
|
|
||||||
resultsCounter = resultsCounter + 1
|
|
||||||
results[resultsCounter] = redis.call("CL.THROTTLE", KEYS[2], limits[1], limits[2], v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return results
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UserLimits
|
* UserLimits
|
||||||
* @param {cartodb-redis} metadataBackend
|
* @param {cartodb-redis} metadataBackend
|
||||||
@ -29,128 +8,18 @@ class UserLimits {
|
|||||||
this.metadataBackend = metadataBackend;
|
this.metadataBackend = metadataBackend;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
|
||||||
this.rateLimits = {
|
|
||||||
redisCommand: 'EVAL',
|
|
||||||
sha: null,
|
|
||||||
lua: getRateLimitLuaScript
|
|
||||||
};
|
|
||||||
|
|
||||||
this.preprareRateLimit();
|
this.preprareRateLimit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns Redis key where the limits are saved by user and endpoint
|
|
||||||
* The value is a Redis hash:
|
|
||||||
* maxBurst (b): Integer (as string)
|
|
||||||
* countPerPeriod (c): Integer (as string)
|
|
||||||
* period (p): Integer (as string)
|
|
||||||
* @param {string} user
|
|
||||||
* @param {string} endpointGroup
|
|
||||||
*/
|
|
||||||
static getRateLimitsStoreKey(user, endpointGroup) {
|
|
||||||
return `limits:rate:store:${user}:sql:${endpointGroup}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns Redis key where the current state of the limit by user and endpoint
|
|
||||||
* This key is managed by redis-cell (CL.THROTTLE command)
|
|
||||||
* @param {string} user
|
|
||||||
* @param {string} endpointGroup
|
|
||||||
*/
|
|
||||||
static getRateLimitStatusKey(user, endpointGroup) {
|
|
||||||
return `limits:rate:status:${user}:sql:${endpointGroup}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the inner rateLimit what is the strictest one
|
|
||||||
* @param {Array} rateLimits Each inner array has 5 integers indicating:
|
|
||||||
* isBloqued, limit, remaining, retry, reset
|
|
||||||
*/
|
|
||||||
static getLowerRateLimit(rateLimits) {
|
|
||||||
/*jshint maxcomplexity:10 */
|
|
||||||
if (!Array.isArray(rateLimits) || !rateLimits.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let minIndex;
|
|
||||||
let minRemainingValue;
|
|
||||||
for (let currentIndex = 0; currentIndex < rateLimits.length; currentIndex++) {
|
|
||||||
const rateLimit = rateLimits[currentIndex];
|
|
||||||
if (!UserLimits.validateRatelimit(rateLimit)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [isBlocked, , remaining] = rateLimit;
|
|
||||||
|
|
||||||
if (isBlocked === 1) {
|
|
||||||
minIndex = currentIndex;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minRemainingValue === undefined || remaining < minRemainingValue) {
|
|
||||||
minIndex = currentIndex;
|
|
||||||
minRemainingValue = remaining;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rateLimits[minIndex]) {
|
|
||||||
return rateLimits[minIndex];
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static validateRatelimit(rateLimit) {
|
|
||||||
return rateLimit.length === 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
preprareRateLimit() {
|
preprareRateLimit() {
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (this.options.limits.rateLimitsEnabled) {
|
if (this.options.limits.rateLimitsEnabled) {
|
||||||
this.metadataBackend.redisCmd(
|
this.metadataBackend.loadRateLimitsScript();
|
||||||
RATE_LIMIT_REDIS_DB,
|
|
||||||
'SCRIPT',
|
|
||||||
['LOAD', getRateLimitLuaScript],
|
|
||||||
(err, sha) => {
|
|
||||||
if (!err && sha) {
|
|
||||||
self.rateLimits.sha = sha;
|
|
||||||
self.rateLimits.redisCommand = 'EVALSHA';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getRateLimit(user, endpointGroup, callback) {
|
getRateLimit(user, endpointGroup, callback) {
|
||||||
var self = this;
|
this.metadataBackend.getRateLimit(user, 'sql', endpointGroup, callback);
|
||||||
|
|
||||||
let redisParams = [
|
|
||||||
this.rateLimits.redisCommand === 'EVAL' ? this.rateLimits.lua : this.rateLimits.sha,
|
|
||||||
2,
|
|
||||||
UserLimits.getRateLimitsStoreKey(user, endpointGroup), // KEY[1]
|
|
||||||
UserLimits.getRateLimitStatusKey(user, endpointGroup) // KEY[2]
|
|
||||||
];
|
|
||||||
|
|
||||||
this.metadataBackend.redisCmd(
|
|
||||||
RATE_LIMIT_REDIS_DB,
|
|
||||||
this.rateLimits.redisCommand,
|
|
||||||
redisParams,
|
|
||||||
(err, rateLimits) => {
|
|
||||||
if (err) {
|
|
||||||
if (err.name === 'ReplyError' && err.message === 'NOSCRIPT No matching script. Please use EVAL.') {
|
|
||||||
self.rateLimits.redisCommand = 'EVAL';
|
|
||||||
return self.getRateLimit(user, endpointGroup, callback);
|
|
||||||
} else {
|
|
||||||
callback(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, UserLimits.getLowerRateLimit(rateLimits));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = UserLimits;
|
module.exports = UserLimits;
|
||||||
module.exports.RATE_LIMIT_REDIS_DB = RATE_LIMIT_REDIS_DB;
|
|
||||||
|
Loading…
Reference in New Issue
Block a user