2013-02-05 14:16:51 -05:00
|
|
|
require_dependency 'rate_limiter/limit_exceeded'
|
|
|
|
require_dependency 'rate_limiter/on_create_record'
|
|
|
|
|
|
|
|
# A redis backed rate limiter.
|
|
|
|
class RateLimiter
|
|
|
|
|
|
|
|
# We don't observe rate limits in test mode
|
|
|
|
def self.disabled?
|
2013-02-25 11:42:20 -05:00
|
|
|
Rails.env.test?
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(user, key, max, secs)
|
|
|
|
@user = user
|
|
|
|
@key = "rate-limit:#{@user.id}:#{key}"
|
|
|
|
@max = max
|
|
|
|
@secs = secs
|
|
|
|
end
|
|
|
|
|
|
|
|
def clear!
|
|
|
|
$redis.del(@key)
|
|
|
|
end
|
|
|
|
|
|
|
|
def can_perform?
|
2013-05-23 20:18:59 -04:00
|
|
|
rate_unlimited? || is_under_limit?
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def performed!
|
2013-05-23 20:18:59 -04:00
|
|
|
return if rate_unlimited?
|
2013-02-05 14:16:51 -05:00
|
|
|
|
|
|
|
result = $redis.incr(@key).to_i
|
|
|
|
$redis.expire(@key, @secs) if result == 1
|
|
|
|
if result > @max
|
|
|
|
|
|
|
|
# In case we go over, clamp it to the maximum
|
|
|
|
$redis.decr(@key)
|
|
|
|
|
2013-02-25 11:42:20 -05:00
|
|
|
raise LimitExceeded.new($redis.ttl(@key))
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def rollback!
|
|
|
|
return if RateLimiter.disabled?
|
|
|
|
$redis.decr(@key)
|
|
|
|
end
|
|
|
|
|
2013-05-23 20:18:59 -04:00
|
|
|
private
|
|
|
|
|
|
|
|
def is_under_limit?
|
|
|
|
$redis.get(@key).to_i < @max
|
|
|
|
end
|
|
|
|
|
|
|
|
def rate_unlimited?
|
|
|
|
!!(RateLimiter.disabled? || @user.staff?)
|
|
|
|
end
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|