Class: Rack::Throttle::Limiter

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/throttle/limiter.rb

Overview

This is the base class for rate limiter implementations.

Examples:

Defining a rate limiter subclass

class MyLimiter < Limiter
  def allowed?(request)
    # TODO: custom logic goes here
  end
end

Direct Known Subclasses

Interval, TimeWindow

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (Limiter) initialize(app, options = {})

Returns a new instance of Limiter

Options Hash (options):

  • :cache (String) — default: Hash.new
  • :key (String) — default: nil
  • :key_prefix (String) — default: nil
  • :code (Integer) — default: 403
  • :message (String) — default: "Rate Limit Exceeded"


24
25
26
# File 'lib/rack/throttle/limiter.rb', line 24

def initialize(app, options = {})
  @app, @options = app, options
end

Instance Attribute Details

- (Object) app (readonly)

Returns the value of attribute app



13
14
15
# File 'lib/rack/throttle/limiter.rb', line 13

def app
  @app
end

- (Object) options (readonly)

Returns the value of attribute options



14
15
16
# File 'lib/rack/throttle/limiter.rb', line 14

def options
  @options
end

Instance Method Details

- (Boolean) allowed?(request)

Returns false if the rate limit has been exceeded for the given request, or true otherwise.

Override this method in subclasses that implement custom rate limiter strategies.



46
47
48
49
50
51
52
# File 'lib/rack/throttle/limiter.rb', line 46

def allowed?(request)
  case
    when whitelisted?(request) then true
    when blacklisted?(request) then false
    else true # override in subclasses
  end
end

- (Boolean) blacklisted?(request)

This method is abstract.

Returns true if the originator of the given request is blacklisted (not honoring rate limits, and thus permanently forbidden access without the need to maintain further rate limit counters).

The default implementation always returns false. Override this method in a subclass to implement custom blacklisting logic.



79
80
81
# File 'lib/rack/throttle/limiter.rb', line 79

def blacklisted?(request)
  false
end

- (Hash) cache (protected)



87
88
89
90
91
92
# File 'lib/rack/throttle/limiter.rb', line 87

def cache
  case cache = (options[:cache] ||= {})
    when Proc then cache.call
    else cache
  end
end

- (Object) cache_get(key, default = nil) (protected)



109
110
111
112
113
114
115
116
# File 'lib/rack/throttle/limiter.rb', line 109

def cache_get(key, default = nil)
  case
    when cache.respond_to?(:[])
      cache[key] || default
    when cache.respond_to?(:get)
      cache.get(key) || default
  end
end

- (Boolean) cache_has?(key) (protected)



96
97
98
99
100
101
102
103
104
# File 'lib/rack/throttle/limiter.rb', line 96

def cache_has?(key)
  case
    when cache.respond_to?(:has_key?)
      cache.has_key?(key)
    when cache.respond_to?(:get)
      cache.get(key) rescue false
    else false
  end
end

- (String) cache_key(request) (protected)



143
144
145
146
147
148
149
150
151
152
# File 'lib/rack/throttle/limiter.rb', line 143

def cache_key(request)
  id = client_identifier(request)
  case
    when options.has_key?(:key)
      options[:key].call(request)
    when options.has_key?(:key_prefix)
      [options[:key_prefix], id].join(':')
    else id
  end
end

- cache_set(key, value) (protected)



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/rack/throttle/limiter.rb', line 122

def cache_set(key, value)
  case
    when cache.respond_to?(:[]=)
      begin
        cache[key] = value
      rescue TypeError => e
        # GDBM throws a "TypeError: can't convert Float into String"
        # exception when trying to store a Float. On the other hand, we
        # don't want to unnecessarily coerce the value to a String for
        # any stores that do support other data types (e.g. in-memory
        # hash objects). So, this is a compromise.
        cache[key] = value.to_s
      end
    when cache.respond_to?(:set)
      cache.set(key, value)
  end
end

- (Array(Integer, Hash, #each)) call(env)



32
33
34
35
# File 'lib/rack/throttle/limiter.rb', line 32

def call(env)
  request = Rack::Request.new(env)
  allowed?(request) ? app.call(env) : rate_limit_exceeded
end

- (String) client_identifier(request) (protected)



157
158
159
# File 'lib/rack/throttle/limiter.rb', line 157

def client_identifier(request)
  request.ip.to_s
end

- (Array(Integer, Hash, #each)) http_error(code, message = nil, headers = {}) (protected)

Outputs an HTTP 4xx or 5xx response.



189
190
191
192
# File 'lib/rack/throttle/limiter.rb', line 189

def http_error(code, message = nil, headers = {})
  [code, {'Content-Type' => 'text/plain; charset=utf-8'}.merge(headers),
    http_status(code) + (message.nil? ? "\n" : " (#{message})\n")]
end

- (String) http_status(code) (protected)

Returns the standard HTTP status message for the given status code.



199
200
201
# File 'lib/rack/throttle/limiter.rb', line 199

def http_status(code)
  [code, Rack::Utils::HTTP_STATUS_CODES[code]].join(' ')
end

- (Array(Integer, Hash, #each)) rate_limit_exceeded (protected)

Outputs a Rate Limit Exceeded error.



177
178
179
180
# File 'lib/rack/throttle/limiter.rb', line 177

def rate_limit_exceeded
  headers = respond_to?(:retry_after) ? {'Retry-After' => retry_after.to_f.ceil.to_s} : {}
  http_error(options[:code] || 403, options[:message] || 'Rate Limit Exceeded', headers)
end

- (Float) request_start_time(request) (protected)



164
165
166
167
168
169
170
171
# File 'lib/rack/throttle/limiter.rb', line 164

def request_start_time(request)
  case
    when request.env.has_key?('HTTP_X_REQUEST_START')
      request.env['HTTP_X_REQUEST_START'].to_f / 1000
    else
      Time.now.to_f
  end
end

- (Boolean) whitelisted?(request)

This method is abstract.

Returns true if the originator of the given request is whitelisted (not subject to further rate limits).

The default implementation always returns false. Override this method in a subclass to implement custom whitelisting logic.



64
65
66
# File 'lib/rack/throttle/limiter.rb', line 64

def whitelisted?(request)
  false
end