Class: Rack::RateLimiter

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

Instance Method Summary collapse

Constructor Details

#initialize(app, options = {}) ⇒ RateLimiter

Returns a new instance of RateLimiter.



7
8
9
10
11
12
13
14
15
16
# File 'lib/rack/rate_limiter.rb', line 7

def initialize(app, options={})
  @app = app
  @options ={
    :redis => nil,
    :limit => nil,
    :max_requests => 60, 
    :interval => 60, # in seconds
    :redis_namespace => 'rate_limiter'
  }.merge(options)
end

Instance Method Details

#call(env, options = {}) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/rack/rate_limiter.rb', line 18

def call(env, options={})
  request = Rack::Request.new(env)
  status, headers, body = @app.call(env)

  return status, headers, body unless rate_limited?(request)

  begin
    key = generate_key(request)
    redis.setex(key, @options[:interval], @options[:max_requests]) unless redis.exists(key)
    @rate_remaining = redis.decr(key)
  
    return limit_exceeded! if @rate_remaining < 0
  rescue ::Errno::ECONNREFUSED
    return [status, headers, body]
  end

  headers.merge!({'X-RateLimit-Limit' => @options[:max_requests].to_s,
                  'X-RateLimit-Remaining' => @rate_remaining.to_s
                })

  return status, headers, body
end

#client_id(request) ⇒ Object



45
46
47
# File 'lib/rack/rate_limiter.rb', line 45

def client_id(request)
  request.ip.to_s
end

#generate_key(request) ⇒ Object



41
42
43
# File 'lib/rack/rate_limiter.rb', line 41

def generate_key(request)
  "#{client_id(request)}"
end

#limit_exceeded!Object



59
60
61
62
63
64
65
66
# File 'lib/rack/rate_limiter.rb', line 59

def limit_exceeded!
  code = 403
  body = {:error => {:code => 403, :message => "Rate Limit Exceeded"}}.to_json
  [code, 
      {'Content-Type' => 'application/json; charset=utf-8',
       'Content-Lenght' => body.size.to_s
      }, body]
end

#rate_limited?(request) ⇒ Boolean

Returns:

  • (Boolean)


49
50
51
52
53
54
55
56
57
# File 'lib/rack/rate_limiter.rb', line 49

def rate_limited?(request)
  limit = @options[:limit]

  unless limit[:domain].nil?
    return request.host == limit[:domain]
  end

  return false
end

#redisObject



68
69
70
71
72
73
74
75
76
77
78
# File 'lib/rack/rate_limiter.rb', line 68

def redis
  if @options[:redis].nil?
    begin
      @redis = Redis::Namespace.new(@options[:redis_namespace], :redis => Redis.new(:thread_safe => true))
    rescue Errno::ECONNREFUSED
    end
  else
    @redis = @options[:redis]
  end
  @redis
end