module Sinatra::RateLimiter::Helpers

Public Instance Methods

rate_limit(*args) click to toggle source
# File lib/sinatra/rate-limiter.rb, line 13
def rate_limit(*args)
  return unless settings.rate_limiter and settings.rate_limiter_environments.include?(settings.environment)

  bucket, options, limits = parse_args(args)

  limiter = RateLimit.new(bucket, limits)
  limiter.settings  = settings
  limiter.request   = request
  limiter.options   = options

  limiter.headers.each{|h,v| response.headers[h] = v} if limiter.options.send_headers

  if (error_locals = limiter.limits_exceeded?)
    response.headers['Retry-After'] = error_locals[:try_again] if limiter.options.send_headers

    request.env['sinatra.error.rate_limiter'] = Struct.new(*error_locals.keys).new(*error_locals.values)
    raise Sinatra::RateLimiter::Exceeded, "#{bucket.eql?('default') ? 'R' : bucket + ' r'}ate limit exceeded"
  end

  limiter.log_request
end

Private Instance Methods

parse_args(args) click to toggle source
# File lib/sinatra/rate-limiter.rb, line 37
def parse_args(args)
  bucket  = (args.first.class == String) ? args.shift : 'default'
  options = (args.last.class == Hash)    ? args.pop   : {}
  limits  = (args.size < 1) ? settings.rate_limiter_default_limits : args

  if (limits.size < 1)
    raise ArgumentError, 'No explicit or default limits values provided.'
  elsif (limits.map{|a| a.class}.select{|a| a != Fixnum}.count > 0)
    raise ArgumentError, 'Non-Fixnum parameters supplied. All parameters must be Fixnum except the first which may be a String.'
  elsif ((limits.map{|a| a.class}.size % 2) != 0)
    raise ArgumentError, 'Wrong number of Fixnum parameters supplied.'
  elsif !(bucket =~ /^[a-zA-Z0-9\-]*$/)
    raise ArgumentError, 'Limit name must be a String containing only a-z, A-Z, 0-9, and -.'
  end

  options.to_a.each do |option, value|
    case option
    when :send_headers
      raise ArgumentError, 'send_headers must be true or false' if !(value == (true or false))
    when :header_prefix
      raise ArgumentError, 'header_prefix must be a String' if value.class != String
    when :identifier
      raise ArgumentError, 'identifier must be a Proc' if value.class != Proc
    else
      raise ArgumentError, "Invalid option #{option}"
    end
  end

  return [bucket,
          options,
          limits.each_slice(2).map{|a| {requests: a[0], seconds: a[1]}}]
end