2013-02-05 13:16:51 -06:00
|
|
|
require_dependency 'rate_limiter/limit_exceeded'
|
|
|
|
require_dependency 'rate_limiter/on_create_record'
|
|
|
|
|
|
|
|
# A redis backed rate limiter.
|
|
|
|
class RateLimiter
|
|
|
|
|
2015-04-15 18:44:30 -05:00
|
|
|
attr_reader :max, :secs, :user, :key
|
|
|
|
|
2015-02-02 11:44:21 -06:00
|
|
|
def self.key_prefix
|
|
|
|
"l-rate-limit:"
|
|
|
|
end
|
2015-01-29 10:44:51 -06:00
|
|
|
|
2013-10-08 23:10:37 -05:00
|
|
|
def self.disable
|
|
|
|
@disabled = true
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.enable
|
|
|
|
@disabled = false
|
|
|
|
end
|
|
|
|
|
2013-02-05 13:16:51 -06:00
|
|
|
# We don't observe rate limits in test mode
|
|
|
|
def self.disabled?
|
2013-10-08 23:10:37 -05:00
|
|
|
@disabled || Rails.env.test?
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
|
2015-01-29 10:44:51 -06:00
|
|
|
def self.clear_all!
|
2015-02-02 11:44:21 -06:00
|
|
|
$redis.delete_prefixed(RateLimiter.key_prefix)
|
2015-01-29 10:44:51 -06:00
|
|
|
end
|
|
|
|
|
2013-02-05 13:16:51 -06:00
|
|
|
def initialize(user, key, max, secs)
|
|
|
|
@user = user
|
2015-02-02 11:44:21 -06:00
|
|
|
@key = "#{RateLimiter.key_prefix}:#{@user && @user.id}:#{key}"
|
2013-02-05 13:16:51 -06:00
|
|
|
@max = max
|
|
|
|
@secs = secs
|
|
|
|
end
|
|
|
|
|
|
|
|
def clear!
|
|
|
|
$redis.del(@key)
|
|
|
|
end
|
|
|
|
|
|
|
|
def can_perform?
|
2013-05-23 19:18:59 -05:00
|
|
|
rate_unlimited? || is_under_limit?
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
def performed!
|
2013-05-23 19:18:59 -05:00
|
|
|
return if rate_unlimited?
|
2013-02-05 13:16:51 -06:00
|
|
|
|
2013-05-25 14:37:28 -05:00
|
|
|
if is_under_limit?
|
|
|
|
# simple ring buffer.
|
|
|
|
$redis.lpush(@key, Time.now.to_i)
|
|
|
|
$redis.ltrim(@key, 0, @max - 1)
|
2013-05-27 17:58:45 -05:00
|
|
|
|
|
|
|
# let's ensure we expire this key at some point, otherwise we have leaks
|
|
|
|
$redis.expire(@key, @secs * 2)
|
2013-05-25 14:37:28 -05:00
|
|
|
else
|
|
|
|
raise LimitExceeded.new(seconds_to_wait)
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def rollback!
|
|
|
|
return if RateLimiter.disabled?
|
2013-05-25 14:37:28 -05:00
|
|
|
$redis.lpop(@key)
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
|
2013-05-23 19:18:59 -05:00
|
|
|
private
|
|
|
|
|
2013-05-25 14:37:28 -05:00
|
|
|
def seconds_to_wait
|
|
|
|
@secs - age_of_oldest
|
|
|
|
end
|
|
|
|
|
|
|
|
def age_of_oldest
|
|
|
|
# age of oldest event in buffer, in seconds
|
|
|
|
Time.now.to_i - $redis.lrange(@key, -1, -1).first.to_i
|
|
|
|
end
|
|
|
|
|
2013-05-23 19:18:59 -05:00
|
|
|
def is_under_limit?
|
2015-01-29 10:44:51 -06:00
|
|
|
# number of events in buffer less than max allowed? OR
|
|
|
|
($redis.llen(@key) < @max) ||
|
|
|
|
# age bigger than silding window size?
|
|
|
|
(age_of_oldest > @secs)
|
2013-05-23 19:18:59 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def rate_unlimited?
|
2014-08-17 19:55:30 -05:00
|
|
|
!!(RateLimiter.disabled? || (@user && @user.staff?))
|
2013-05-23 19:18:59 -05:00
|
|
|
end
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|