Skip to content

Instantly share code, notes, and snippets.

@JunilJacob
Last active October 23, 2019 08:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JunilJacob/d6567f8b1af0019a2c9d07f3b0593bf3 to your computer and use it in GitHub Desktop.
Save JunilJacob/d6567f8b1af0019a2c9d07f3b0593bf3 to your computer and use it in GitHub Desktop.
Request Rate Limiter
class Api::V3::BaseController < ActionController::Base
include Limiter
before_action -> { rate_limit(key: "api_#{doorkeeper_token.application_id}", rate: 60, interval: 60) }
private
def handle_rate_limit_exception
log_exception RateLimitError.new("Rate limit reached for #{doorkeeper_token.application.name}")
render json: { error: { type: "rate_limit_exceeded", message: "Rate limit exceeded." } }, status: 429
end
end
class RateLimitError < RuntimeError; end
RATE_LIMIT_SCRIPT = File.read(Rails.root.join('config/redis/request_rate_limiter.lua'))
module Limiter
def rate_limit(key:, rate:, interval: 60, requested: 1, &block)
args = [rate, interval, requested, Time.current.to_i]
redis_keys = [key + ".tokens", key + ".timestamp"]
allowed, remaining_count = $redis.eval(::RATE_LIMIT_SCRIPT, redis_keys, args)
if !allowed
handle_rate_limit_exception
end
yield if block_given?
end
end
local tokens_key = KEYS[1]
local timestamp_key = KEYS[2]
local rate = tonumber(ARGV[1])
local interval = tonumber(ARGV[2])
local requested = tonumber(ARGV[3])
local now = tonumber(ARGV[4])
local remaining_count = 0
local current_count = tonumber(redis.call("get", tokens_key))
if current_count == nil then
current_count = rate
end
local last_refreshed = tonumber(redis.call("get", timestamp_key))
if last_refreshed == nil then
last_refreshed = now
end
local ttl = interval - (now - last_refreshed)
local allowed = current_count >= requested
if allowed then
remaining_count = current_count - requested
end
redis.call("setex", tokens_key, ttl, remaining_count)
redis.call("setex", timestamp_key, ttl, last_refreshed)
return { allowed, remaining_count }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment