viximo-rack-throttle 0.4.0 → 0.5.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/README +8 -21
- data/lib/rack/throttle/daily.rb +5 -1
- data/lib/rack/throttle/hourly.rb +4 -0
- data/lib/rack/throttle/interval.rb +4 -4
- data/lib/rack/throttle/limiter.rb +7 -3
- data/lib/rack/throttle/minute.rb +4 -0
- data/lib/rack/throttle/version.rb +1 -1
- metadata +80 -101
data/README
CHANGED
@@ -139,32 +139,18 @@ override the `#client_identifier` method.
|
|
139
139
|
|
140
140
|
HTTP Response Codes and Headers
|
141
141
|
-------------------------------
|
142
|
+
### 429 Too Many Requests
|
143
|
+
In the past various status codes has been used to indicate over-use.
|
142
144
|
|
143
|
-
|
145
|
+
In 2012, the IETF standardized on a new response status 429 (Too Many Requests)
|
146
|
+
[RFC6585].
|
144
147
|
|
145
148
|
When a client exceeds their rate limit, `Rack::Throttle` by default returns
|
146
|
-
a
|
149
|
+
a 429 (Too Many Requests) header with an associated "Rate Limit Exceeded" message
|
147
150
|
in the response body.
|
148
151
|
|
149
|
-
|
150
|
-
|
151
|
-
This indicates an error on the client's part in exceeding the rate limits
|
152
|
-
outlined in the acceptable use policy for the site, service, or API.
|
153
|
-
|
154
|
-
### 503 Service Unavailable (Rate Limit Exceeded)
|
155
|
-
|
156
|
-
However, there exists a widespread practice of instead returning a "503
|
157
|
-
Service Unavailable" response when a client exceeds the set rate limits.
|
158
|
-
This is technically dubious because it indicates an error on the server's
|
159
|
-
part, which is certainly not the case with rate limiting - it was the client
|
160
|
-
that committed the oops, not the server.
|
161
|
-
|
162
|
-
An HTTP 503 response would be correct in situations where the server was
|
163
|
-
genuinely overloaded and couldn't handle more requests, but for rate
|
164
|
-
limiting an HTTP 403 response is more appropriate. Nonetheless, if you think
|
165
|
-
otherwise, `Rack::Throttle` does allow you to override the returned HTTP
|
166
|
-
status code by passing in a `:code => 503` option when constructing a
|
167
|
-
`Rack::Throttle::Limiter` instance.
|
152
|
+
The status code can be overridden by passing in `:code => 403` option when
|
153
|
+
constructing a `Rack::Throttle::Limiter` instance.
|
168
154
|
|
169
155
|
Documentation
|
170
156
|
-------------
|
@@ -221,3 +207,4 @@ information, see <http://unlicense.org/> or the accompanying UNLICENSE file.
|
|
221
207
|
[redis]: http://rubygems.org/gems/redis
|
222
208
|
[Heroku]: http://heroku.com/
|
223
209
|
[Heroku memcache]: http://docs.heroku.com/memcache
|
210
|
+
[RFC6585]: http://tools.ietf.org/html/rfc6585
|
data/lib/rack/throttle/daily.rb
CHANGED
@@ -32,6 +32,10 @@ module Rack; module Throttle
|
|
32
32
|
|
33
33
|
alias_method :max_per_window, :max_per_day
|
34
34
|
|
35
|
+
def retry_after
|
36
|
+
"86400" # simplification, because the strategy is not sliding
|
37
|
+
end
|
38
|
+
|
35
39
|
protected
|
36
40
|
|
37
41
|
##
|
@@ -41,4 +45,4 @@ module Rack; module Throttle
|
|
41
45
|
[super, Time.now.strftime('%Y-%m-%d')].join(':')
|
42
46
|
end
|
43
47
|
end
|
44
|
-
end; end
|
48
|
+
end; end
|
data/lib/rack/throttle/hourly.rb
CHANGED
@@ -27,11 +27,11 @@ module Rack; module Throttle
|
|
27
27
|
# @param [Rack::Request] request
|
28
28
|
# @return [Boolean]
|
29
29
|
def allowed?(request)
|
30
|
-
|
31
|
-
|
32
|
-
allowed = !
|
30
|
+
start_time = request_start_time(request)
|
31
|
+
last_call_time = cache_get(key = cache_key(request)) rescue nil
|
32
|
+
allowed = !last_call_time || (dt = start_time - last_call_time.to_f) >= minimum_interval
|
33
33
|
begin
|
34
|
-
cache_set(key,
|
34
|
+
cache_set(key, start_time)
|
35
35
|
allowed
|
36
36
|
rescue => e
|
37
37
|
# If an error occurred while trying to update the timestamp stored
|
@@ -10,16 +10,20 @@ module Rack; module Throttle
|
|
10
10
|
# end
|
11
11
|
#
|
12
12
|
class Limiter
|
13
|
+
|
13
14
|
attr_reader :app
|
14
15
|
attr_reader :options
|
15
|
-
|
16
|
+
|
17
|
+
CODE = 429 # http://tools.ietf.org/html/rfc6585
|
18
|
+
MESSAGE = "Rate Limit Exceeded"
|
19
|
+
|
16
20
|
##
|
17
21
|
# @param [#call] app
|
18
22
|
# @param [Hash{Symbol => Object}] options
|
19
23
|
# @option options [String] :cache (Hash.new)
|
20
24
|
# @option options [String] :key (nil)
|
21
25
|
# @option options [String] :key_prefix (nil)
|
22
|
-
# @option options [Integer] :code (
|
26
|
+
# @option options [Integer] :code (429)
|
23
27
|
# @option options [String] :message ("Rate Limit Exceeded")
|
24
28
|
def initialize(app, options = {})
|
25
29
|
@app, @options = app, options
|
@@ -177,7 +181,7 @@ module Rack; module Throttle
|
|
177
181
|
def rate_limit_exceeded(request)
|
178
182
|
options[:rate_limit_exceeded_callback].call(request) if options[:rate_limit_exceeded_callback]
|
179
183
|
headers = respond_to?(:retry_after) ? {'Retry-After' => retry_after.to_f.ceil.to_s} : {}
|
180
|
-
http_error(options[:code] ||
|
184
|
+
http_error(options[:code] || CODE, options[:message] || MESSAGE, headers)
|
181
185
|
end
|
182
186
|
|
183
187
|
##
|
data/lib/rack/throttle/minute.rb
CHANGED
@@ -28,6 +28,10 @@ module Rack; module Throttle
|
|
28
28
|
def max_per_minute
|
29
29
|
@max_per_hour ||= options[:max_per_minute] || options[:max] || 60
|
30
30
|
end
|
31
|
+
|
32
|
+
def retry_after
|
33
|
+
"60" # simplification, because the strategy is not sliding
|
34
|
+
end
|
31
35
|
|
32
36
|
alias_method :max_per_window, :max_per_minute
|
33
37
|
|
metadata
CHANGED
@@ -1,113 +1,103 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: viximo-rack-throttle
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 4
|
9
|
-
- 0
|
10
|
-
version: 0.4.0
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Arto Bendiken
|
14
9
|
- Brendon Murphy
|
15
10
|
autorequire:
|
16
11
|
bindir: bin
|
17
12
|
cert_chain: []
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
dependencies:
|
22
|
-
- !ruby/object:Gem::Dependency
|
13
|
+
date: 2012-05-14 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
23
16
|
name: rack-test
|
24
|
-
|
25
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
26
18
|
none: false
|
27
|
-
requirements:
|
28
|
-
- -
|
29
|
-
- !ruby/object:Gem::Version
|
30
|
-
hash: 13
|
31
|
-
segments:
|
32
|
-
- 0
|
33
|
-
- 5
|
34
|
-
- 3
|
19
|
+
requirements:
|
20
|
+
- - '='
|
21
|
+
- !ruby/object:Gem::Version
|
35
22
|
version: 0.5.3
|
36
23
|
type: :development
|
37
|
-
version_requirements: *id001
|
38
|
-
- !ruby/object:Gem::Dependency
|
39
|
-
name: rspec
|
40
24
|
prerelease: false
|
41
|
-
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - '='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 0.5.3
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: rspec
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
42
34
|
none: false
|
43
|
-
requirements:
|
44
|
-
- -
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
hash: 27
|
47
|
-
segments:
|
48
|
-
- 1
|
49
|
-
- 3
|
50
|
-
- 0
|
35
|
+
requirements:
|
36
|
+
- - '='
|
37
|
+
- !ruby/object:Gem::Version
|
51
38
|
version: 1.3.0
|
52
39
|
type: :development
|
53
|
-
version_requirements: *id002
|
54
|
-
- !ruby/object:Gem::Dependency
|
55
|
-
name: yard
|
56
40
|
prerelease: false
|
57
|
-
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
42
|
none: false
|
59
|
-
requirements:
|
60
|
-
- -
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
43
|
+
requirements:
|
44
|
+
- - '='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.3.0
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: yard
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
67
54
|
version: 0.5.5
|
68
55
|
type: :development
|
69
|
-
version_requirements: *id003
|
70
|
-
- !ruby/object:Gem::Dependency
|
71
|
-
name: timecop
|
72
56
|
prerelease: false
|
73
|
-
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 0.5.5
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: timecop
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
74
66
|
none: false
|
75
|
-
requirements:
|
76
|
-
- -
|
77
|
-
- !ruby/object:Gem::Version
|
78
|
-
hash: 27
|
79
|
-
segments:
|
80
|
-
- 0
|
81
|
-
- 3
|
82
|
-
- 4
|
67
|
+
requirements:
|
68
|
+
- - '='
|
69
|
+
- !ruby/object:Gem::Version
|
83
70
|
version: 0.3.4
|
84
71
|
type: :development
|
85
|
-
version_requirements: *id004
|
86
|
-
- !ruby/object:Gem::Dependency
|
87
|
-
name: rack
|
88
72
|
prerelease: false
|
89
|
-
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - '='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: 0.3.4
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: rack
|
81
|
+
requirement: !ruby/object:Gem::Requirement
|
90
82
|
none: false
|
91
|
-
requirements:
|
92
|
-
- -
|
93
|
-
- !ruby/object:Gem::Version
|
94
|
-
hash: 23
|
95
|
-
segments:
|
96
|
-
- 1
|
97
|
-
- 0
|
98
|
-
- 0
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
99
86
|
version: 1.0.0
|
100
87
|
type: :runtime
|
101
|
-
|
88
|
+
prerelease: false
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 1.0.0
|
102
95
|
description: Rack middleware for rate-limiting incoming HTTP requests.
|
103
96
|
email: arto.bendiken@gmail.com
|
104
97
|
executables: []
|
105
|
-
|
106
98
|
extensions: []
|
107
|
-
|
108
99
|
extra_rdoc_files: []
|
109
|
-
|
110
|
-
files:
|
100
|
+
files:
|
111
101
|
- AUTHORS
|
112
102
|
- README
|
113
103
|
- UNLICENSE
|
@@ -119,41 +109,30 @@ files:
|
|
119
109
|
- lib/rack/throttle/time_window.rb
|
120
110
|
- lib/rack/throttle/version.rb
|
121
111
|
- lib/rack/throttle.rb
|
122
|
-
has_rdoc: false
|
123
112
|
homepage: http://github.com/Viximo/rack-throttle
|
124
|
-
licenses:
|
113
|
+
licenses:
|
125
114
|
- Public Domain
|
126
115
|
post_install_message:
|
127
116
|
rdoc_options: []
|
128
|
-
|
129
|
-
require_paths:
|
117
|
+
require_paths:
|
130
118
|
- lib
|
131
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
120
|
none: false
|
133
|
-
requirements:
|
134
|
-
- -
|
135
|
-
- !ruby/object:Gem::Version
|
136
|
-
hash: 51
|
137
|
-
segments:
|
138
|
-
- 1
|
139
|
-
- 8
|
140
|
-
- 2
|
121
|
+
requirements:
|
122
|
+
- - ! '>='
|
123
|
+
- !ruby/object:Gem::Version
|
141
124
|
version: 1.8.2
|
142
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
126
|
none: false
|
144
|
-
requirements:
|
145
|
-
- -
|
146
|
-
- !ruby/object:Gem::Version
|
147
|
-
|
148
|
-
segments:
|
149
|
-
- 0
|
150
|
-
version: "0"
|
127
|
+
requirements:
|
128
|
+
- - ! '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
151
131
|
requirements: []
|
152
|
-
|
153
132
|
rubyforge_project: datagraph
|
154
|
-
rubygems_version: 1.
|
133
|
+
rubygems_version: 1.8.21
|
155
134
|
signing_key:
|
156
135
|
specification_version: 3
|
157
136
|
summary: HTTP request rate limiter for Rack applications.
|
158
137
|
test_files: []
|
159
|
-
|
138
|
+
has_rdoc: false
|