Packages

Rate limiting Plug module based on Redis Lua scripting

Current section

Files

Jump to
plug_limit lua token_bucket.lua
Raw

lua/token_bucket.lua

local id = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local burst = tonumber(ARGV[3])
if limit < burst then
error('Invalid token_bucket script input. Limit can not be smaller than burst rate.')
end
if burst < 1 then
error('Invalid token_bucket script input. Burst must be greater than or equal to one.')
end
local function time_msec()
local t = redis.pcall('TIME')
return t[1] * 1000 + math.floor(t[2] / 1000)
end
local function headers(limit, window, ttl, remaining, burst)
return {
tostring(limit) .. ' ' ..
tostring(limit) .. ';w=' .. tostring(window) .. ';burst=' ..
tostring(burst) .. ';policy=token_bucket',
tostring(ttl),
tostring(remaining)
}
end
if redis.pcall('EXISTS', id) == 1 then
local now = time_msec()
local data = redis.pcall('HMGET', id, 'bucket', 'remaining', 'ts')
local ttl = redis.pcall('TTL', id)
local bucket = data[1]
local remaining = data[2]
local ts = data[3]
local d_bucket = (limit - burst) * (now - ts) / window / 1000
local new_bucket = bucket + d_bucket
if new_bucket >= 1 then
local new_bucket = math.floor(redis.pcall('HINCRBYFLOAT', id, 'bucket', d_bucket - 1))
local remaining = redis.pcall('HINCRBY', id, 'remaining', -1)
redis.pcall('HSET', id, 'ts', now)
local h = headers(limit, window, ttl, remaining, burst)
if new_bucket >= 1 then
return {'allow', h}
else
local retry_after = math.min(math.ceil(window / (limit - burst)), ttl)
h[#h+1] = tostring(retry_after)
return {'allow', h}
end
else
local h = headers(limit, window, ttl, remaining, burst)
local retry_after = math.min(math.abs(math.ceil(window / (limit - burst) - (now - ts) / 1000)), ttl)
h[#h+1] = tostring(retry_after)
return {'deny', h}
end
else
local now = time_msec()
local remaining = limit - 1
local bucket = burst - 1
redis.pcall('HSET', id, 'bucket', bucket, 'remaining', remaining, 'ts', now)
redis.pcall('EXPIRE', id, window)
local h = headers(limit, window, window, remaining, burst)
if bucket >= 1 then
return {'allow', h}
else
local dt_token = math.ceil(window / (limit - burst))
h[#h+1] = tostring(dt_token)
return {'allow', h}
end
end