throttling 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +6 -0
- data/README.md +81 -0
- data/lib/throttling/base.rb +24 -7
- data/lib/throttling/version.rb +1 -1
- data/spec/base_spec.rb +44 -0
- data/spec/fixtures/throttling.yml +14 -0
- data/throttling.gemspec +2 -2
- metadata +15 -15
- data/.travis.yml +0 -9
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -46,6 +46,34 @@ The basic structure of the file is:
|
|
46
46
|
limit: 10000
|
47
47
|
period: 86400
|
48
48
|
|
49
|
+
request_priority:
|
50
|
+
period: 86400
|
51
|
+
default_value: 25
|
52
|
+
values:
|
53
|
+
high_priority:
|
54
|
+
limit: 5
|
55
|
+
value: 10
|
56
|
+
medium_priority:
|
57
|
+
limit: 15
|
58
|
+
value: 15
|
59
|
+
low_priority:
|
60
|
+
limit: 100
|
61
|
+
value: 20
|
62
|
+
|
63
|
+
This example covers three different scenarios:
|
64
|
+
|
65
|
+
1. Single period. In this case only 20 actions will be allowed in a period of
|
66
|
+
one hour (3600 seconds).
|
67
|
+
|
68
|
+
2. Multiple periods. Action will be allowed to perform 300 times in 10 minutes,
|
69
|
+
1000 times an hour, and 10000 times a day.
|
70
|
+
|
71
|
+
3. This special case covers following scenario: based on the number of actions,
|
72
|
+
it returns a value, or default value when largest limit is reached. In this
|
73
|
+
case it will return 10, when there were 5 or less requests (including current one),
|
74
|
+
15 for up to 15 requests, 20 for up to 100 requests, and 25 when there were
|
75
|
+
more than 100 requests.
|
76
|
+
|
49
77
|
You can also specify limits as a Hash:
|
50
78
|
|
51
79
|
Throttling.limits = {
|
@@ -100,6 +128,59 @@ You can add more helpers like this:
|
|
100
128
|
end
|
101
129
|
end
|
102
130
|
|
131
|
+
## Use cases
|
132
|
+
|
133
|
+
### Limiting number of sign-ups
|
134
|
+
|
135
|
+
user_signup:
|
136
|
+
limit: 20
|
137
|
+
period: 3600
|
138
|
+
|
139
|
+
Limit the number of sign-ups to 20 per hour per IP address:
|
140
|
+
|
141
|
+
Throttling.for('user_signup').check_ip(request.remote_ip)
|
142
|
+
|
143
|
+
### Limiting number of document uploads
|
144
|
+
|
145
|
+
document_uploads:
|
146
|
+
minutely:
|
147
|
+
limit: 5
|
148
|
+
period: 600
|
149
|
+
hourly:
|
150
|
+
limit: 10
|
151
|
+
period: 3600
|
152
|
+
daily:
|
153
|
+
limit: 50
|
154
|
+
period: 86400
|
155
|
+
|
156
|
+
In this case user will be allowed to upload 5 documents in 10 minutes, 10 documents
|
157
|
+
in an hour, or 50 documents a day:
|
158
|
+
|
159
|
+
Throttling.for('document_uploads').check_user_id(current_user.id)
|
160
|
+
|
161
|
+
### Prioritizing uploads based on number of uploads
|
162
|
+
|
163
|
+
document_priorities:
|
164
|
+
period: 86400
|
165
|
+
default_value: 25
|
166
|
+
values:
|
167
|
+
high_priority:
|
168
|
+
limit: 5
|
169
|
+
value: 10
|
170
|
+
medium_priority:
|
171
|
+
limit: 15
|
172
|
+
value: 15
|
173
|
+
low_priority:
|
174
|
+
limit: 100
|
175
|
+
value: 20
|
176
|
+
|
177
|
+
All documents could be prioritized based on the number of uploads: if user uploads
|
178
|
+
less than 5 documents a day, they all will have priority 10. Next 10 documents
|
179
|
+
(first five keep their original priority) will receive priority 15. Documents
|
180
|
+
16 to 100 will get priority 20, and everything else will get priority 25.
|
181
|
+
|
182
|
+
Throttling.for('document_priorities').check_user_id(current_user.id)
|
183
|
+
|
103
184
|
## Contributing
|
104
185
|
|
105
186
|
1. Fork it
|
data/lib/throttling/base.rb
CHANGED
@@ -11,7 +11,10 @@ module Throttling
|
|
11
11
|
raise ArgumentError, "No Throttling.limits[#{action}] section found" unless limits
|
12
12
|
|
13
13
|
# Convert simple limits to a hash
|
14
|
-
if @limits[:
|
14
|
+
if @limits[:period]
|
15
|
+
if @limits[:values]
|
16
|
+
@limits[:values] = @limits[:values].sort_by { |name, params| params && params[:limit] }
|
17
|
+
end
|
15
18
|
@limits = [[ 'global', @limits ]]
|
16
19
|
else
|
17
20
|
@limits = @limits.sort_by { |name, params| params && params[:period] }
|
@@ -31,19 +34,33 @@ module Throttling
|
|
31
34
|
return true if !Throttling.enabled? || check_value.nil?
|
32
35
|
|
33
36
|
limits.each do |period_name, params|
|
34
|
-
raise ArgumentError, "Invalid or no 'period' parameter in the limits[#{period_name}] config" if params[:period].to_i < 1
|
35
|
-
raise ArgumentError, "Invalid or no 'limit' parameter in the limits[#{period_name}] config" if params[:limit].nil? || params[:limit].to_i < 0
|
36
|
-
|
37
37
|
period = params[:period].to_i
|
38
|
+
limit = params[:limit].to_i
|
39
|
+
values = params[:values]
|
40
|
+
|
41
|
+
raise ArgumentError, "Invalid or no 'period' parameter in the limits[#{period_name}] config" if period < 1
|
42
|
+
raise ArgumentError, "Invalid or no 'limit' parameter in the limits[#{period_name}] config" if limit < 1 && !values
|
43
|
+
|
38
44
|
key = hits_store_key(check_type, check_value, period_name, period)
|
39
45
|
|
40
46
|
# Retrieve current value
|
41
|
-
hits = Throttling.storage.fetch(key, :expires_in => hits_store_ttl(period), :raw => true) { '0' }
|
47
|
+
hits = Throttling.storage.fetch(key, :expires_in => hits_store_ttl(period), :raw => true) { '0' }.to_i
|
42
48
|
|
43
|
-
|
44
|
-
|
49
|
+
if values
|
50
|
+
value = params[:default_value] || false
|
51
|
+
values.each do |value_name, value_params|
|
52
|
+
if hits < value_params[:limit].to_i
|
53
|
+
value = value_params[:value] || value_params[:default_value] || false
|
54
|
+
break
|
55
|
+
end
|
56
|
+
end
|
57
|
+
else
|
58
|
+
# Over limit?
|
59
|
+
return false if hits > limit
|
60
|
+
end
|
45
61
|
|
46
62
|
Throttling.storage.increment(key) if auto_increment
|
63
|
+
return value if values
|
47
64
|
end
|
48
65
|
|
49
66
|
return true
|
data/lib/throttling/version.rb
CHANGED
data/spec/base_spec.rb
CHANGED
@@ -97,6 +97,50 @@ describe Throttling do
|
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
100
|
+
context 'with values specified' do
|
101
|
+
before do
|
102
|
+
Throttling.limits_config = File.expand_path('../fixtures/throttling.yml', __FILE__)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should return value when limit is not reached' do
|
106
|
+
@storage.should_receive(:fetch).and_return(0)
|
107
|
+
Throttling.for('request_priority').check_ip('127.0.0.1').should == 10
|
108
|
+
@storage.should_receive(:fetch).and_return(4)
|
109
|
+
Throttling.for('request_priority').check_ip('127.0.0.1').should == 10
|
110
|
+
|
111
|
+
@storage.should_receive(:fetch).and_return(5)
|
112
|
+
Throttling.for('request_priority').check_ip('127.0.0.1').should == 15
|
113
|
+
@storage.should_receive(:fetch).and_return(14)
|
114
|
+
Throttling.for('request_priority').check_ip('127.0.0.1').should == 15
|
115
|
+
|
116
|
+
@storage.should_receive(:fetch).and_return(15)
|
117
|
+
Throttling.for('request_priority').check_ip('127.0.0.1').should == 20
|
118
|
+
@storage.should_receive(:fetch).and_return(99)
|
119
|
+
Throttling.for('request_priority').check_ip('127.0.0.1').should == 20
|
120
|
+
|
121
|
+
@storage.should_receive(:fetch).and_return(100)
|
122
|
+
Throttling.for('request_priority').check_ip('127.0.0.1').should == 25
|
123
|
+
@storage.should_receive(:fetch).and_return(1000)
|
124
|
+
Throttling.for('request_priority').check_ip('127.0.0.1').should == 25
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should increase hit counter' do
|
128
|
+
@storage.should_receive(:fetch).and_return(4)
|
129
|
+
@storage.should_receive(:increment)
|
130
|
+
Throttling.for('request_priority').check_ip('127.0.0.1')
|
131
|
+
|
132
|
+
@storage.should_receive(:fetch).and_return(1000)
|
133
|
+
@storage.should_receive(:increment)
|
134
|
+
Throttling.for('request_priority').check_ip('127.0.0.1')
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should return false when highest limit reached' do
|
138
|
+
Throttling.limits['request_priority'].delete('default_value')
|
139
|
+
@storage.should_receive(:fetch).and_return(1000)
|
140
|
+
Throttling.for('request_priority').check_ip('127.0.0.1').should be_false
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
100
144
|
context do
|
101
145
|
before do
|
102
146
|
Throttling.limits = { 'foo' => {'limit' => 5, 'period' => 86400} }
|
@@ -12,3 +12,17 @@ search_requests:
|
|
12
12
|
daily:
|
13
13
|
limit: 10000
|
14
14
|
period: 86400
|
15
|
+
|
16
|
+
request_priority:
|
17
|
+
period: 86400
|
18
|
+
default_value: 25
|
19
|
+
values:
|
20
|
+
high_priority:
|
21
|
+
limit: 5
|
22
|
+
value: 10
|
23
|
+
medium_priority:
|
24
|
+
limit: 15
|
25
|
+
value: 15
|
26
|
+
low_priority:
|
27
|
+
limit: 100
|
28
|
+
value: 20
|
data/throttling.gemspec
CHANGED
@@ -15,9 +15,9 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.add_development_dependency 'rb-fsevent'
|
16
16
|
gem.add_development_dependency 'growl'
|
17
17
|
|
18
|
-
gem.files = `git ls-files`.split($\)
|
18
|
+
gem.files = `git ls-files`.split($\) - %w[.travis.yml]
|
19
19
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
20
|
-
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) + %w[Guardfile]
|
21
21
|
gem.name = "throttling"
|
22
22
|
gem.require_paths = ["lib"]
|
23
23
|
gem.version = Throttling::VERSION
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: throttling
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,11 +10,11 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-04-
|
13
|
+
date: 2012-04-13 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rake
|
17
|
-
requirement: &
|
17
|
+
requirement: &70192665062020 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: '0'
|
23
23
|
type: :development
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *70192665062020
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: rspec
|
28
|
-
requirement: &
|
28
|
+
requirement: &70192665061600 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *70192665061600
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: timecop
|
39
|
-
requirement: &
|
39
|
+
requirement: &70192665061180 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ! '>='
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: '0'
|
45
45
|
type: :development
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *70192665061180
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: guard-rspec
|
50
|
-
requirement: &
|
50
|
+
requirement: &70192665060760 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ! '>='
|
@@ -55,10 +55,10 @@ dependencies:
|
|
55
55
|
version: '0'
|
56
56
|
type: :development
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
58
|
+
version_requirements: *70192665060760
|
59
59
|
- !ruby/object:Gem::Dependency
|
60
60
|
name: rb-fsevent
|
61
|
-
requirement: &
|
61
|
+
requirement: &70192665060340 !ruby/object:Gem::Requirement
|
62
62
|
none: false
|
63
63
|
requirements:
|
64
64
|
- - ! '>='
|
@@ -66,10 +66,10 @@ dependencies:
|
|
66
66
|
version: '0'
|
67
67
|
type: :development
|
68
68
|
prerelease: false
|
69
|
-
version_requirements: *
|
69
|
+
version_requirements: *70192665060340
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: growl
|
72
|
-
requirement: &
|
72
|
+
requirement: &70192665059920 !ruby/object:Gem::Requirement
|
73
73
|
none: false
|
74
74
|
requirements:
|
75
75
|
- - ! '>='
|
@@ -77,7 +77,7 @@ dependencies:
|
|
77
77
|
version: '0'
|
78
78
|
type: :development
|
79
79
|
prerelease: false
|
80
|
-
version_requirements: *
|
80
|
+
version_requirements: *70192665059920
|
81
81
|
description: Throttling gem provides basic, but very powerful way to throttle various
|
82
82
|
user actions in your application
|
83
83
|
email:
|
@@ -87,7 +87,6 @@ extensions: []
|
|
87
87
|
extra_rdoc_files: []
|
88
88
|
files:
|
89
89
|
- .gitignore
|
90
|
-
- .travis.yml
|
91
90
|
- CHANGELOG.md
|
92
91
|
- Gemfile
|
93
92
|
- Guardfile
|
@@ -132,3 +131,4 @@ test_files:
|
|
132
131
|
- spec/fixtures/throttling.yml
|
133
132
|
- spec/spec_helper.rb
|
134
133
|
- spec/throttling_spec.rb
|
134
|
+
- Guardfile
|