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

Conversation

doliveirakn
Copy link

@doliveirakn doliveirakn commented Feb 15, 2018

Aiming to solve #283

Add an reader for the epoch_time variable in the cache so that it can 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

… 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
@lmansur
Copy link
Contributor

lmansur commented Mar 12, 2018

Hi @doliveirakn, thank you for implementing this PR.

Could you please write a test case for the issue you reported? This ensures the Gem is actually fixing your exact problem, helps document this specific use case and catches any future regression.

@doliveirakn
Copy link
Author

doliveirakn commented Mar 12, 2018

@lmansur I thought about a test case for the specific issue that was reported and I don't see a good way to write a spec for that.

The reason being is that the race condition comes from the fact that the time used in the cache is not exposed.

In the issue (#283) I have code that looks like this:

class FooMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, body = @app.call(env)
    now = Time.now
    match_data = env['rack.attack.match_data']
    if match_data
      headers.merge!({
        'X-RateLimit-Limit' => match_data[:limit].to_s,
        'X-RateLimit-Remaining' => match_data[:limit] - match_data[:count],
        'X-RateLimit-Reset' => (now + (match_data[:period] - now.to_i % match_data[:period])).to_s
      })
    end
    [status, headers, body]
  end
end

This PR as it stands does not fix the problem. However it does let code be written like:

class FooMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, body = @app.call(env)
    match_data = env['rack.attack.match_data']
    now = match_data[:epoch_time]
    if match_data
      headers.merge!({
        'X-RateLimit-Limit' => match_data[:limit].to_s,
        'X-RateLimit-Remaining' => match_data[:limit] - match_data[:count],
        'X-RateLimit-Reset' => (now + (match_data[:period] - now % match_data[:period])).to_s
      })
    end
    [status, headers, body]
  end
end

The tests now assert that the epoch_time is now available in the match_data. So long as that contract is upheld (and it is now included in the X-RateLimit headers for well-behaved clients section of the Readme) users should be able to avoid this race condition in the future. I believe that to be sufficient. if you don't, would you be able to point to see what kind of test you would like to see and I will happily add it.

@lmansur
Copy link
Contributor

lmansur commented Mar 12, 2018

I think you are right, the gem shouldn't really care about the user's implementation.

PR looks fine for me. 👍

@grzuy grzuy changed the title Provide epoch_time that was used for the request in the data set [Fixes #283] Provide epoch_time that was used for the request in the data set Mar 22, 2018
Copy link
Collaborator

@grzuy grzuy left a comment

Choose a reason for hiding this comment

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

Thank you for your contribution!

@@ -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 :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants