class Prop::Limiter

Attributes

before_throttle_callback[RW]
cache[RW]
configurations[RW]
handles[RW]

Public Class Methods

before_throttle(&blk) click to toggle source
# File lib/prop/limiter.rb, line 38
def before_throttle(&blk)
  self.before_throttle_callback = blk
end
cache=(cache) click to toggle source
# File lib/prop/limiter.rb, line 22
def cache=(cache)
  [:read, :write, :increment, :decrement].each do |method|
    next if cache.respond_to?(method)
    raise ArgumentError, "Cache needs to respond to #{method}"
  end

  # https://github.com/petergoldstein/dalli/pull/481
  if defined?(ActiveSupport::Cache::DalliStore) &&
      cache.is_a?(ActiveSupport::Cache::DalliStore) &&
      Gem::Version.new(Dalli::VERSION) <= Gem::Version.new("2.7.4")
    raise "Upgrade to dalli 2.7.5+ to use prop v2, it fixes a local_cache vs increment bug"
  end

  @cache = cache
end
configure(handle, defaults) click to toggle source

Public: Registers a handle for rate limiting

handle - the name of the handle you wish to use in your code, e.g. :login_attempt defaults - the settings for this handle, e.g. { threshold: 5, interval: 5.minutes }

Raises Prop::RateLimited if the number if the threshold for this handle has been reached

# File lib/prop/limiter.rb, line 48
def configure(handle, defaults)
  Prop::Options.validate_options!(defaults)

  self.handles ||= {}
  self.handles[handle] = defaults
end
count(handle, key = nil, options = {}) click to toggle source

Public: Counts the number of times the given handle/key combination has been hit in the current window

handle - the throttle identifier key - the associated key

Returns a count of hits in the current window

# File lib/prop/limiter.rb, line 132
def count(handle, key = nil, options = {})
  options, cache_key, strategy = prepare(handle, key, options)
  strategy.counter(cache_key, options)
end
Also aliased as: query
disabled() { || ... } click to toggle source

Public: Disables Prop for a block of code

block - a block of code within which Prop will not raise

# File lib/prop/limiter.rb, line 58
def disabled(&_block)
  @disabled = true
  yield
ensure
  @disabled = false
end
query(handle, key = nil, options = {})
Alias for: count
read(&blk) click to toggle source
# File lib/prop/limiter.rb, line 14
def read(&blk)
  raise "Use .cache = "
end
reset(handle, key = nil, options = {}) click to toggle source

Public: Resets a specific throttle

handle - the throttle identifier key - the associated key

Returns nothing

# File lib/prop/limiter.rb, line 121
def reset(handle, key = nil, options = {})
  _options, cache_key, strategy = prepare(handle, key, options)
  strategy.reset(cache_key, options)
end
throttle(handle, key = nil, options = {}) { |: throttled| ... } click to toggle source

Public: Records a single action for the given handle/key combination.

handle - the registered handle associated with the action key - a custom request specific key, e.g. [ account.id, “download”, request.remote_ip ] options - request specific overrides to the defaults configured for this handle (optional) a block of code that this throttle is guarding

Returns true if the threshold for this handle has been reached, else returns false

# File lib/prop/limiter.rb, line 73
def throttle(handle, key = nil, options = {})
  options, cache_key, strategy = prepare(handle, key, options)
  throttled = _throttle(strategy, handle, key, cache_key, options).first
  block_given? && !throttled ? yield : throttled
end
throttle!(handle, key = nil, options = {}) { |: counter| ... } click to toggle source

Public: Records a single action for the given handle/key combination.

handle - the registered handle associated with the action key - a custom request specific key, e.g. [ account.id, “download”, request.remote_ip ] options - request specific overrides to the defaults configured for this handle (optional) a block of code that this throttle is guarding

Raises Prop::RateLimited if the threshold for this handle has been reached Returns the value of the block if given a such, otherwise the current count of the throttle

# File lib/prop/limiter.rb, line 88
def throttle!(handle, key = nil, options = {}, &block)
  options, cache_key, strategy = prepare(handle, key, options)
  throttled, counter = _throttle(strategy, handle, key, cache_key, options)

  if throttled
    raise Prop::RateLimited.new(options.merge(
      cache_key: cache_key,
      handle: handle,
      first_throttled: (throttled == :first_throttled)
    ))
  end

  block_given? ? yield : counter
end
throttled?(handle, key = nil, options = {}) click to toggle source

Public: Is the given handle/key combination currently throttled ?

handle - the throttle identifier key - the associated key

Returns true if a call to ‘throttle!` with same parameters would raise, otherwise false

# File lib/prop/limiter.rb, line 109
def throttled?(handle, key = nil, options = {})
  options, cache_key, strategy = prepare(handle, key, options)
  counter = strategy.counter(cache_key, options)
  strategy.compare_threshold?(counter, :>=, options)
end
write(&blk) click to toggle source
# File lib/prop/limiter.rb, line 18
def write(&blk)
  raise "Use .cache = "
end

Private Class Methods

_throttle(strategy, handle, key, cache_key, options) click to toggle source
# File lib/prop/limiter.rb, line 149
def _throttle(strategy, handle, key, cache_key, options)
  return [false, strategy.zero_counter] if disabled?

  if leaky_bucket_strategy?(strategy)
    return Prop::LeakyBucketStrategy._throttle_leaky_bucket(handle, key, cache_key, options)
  end

  counter = options.key?(:decrement) ?
    strategy.decrement(cache_key, options.fetch(:decrement), options) :
    strategy.increment(cache_key, options.fetch(:increment, 1), options)

  if strategy.compare_threshold?(counter, :>, options)
    before_throttle_callback &&
      before_throttle_callback.call(handle, key, options[:threshold], options[:interval])

    result = if options[:first_throttled] && strategy.first_throttled?(counter, options)
      :first_throttled
    else
      true
    end

    [result, counter]
  else
    [false, counter]
  end
end
disabled?() click to toggle source
# File lib/prop/limiter.rb, line 176
def disabled?
  defined?(@disabled) && !!@disabled
end
leaky_bucket_strategy?(strategy) click to toggle source
# File lib/prop/limiter.rb, line 145
def leaky_bucket_strategy?(strategy)
  strategy == Prop::LeakyBucketStrategy
end
prepare(handle, key, params) click to toggle source
# File lib/prop/limiter.rb, line 180
def prepare(handle, key, params)
  unless defaults = handles[handle]
    raise KeyError.new("No such handle configured: #{handle.inspect}")
  end

  options = Prop::Options.build(key: key, params: params, defaults: defaults)

  strategy = options.fetch(:strategy)

  cache_key = strategy.build(key: key, handle: handle, interval: options[:interval])

  [ options, cache_key, strategy ]
end