using cartodb-redis for rate limit
This commit is contained in:
@@ -1,8 +1,5 @@
|
||||
var step = require('step');
|
||||
|
||||
|
||||
const RATE_LIMIT_REDIS_DB = 8;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param metadataBackend
|
||||
@@ -14,11 +11,6 @@ function UserLimitsApi(metadataBackend, options) {
|
||||
this.metadataBackend = metadataBackend;
|
||||
this.options = options || {};
|
||||
this.options.limits = this.options.limits || {};
|
||||
this.rateLimits = {
|
||||
redisCommand: 'EVAL',
|
||||
sha: null,
|
||||
lua: getRateLimitLuaScript()
|
||||
};
|
||||
|
||||
this.preprareRateLimit();
|
||||
}
|
||||
@@ -89,139 +81,11 @@ UserLimitsApi.prototype.getTimeoutRenderLimit = function (username, apiKey, call
|
||||
};
|
||||
|
||||
UserLimitsApi.prototype.preprareRateLimit = function () {
|
||||
var self = this;
|
||||
|
||||
if (this.options.limits.rateLimitsEnabled) {
|
||||
this.metadataBackend.redisCmd(
|
||||
RATE_LIMIT_REDIS_DB,
|
||||
'SCRIPT',
|
||||
['LOAD', getRateLimitLuaScript()],
|
||||
function (err, sha) {
|
||||
if (!err && sha) {
|
||||
self.rateLimits.sha = sha;
|
||||
self.rateLimits.redisCommand = 'EVALSHA';
|
||||
}
|
||||
}
|
||||
);
|
||||
this.metadataBackend.loadRateLimitsScript();
|
||||
}
|
||||
};
|
||||
|
||||
UserLimitsApi.prototype.getRateLimit = function (user, endpointGroup, callback) {
|
||||
var self = this;
|
||||
|
||||
let redisParams = [
|
||||
this.rateLimits.redisCommand === 'EVAL' ? this.rateLimits.lua : this.rateLimits.sha,
|
||||
2,
|
||||
getStoreKey(user, endpointGroup), // KEY[1]
|
||||
getStatusKey(user, endpointGroup) // KEY[2]
|
||||
];
|
||||
|
||||
this.metadataBackend.redisCmd(
|
||||
RATE_LIMIT_REDIS_DB,
|
||||
this.rateLimits.redisCommand,
|
||||
redisParams,
|
||||
function (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, getLowerRateLimit(rateLimits));
|
||||
}
|
||||
);
|
||||
this.metadataBackend.getRateLimit(user, 'maps', endpointGroup, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
function getStoreKey(user, endpointGroup) {
|
||||
return `limits:rate:store:${user}:maps:${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
|
||||
*/
|
||||
function getStatusKey(user, endpointGroup) {
|
||||
return `limits:rate:status:${user}:maps:${endpointGroup}`;
|
||||
}
|
||||
|
||||
function getRateLimitLuaScript() {
|
||||
return `
|
||||
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
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the inner rateLimit what is the strictest one
|
||||
* @param {Array} rateLimits Each inner array has 5 integers indicating:
|
||||
* isBloqued, limit, remaining, retry, reset
|
||||
*/
|
||||
function 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 (!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;
|
||||
}
|
||||
}
|
||||
|
||||
function validateRatelimit(rateLimit) {
|
||||
return rateLimit.length === 5;
|
||||
}
|
||||
|
||||
module.exports.getStoreKey = getStoreKey;
|
||||
module.exports.getLowerRateLimit = getLowerRateLimit;
|
||||
|
||||
Reference in New Issue
Block a user