Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fixes #283] Provide epoch_time that was used for the request in the data set #282

Merged
merged 1 commit into from
Jun 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Collaborator

@grzuy grzuy Jun 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether making the epoch time an argument to the #count method might end up feeling a bit cleaner... Instead of keeping the last epoch time in the cache.

Not a blocker for merging though, just a consideration for a possible future refactor :-)


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