class Prop::Limiter
Attributes
Public Class Methods
# File lib/prop/limiter.rb, line 38 def before_throttle(&blk) self.before_throttle_callback = blk end
# 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
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
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
# File lib/prop/limiter.rb, line 14 def read(&blk) raise "Use .cache = " end
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
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
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
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
# File lib/prop/limiter.rb, line 18 def write(&blk) raise "Use .cache = " end
Private Class Methods
# 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
# File lib/prop/limiter.rb, line 176 def disabled? defined?(@disabled) && !!@disabled end
# File lib/prop/limiter.rb, line 145 def leaky_bucket_strategy?(strategy) strategy == Prop::LeakyBucketStrategy end
# 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