rack-attack-rate-limit 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ae6161b3d1a8760682d587b41b570240aea058f4
4
- data.tar.gz: 96c6e87f6143f46feb7c38c136a16c00fc6e7188
3
+ metadata.gz: 1d10bdcfab72d5a2a4b398d240d1944189da6318
4
+ data.tar.gz: 86f9d556c90a96e7d5a5f2e57231a8c24b602203
5
5
  SHA512:
6
- metadata.gz: 4b487f260716def9aa4bba218fcbda154a214132eaaa0055cc13e6e90702f9008d2bc146fd0c00d86f4f77ac1c186c9a0f6d6898a276d2f9ad723c2f78f520b8
7
- data.tar.gz: 6a34a589b16e7ea377ed4e1ec751ffee171a0897d11e6087b3f1ac339aef2bcbcb9ca1f966eadd0bb21682f273640bda3f2fd8e91441cd141a8a3689e1eceaa1
6
+ metadata.gz: 037cb6dee3e51d86c6f8b29f16fa6eeb64c304debabb1f8381e45bdacb239bce77b03fa11aae869bede31ca8e8955c113fc09af09d047376f315b07564761e2a
7
+ data.tar.gz: 97a2e8aeb2734b1c3681bd196c72b82be999641ec6a7def2ff0f76f3573fb6ea87146bf69a518cd257532a1902d8ac0b7a29b380c1bad89a99a06af03522a635
@@ -1,5 +1,9 @@
1
1
  # rack-attack-rate-limit changelog
2
2
 
3
+ ## 1.1.0
4
+
5
+ * Add support for multiple throttles.
6
+
3
7
  ## 1.0.0
4
8
 
5
9
  * Support for Rails 4
data/README.md CHANGED
@@ -26,7 +26,7 @@ bundle
26
26
 
27
27
  ## Usage
28
28
 
29
- Rack::Attack::RateLimit expects a Rack::Attack throttle to be defined:
29
+ Rack::Attack::RateLimit expects at least one Rack::Attack throttle to be defined:
30
30
 
31
31
  ```ruby
32
32
  Rack::Attack.throttle('my_throttle') do |req|
@@ -34,20 +34,20 @@ Rack::Attack.throttle('my_throttle') do |req|
34
34
  end
35
35
  ```
36
36
 
37
- To include rate limit headers for this throttle, include the Rack::Attack::RateLimit middleware
37
+ To include rate limit headers for throttles, include the Rack::Attack::RateLimit middleware, and provide it with the names of the throttles you want to add rate limit headers for. A single throttle name can be provided as a string, while multiple throttle names must be provided as an array of strings.
38
38
 
39
39
  For Rails 3+:
40
40
 
41
41
  ```ruby
42
- config.middleware.use Rack::Attack::RateLimit, throttle: 'my_throttle'
42
+ config.middleware.use Rack::Attack::RateLimit, throttle: ['my_throttle', 'my_other_throttle']
43
43
  ```
44
44
 
45
- Currently, Rack::Attack::RateLimit can only be configured to return rate limit headers for a single throttle, whose name can be specified as an option.
46
-
47
45
  Rate limit headers are:
48
46
 
49
47
  * 'X-RateLimit-Limit' - The total number of requests allowed.
50
- * 'X-RateLimit-Remaining' - The number of remaining requests.
48
+ * 'X-RateLimit-Remaining' - The number of remaining requests.
49
+
50
+ If a request triggers multiple throttles, the gem will add headers for the throttle with the lowest number of remaining requests.
51
51
 
52
52
  ## Contributing
53
53
 
@@ -41,7 +41,7 @@ module Rack
41
41
  end
42
42
 
43
43
  def throttle
44
- options[:throttle] || ''
44
+ Array(options[:throttle]) || []
45
45
  end
46
46
 
47
47
  # Return hash of headers with Rate Limiting data
@@ -50,8 +50,9 @@ module Rack
50
50
  #
51
51
  # Returns hash
52
52
  def add_rate_limit_headers!(headers, env)
53
- headers['X-RateLimit-Limit'] = rate_limit_limit(env).to_s
54
- headers['X-RateLimit-Remaining'] = rate_limit_remaining(env).to_s
53
+ throttle_data = throttle_data_closest_to_limit(env)
54
+ headers['X-RateLimit-Limit'] = rate_limit_limit(throttle_data).to_s
55
+ headers['X-RateLimit-Remaining'] = rate_limit_remaining(throttle_data).to_s
55
56
  headers
56
57
  end
57
58
 
@@ -62,8 +63,8 @@ module Rack
62
63
  # env - Hash
63
64
  #
64
65
  # Returns Fixnum
65
- def rate_limit_limit(env)
66
- env[rack_attack_key][throttle][:limit]
66
+ def rate_limit_limit(throttle_data)
67
+ throttle_data[:limit]
67
68
  end
68
69
 
69
70
  # RateLimit remaining request from Rack::Attack
@@ -71,18 +72,41 @@ module Rack
71
72
  # env - Hash
72
73
  #
73
74
  # Returns Fixnum
74
- def rate_limit_remaining(env)
75
- rate_limit_limit(env) - env[rack_attack_key][throttle][:count]
75
+ def rate_limit_remaining(throttle_data)
76
+ rate_limit_limit(throttle_data) - throttle_data[:count]
76
77
  end
77
78
 
78
79
  # Rate Limit available method for Rack::Attack provider
79
- # Checks the key identifed by options[:namespace] under the rack.attak.throttle_data env hash key
80
+ # Checks that at least one of the keys provided by the user are in the rack.attack.throttle_data env hash key
80
81
  #
81
82
  # env - Hash
82
83
  #
83
84
  # Returns boolean
84
85
  def rate_limit_available?(env)
85
- env.key?(rack_attack_key) && env[rack_attack_key].key?(throttle)
86
+ env.key?(rack_attack_key) && (env[rack_attack_key].keys & throttle).any?
87
+ end
88
+
89
+ # Throttle Data of Interest
90
+ # Filters the rack.attack.throttle_data env hash key for the throttle names provided by the user
91
+ #
92
+ # env - Hash
93
+ #
94
+ # Returns Hash
95
+ def throttle_data_of_interest(env)
96
+ env[rack_attack_key].select { |k, _v| throttle.include?(k) }
97
+ end
98
+
99
+ # Throttle Data Closest to Limit
100
+ # Selects the hash in throttle_data_of_interest where the user is closest to the limit
101
+ #
102
+ # env - Hash
103
+ #
104
+ # Returns Hash
105
+ def throttle_data_closest_to_limit(env)
106
+ min_array = throttle_data_of_interest(env).min_by { |_k, v| v[:limit] - v[:count] }
107
+ # The min_by method returns an array of the form [key, value]
108
+ # We only need the values
109
+ min_array.last
86
110
  end
87
111
  end
88
112
  end
@@ -1,7 +1,7 @@
1
1
  module Rack
2
2
  class Attack
3
3
  class RateLimit
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
6
6
  end
7
7
  end
@@ -1,59 +1,101 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Rack::Attack::RateLimit do
4
-
4
+
5
5
  include Rack::Test::Methods
6
-
7
- let(:throttle) { 'rack_attack_throttle' }
8
-
6
+
7
+ let(:throttle_one) { 'foo_throttle' }
8
+ let(:throttle_two) { 'bar_throttle' }
9
+ let(:throttle_three) { 'baz_throttle' }
10
+
9
11
  let(:app) do
10
- use_throttle = throttle
11
- Rack::Builder.new {
12
+ use_throttle = throttle_one
13
+ Rack::Builder.new do
12
14
  use Rack::Attack::RateLimit, throttle: use_throttle
13
- run lambda { |env| [200, {}, 'Hello, World!'] }
14
- }.to_app
15
-
15
+ run ->(_env) { [200, {}, 'Hello, World!'] }
16
+ end.to_app
16
17
  end
17
-
18
-
18
+
19
19
  context 'Throttle data not present from Rack::Attack' do
20
-
20
+
21
21
  before(:each) do
22
22
  get '/'
23
- end
24
-
23
+ end
24
+
25
25
  it 'should not create RateLimit headers' do
26
26
  last_response.header.key?('X-RateLimit-Limit').should be false
27
27
  last_response.header.key?('X-RateLimit-Remaining').should be false
28
28
  end
29
-
29
+
30
30
  end
31
31
 
32
32
  context 'Throttle data present from Rack::Attack' do
33
-
34
- let(:request_limit) { (1..10000).to_a.sample }
35
- let(:request_count) { (1..(request_limit-10)).to_a.sample }
36
-
37
- let(:rack_attack_throttle_data) do
38
- { "#{throttle}" => { count: request_count, limit: request_limit } }
39
- end
40
-
41
33
  before(:each) do
42
- get "/", {}, { "#{Rack::Attack::RateLimit::RACK_ATTACK_KEY}" => rack_attack_throttle_data }
34
+ get '/', {}, "#{Rack::Attack::RateLimit::RACK_ATTACK_KEY}" => rack_attack_throttle_data
43
35
  end
44
-
45
- it 'should include RateLimit headers' do
46
- last_response.header.key?('X-RateLimit-Limit').should be true
47
- last_response.header.key?('X-RateLimit-Remaining').should be true
48
- end
49
-
50
- it 'should return correct rate limit in header' do
51
- last_response.header['X-RateLimit-Limit'].to_i.should eq request_limit
36
+
37
+ let(:request_limit) { (1..10_000).to_a.sample }
38
+ let(:request_count) { (1..(request_limit - 10)).to_a.sample }
39
+
40
+ context 'one throttle only' do
41
+
42
+ let(:rack_attack_throttle_data) do
43
+ { "#{throttle_one}" => { count: request_count, limit: request_limit } }
44
+ end
45
+
46
+ it 'should include RateLimit headers' do
47
+ last_response.header.key?('X-RateLimit-Limit').should be true
48
+ last_response.header.key?('X-RateLimit-Remaining').should be true
49
+ end
50
+
51
+ it 'should return correct rate limit in header' do
52
+ last_response.header['X-RateLimit-Limit'].to_i.should eq request_limit
53
+ end
54
+
55
+ it 'should return correct remaining calls in header' do
56
+ last_response.header['X-RateLimit-Remaining'].to_i.should eq(request_limit - request_count)
57
+ end
52
58
  end
53
-
54
- it 'should return correct remaining calls in header' do
55
- last_response.header['X-RateLimit-Remaining'].to_i.should eq (request_limit-request_count)
59
+
60
+ context 'multiple throttles' do
61
+
62
+ let(:app) do
63
+ use_throttle = [throttle_one, throttle_two, throttle_three]
64
+ Rack::Builder.new do
65
+ use Rack::Attack::RateLimit, throttle: use_throttle
66
+ run ->(_env) { [200, {}, 'Hello, World!'] }
67
+ end.to_app
68
+ end
69
+
70
+ let(:request_limits) { 3.times.map { (1..10_000).to_a.sample } }
71
+ let(:request_counts) { 3.times.map { |index| (1..(request_limits[index] - 10)).to_a.sample } }
72
+
73
+ let(:rack_attack_throttle_data) do
74
+ data = {}
75
+ [throttle_one, throttle_two, throttle_three].each_with_index do |thr, thr_index|
76
+ data["#{thr}"] = { count: request_counts[thr_index], limit: request_limits[thr_index] }
77
+ end
78
+ data
79
+ end
80
+ it 'should include RateLimit headers' do
81
+ last_response.header.key?('X-RateLimit-Limit').should be true
82
+ last_response.header.key?('X-RateLimit-Remaining').should be true
83
+ end
84
+
85
+ describe 'header values' do
86
+ let(:request_differences) do
87
+ request_limits.map.each_with_index { |limit, index| limit - request_counts[index] }
88
+ end
89
+ let(:min_index) { request_differences.each_with_index.min.last }
90
+
91
+ it 'should return correct rate limit' do
92
+ last_response.header['X-RateLimit-Limit'].to_i.should eq request_limits[min_index]
93
+ end
94
+
95
+ it 'should return correct remaining calls' do
96
+ last_response.header['X-RateLimit-Remaining'].to_i.should eq(request_differences[min_index])
97
+ end
98
+ end
56
99
  end
57
100
  end
58
-
59
- end
101
+ end
@@ -6,7 +6,7 @@ require 'rack/attack/rate-limit'
6
6
 
7
7
  RSpec::configure do |config|
8
8
 
9
- config.treat_symbols_as_metadata_keys_with_true_values = true
10
9
  config.formatter = :documentation
11
10
  config.tty = true
11
+ config.color = true
12
12
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-attack-rate-limit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Byck
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-23 00:00:00.000000000 Z
11
+ date: 2014-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack