Skip to content
Open
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
50 changes: 49 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ end

Use any [ActiveSupport Time extension to Numeric](http://edgeguides.rubyonrails.org/active_support_core_extensions.html#time) object.

---

By default it will use the request ip address to identity the client making the request.
You can pass your own identifier using a `Proc`:

Expand All @@ -83,6 +85,36 @@ class MyApi < Grape::API
end
```

---

By default requests to each endpoint's methods will be counted separately, if you would like the requests to the method to be counted for and checked against a global total, you can specify `global_throttling: true`. When `global_throttling` is set to true, `max` and `per` are ignored on the endpoint and instead, `global_throttling_max`, and `global_throttling_per` from the [configuration](#configuration) are looked at.

> `global_throttling` can also be set to true in the [Grape::Attack configuration](#configuration).

```ruby
class MyApi < Grape::API

use Grape::Attack::Throttle

helpers do
def current_user
@current_user ||= User.authorize!(env)
end
end

resources :comments do

throttle global_throttling: true
get do
Comment.all
end

end
end
```

---

When rate limit is reached, it will raise `Grape::Attack::RateLimitExceededError` exception.
You can catch the exception using `rescue_from`:

Expand Down Expand Up @@ -115,6 +147,8 @@ Content-Type: application/json
{"message":"API rate limit exceeded for xxx.xxx.xxx.xxx."}
```

---

Finally the following headers will automatically be set:

* `X-RateLimit-Limit` -- The maximum number of requests that the consumer is permitted to make per specified period.
Expand All @@ -128,6 +162,21 @@ Currently there is only a Redis adapter. You can set redis client url through `e

Defaults to `redis://localhost:6379/0`.

## Configuration

If you wish to set a different [adapter](#adapters) or provide configuration options for `global_throttling`, you can configure `Grape::Attack` in a Rails initializer.

```ruby
# config/initializers/grape_attack.rb

Grape::Attack.configure do |c|
c.adapter = Grape::Attack::Adapters::Memory.new # defaults to Grape::Attack::Adapters::Redis.new
c.global_throttling = true # defaults to false
c.global_throttling_max = 1000 # defaults to 500
c.global_throttling_per = 1.day # defaults to 1.day
end
```

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
Expand All @@ -142,4 +191,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERN
## License

The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).

5 changes: 4 additions & 1 deletion lib/grape/attack/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ module Grape
module Attack
class Configuration

attr_accessor :adapter, :disable
attr_accessor :adapter, :disable, :global_throttling, :global_throttling_max, :global_throttling_per

def initialize
@adapter = ::Grape::Attack::Adapters::Redis.new
@disable = Proc.new { false }
@global_throttling = false
@global_throttling_max = 500
@global_throttling_per = 1.day
end

end
Expand Down
6 changes: 5 additions & 1 deletion lib/grape/attack/counter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ def update
private

def key
"#{request.method}:#{request.path}:#{request.client_identifier}"
if request.throttle_options.global_throttling
"#{request.client_identifier}"
else
"#{request.method}:#{request.path}:#{request.client_identifier}"
end
end

def ttl_in_seconds
Expand Down
17 changes: 14 additions & 3 deletions lib/grape/attack/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,26 @@ module Attack
class Options
include ActiveModel::Model

attr_accessor :max, :per, :identifier, :remaining
attr_accessor :max, :per, :identifier, :global_throttling, :remaining

validates :max, presence: true
validates :per, presence: true
validates :max, presence: true, unless: :global_throttling
validates :per, presence: true, unless: :global_throttling

def identifier
@identifier || Proc.new {}
end

def max
global_throttling ? ::Grape::Attack.config.global_throttling_max : @max
end

def per
global_throttling ? ::Grape::Attack.config.global_throttling_per : @per
end

def global_throttling
@global_throttling.nil? ? ::Grape::Attack.config.global_throttling : @global_throttling
end
end
end
end
6 changes: 3 additions & 3 deletions lib/grape/attack/throttle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ def after
return if ::Grape::Attack.config.disable.call
return unless request.throttle?

@app_response['X-RateLimit-Limit'] = request.context.route_setting(:throttle)[:max].to_s
@app_response['X-RateLimit-Remaining'] = request.context.route_setting(:throttle)[:remaining].to_s
@app_response['X-RateLimit-Reset'] = request.context.route_setting(:throttle)[:per].from_now.to_i.to_s
@app_response['X-RateLimit-Limit'] = request.throttle_options.max.to_s
@app_response['X-RateLimit-Remaining'] = request.throttle_options.remaining.to_s
@app_response['X-RateLimit-Reset'] = request.throttle_options.per.from_now.to_i.to_s
@app_response
end

Expand Down