Skip to content

Commit

Permalink
Add an reader for the epoch_time variable in the cache so that it can…
Browse files Browse the repository at this point in the history
… also be returned in the data from the throttle.

This is allows access to the same time that the cache uses for the count. This can be important for clients that want to provide rate limit information for well-behaved clients
  • Loading branch information
Kyle d'Oliveira committed Feb 15, 2018
1 parent 39c04b3 commit 9dbece5
Show file tree
Hide file tree
Showing 4 changed files with 15 additions and 11 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,13 @@ Here's an example response that includes conventional `X-RateLimit-*` headers:

```ruby
Rack::Attack.throttled_response = lambda do |env|
now = Time.now
match_data = env['rack.attack.match_data']
now = match_data[:epoch_time]

headers = {
'X-RateLimit-Limit' => match_data[:limit].to_s,
'X-RateLimit-Remaining' => '0',
'X-RateLimit-Reset' => (now + (match_data[:period] - now.to_i % match_data[:period])).to_s
'X-RateLimit-Reset' => (now + (match_data[:period] - now % match_data[:period])).to_s
}

[ 429, headers, ["Throttled\n"]]
Expand All @@ -271,7 +271,7 @@ end
For responses that did not exceed a throttle limit, Rack::Attack annotates the env with match data:

```ruby
request.env['rack.attack.throttle_data'][name] # => { :count => n, :period => p, :limit => l }
request.env['rack.attack.throttle_data'][name] # => { :count => n, :period => p, :limit => l, :epoch_time => t }
```

## Logging & Instrumentation
Expand Down
7 changes: 4 additions & 3 deletions lib/rack/attack/cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class Attack
class Cache

attr_accessor :prefix
attr_reader :last_epoch_time

def initialize
self.store = ::Rails.cache if defined?(::Rails.cache)
Expand Down Expand Up @@ -39,10 +40,10 @@ def delete(unprefixed_key)
private

def key_and_expiry(unprefixed_key, period)
epoch_time = Time.now.to_i
@last_epoch_time = Time.now.to_i
# Add 1 to expires_in to avoid timing error: http://git.io/i1PHXA
expires_in = (period - (epoch_time % period) + 1).to_i
["#{prefix}:#{(epoch_time / period).to_i}:#{unprefixed_key}", expires_in]
expires_in = (period - (@last_epoch_time % period) + 1).to_i
["#{prefix}:#{(@last_epoch_time / period).to_i}:#{unprefixed_key}", expires_in]
end

def do_count(key, expires_in)
Expand Down
5 changes: 4 additions & 1 deletion lib/rack/attack/throttle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@ def [](req)
current_limit = limit.respond_to?(:call) ? limit.call(req) : limit
key = "#{name}:#{discriminator}"
count = cache.count(key, current_period)
epoch_time = cache.last_epoch_time

data = {
:count => count,
:period => current_period,
:limit => current_limit
:limit => current_limit,
:epoch_time => epoch_time
}

(req.env['rack.attack.throttle_data'] ||= {})[name] = data

(count > current_limit).tap do |throttled|
Expand Down
8 changes: 4 additions & 4 deletions spec/rack_attack_throttle_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
end

it 'should populate throttle data' do
data = { :count => 1, :limit => 1, :period => @period }
data = { :count => 1, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i }
last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
end
end
Expand All @@ -37,7 +37,7 @@
it 'should tag the env' do
last_request.env['rack.attack.matched'].must_equal 'ip/sec'
last_request.env['rack.attack.match_type'].must_equal :throttle
last_request.env['rack.attack.match_data'].must_equal({:count => 2, :limit => 1, :period => @period})
last_request.env['rack.attack.match_data'].must_equal({:count => 2, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i})
last_request.env['rack.attack.match_discriminator'].must_equal('1.2.3.4')
end

Expand Down Expand Up @@ -65,7 +65,7 @@
end

it 'should populate throttle data' do
data = { :count => 1, :limit => 1, :period => @period }
data = { :count => 1, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i }
last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
end
end
Expand All @@ -89,7 +89,7 @@
end

it 'should populate throttle data' do
data = { :count => 1, :limit => 1, :period => @period }
data = { :count => 1, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i }
last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
end
end
Expand Down

0 comments on commit 9dbece5

Please sign in to comment.