rack-throttle 0.3.0 → 0.4.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.
- checksums.yaml +15 -0
- data/AUTHORS +1 -2
- data/README +61 -31
- data/VERSION +1 -1
- data/lib/rack/throttle.rb +1 -0
- data/lib/rack/throttle/limiter.rb +11 -9
- data/lib/rack/throttle/minute.rb +43 -0
- data/lib/rack/throttle/time_window.rb +2 -1
- metadata +84 -88
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MDNkYTliNDNmN2ExYWQ1OWY3NjkyZDNkZTk0OTQ0ZGZiODk1ZWFiNw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ODdlYzYyOGQwYzY2OGZiNDgyNzc4NWY3OTEzNDc2ZDFlYzZhZjE1ZA==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NjUxNmJmMTBlNWU1MDk3ZjM2Zjc1YmEzNWJlYTYyYzBlMWQxNDFiOTgzMGMx
|
10
|
+
YjM1OGQ5Mzk1ZjRlZDQxYzQxZjNiZDdlN2IzMjM2NDU3ZGRjZWUwZGJiYmQw
|
11
|
+
ZDQ3NGYxNDNhZGJhZmQ3MzcyZGU4ZDgwYTg5OTE1NmNmYzZhMDg=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
OTRmNWNlN2VkYWU0ODAyMTAzMmRjMmE0ZTllMmE3OTliODA5MmFkMTQxOGRm
|
14
|
+
YjUzOTc0MTNlZjliZmZhN2Q2NWMyNzliYTk1MGMyMTJhYTliMDE5M2I1ZDdh
|
15
|
+
NTRmMWNlYzEyMDhiOWNhYzY0YmFhZGFiZWQxNjI0ZmRjMWE3Nzk=
|
data/AUTHORS
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
* Arto Bendiken <arto
|
2
|
-
* Brendon Murphy <disposable.20.xternal@spamourmet.com>
|
1
|
+
* Arto Bendiken <arto@bendiken.net>
|
data/README
CHANGED
@@ -3,18 +3,18 @@ HTTP Request Rate Limiter for Rack Applications
|
|
3
3
|
|
4
4
|
This is [Rack][] middleware that provides logic for rate-limiting incoming
|
5
5
|
HTTP requests to Rack applications. You can use `Rack::Throttle` with any
|
6
|
-
Ruby web framework based on Rack, including with Ruby on Rails
|
6
|
+
Ruby web framework based on Rack, including with Ruby on Rails and with
|
7
7
|
Sinatra.
|
8
8
|
|
9
|
-
* <
|
9
|
+
* <https://github.com/bendiken/rack-throttle>
|
10
10
|
|
11
11
|
Features
|
12
12
|
--------
|
13
13
|
|
14
14
|
* Throttles a Rack application by enforcing a minimum time interval between
|
15
15
|
subsequent HTTP requests from a particular client, as well as by defining
|
16
|
-
a maximum number of allowed HTTP requests per a given time period (
|
17
|
-
or daily).
|
16
|
+
a maximum number of allowed HTTP requests per a given time period (per minute,
|
17
|
+
hourly, or daily).
|
18
18
|
* Compatible with any Rack application and any Rack-based framework.
|
19
19
|
* Stores rate-limiting counters in any key/value store implementation that
|
20
20
|
responds to `#[]`/`#[]=` (like Ruby's hashes) or to `#get`/`#set` (like
|
@@ -28,18 +28,42 @@ Features
|
|
28
28
|
Examples
|
29
29
|
--------
|
30
30
|
|
31
|
-
### Adding throttling to a
|
31
|
+
### Adding throttling to a Rails application
|
32
32
|
|
33
|
+
# config/application.rb
|
33
34
|
require 'rack/throttle'
|
35
|
+
|
36
|
+
class Application < Rails::Application
|
37
|
+
config.middleware.use Rack::Throttle::Interval
|
38
|
+
end
|
34
39
|
|
40
|
+
### Adding throttling to a Sinatra application
|
41
|
+
|
42
|
+
#!/usr/bin/env ruby -rubygems
|
43
|
+
require 'sinatra'
|
44
|
+
require 'rack/throttle'
|
45
|
+
|
35
46
|
use Rack::Throttle::Interval
|
47
|
+
|
48
|
+
get('/hello') { "Hello, world!\n" }
|
36
49
|
|
50
|
+
### Adding throttling to a Rackup application
|
51
|
+
|
52
|
+
#!/usr/bin/env rackup
|
53
|
+
require 'rack/throttle'
|
54
|
+
|
55
|
+
use Rack::Throttle::Interval
|
56
|
+
|
37
57
|
run lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, world!\n"] }
|
38
58
|
|
39
59
|
### Enforcing a minimum 3-second interval between requests
|
40
60
|
|
41
61
|
use Rack::Throttle::Interval, :min => 3.0
|
42
62
|
|
63
|
+
### Allowing a maximum of 60 requests per minute
|
64
|
+
|
65
|
+
use Rack::Throttle::Minute, :max => 60
|
66
|
+
|
43
67
|
### Allowing a maximum of 100 requests per hour
|
44
68
|
|
45
69
|
use Rack::Throttle::Hourly, :max => 100
|
@@ -52,6 +76,7 @@ Examples
|
|
52
76
|
|
53
77
|
use Rack::Throttle::Daily, :max => 1000 # requests
|
54
78
|
use Rack::Throttle::Hourly, :max => 100 # requests
|
79
|
+
use Rack::Throttle::Minute, :max => 60 # requests
|
55
80
|
use Rack::Throttle::Interval, :min => 3.0 # seconds
|
56
81
|
|
57
82
|
### Storing the rate-limiting counters in a GDBM database
|
@@ -136,16 +161,6 @@ otherwise, `Rack::Throttle` does allow you to override the returned HTTP
|
|
136
161
|
status code by passing in a `:code => 503` option when constructing a
|
137
162
|
`Rack::Throttle::Limiter` instance.
|
138
163
|
|
139
|
-
Documentation
|
140
|
-
-------------
|
141
|
-
|
142
|
-
<http://datagraph.rubyforge.org/rack-throttle/>
|
143
|
-
|
144
|
-
* {Rack::Throttle}
|
145
|
-
* {Rack::Throttle::Interval}
|
146
|
-
* {Rack::Throttle::Daily}
|
147
|
-
* {Rack::Throttle::Hourly}
|
148
|
-
|
149
164
|
Dependencies
|
150
165
|
------------
|
151
166
|
|
@@ -154,34 +169,47 @@ Dependencies
|
|
154
169
|
Installation
|
155
170
|
------------
|
156
171
|
|
157
|
-
The recommended installation method is via RubyGems.
|
158
|
-
official release, do:
|
172
|
+
The recommended installation method is via [RubyGems](http://rubygems.org/).
|
173
|
+
To install the latest official release of the gem, do:
|
159
174
|
|
160
175
|
% [sudo] gem install rack-throttle
|
161
176
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
To get a local working copy of the development repository, do:
|
177
|
+
Authors
|
178
|
+
-------
|
166
179
|
|
167
|
-
|
180
|
+
* [Arto Bendiken](https://gratipay.com/bendiken) - <http://ar.to/>
|
168
181
|
|
169
|
-
|
170
|
-
|
182
|
+
Contributors
|
183
|
+
------------
|
171
184
|
|
172
|
-
|
185
|
+
* [Brendon Murphy](https://github.com/bemurphy)
|
186
|
+
* [Hendrik Kleinwaechter](https://github.com/hendricius)
|
187
|
+
* [Karel Minarik](https://github.com/karmi)
|
188
|
+
* [Keita Urashima](https://github.com/ursm)
|
189
|
+
* [Leonid Beder](https://github.com/lbeder)
|
190
|
+
* [TJ Singleton](https://github.com/tjsingleton)
|
191
|
+
* [Winfield Peterson](https://github.com/wpeterson)
|
173
192
|
|
174
|
-
|
175
|
-
|
193
|
+
Contributing
|
194
|
+
------------
|
176
195
|
|
177
|
-
*
|
178
|
-
*
|
196
|
+
* Do your best to adhere to the existing coding conventions and idioms.
|
197
|
+
* Don't use hard tabs, and don't leave trailing whitespace on any line.
|
198
|
+
Before committing, run `git diff --check` to make sure of this.
|
199
|
+
* Do document every method you add using [YARD][] annotations. Read the
|
200
|
+
[tutorial][YARD-GS] or just look at the existing code for examples.
|
201
|
+
* Don't touch the gemspec or `VERSION` files. If you need to change them,
|
202
|
+
do so on your private branch only.
|
203
|
+
* Do feel free to add yourself to the `CREDITS` file and the
|
204
|
+
corresponding list in the the `README`. Alphabetical order applies.
|
205
|
+
* Don't touch the `AUTHORS` file. If your contributions are significant
|
206
|
+
enough, be assured we will eventually add you in there.
|
179
207
|
|
180
208
|
License
|
181
209
|
-------
|
182
210
|
|
183
|
-
|
184
|
-
|
211
|
+
This is free and unencumbered public domain software. For more information,
|
212
|
+
see <http://unlicense.org/> or the accompanying `UNLICENSE` file.
|
185
213
|
|
186
214
|
[Rack]: http://rack.rubyforge.org/
|
187
215
|
[gdbm]: http://ruby-doc.org/stdlib/libdoc/gdbm/rdoc/classes/GDBM.html
|
@@ -191,3 +219,5 @@ information, see <http://unlicense.org/> or the accompanying UNLICENSE file.
|
|
191
219
|
[redis]: http://rubygems.org/gems/redis
|
192
220
|
[Heroku]: http://heroku.com/
|
193
221
|
[Heroku memcache]: http://docs.heroku.com/memcache
|
222
|
+
[YARD]: http://yardoc.org/
|
223
|
+
[YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/lib/rack/throttle.rb
CHANGED
@@ -31,7 +31,7 @@ module Rack; module Throttle
|
|
31
31
|
# @see http://rack.rubyforge.org/doc/SPEC.html
|
32
32
|
def call(env)
|
33
33
|
request = Rack::Request.new(env)
|
34
|
-
allowed?(request) ? app.call(env) : rate_limit_exceeded
|
34
|
+
allowed?(request) ? app.call(env) : rate_limit_exceeded(request)
|
35
35
|
end
|
36
36
|
|
37
37
|
##
|
@@ -124,7 +124,7 @@ module Rack; module Throttle
|
|
124
124
|
when cache.respond_to?(:[]=)
|
125
125
|
begin
|
126
126
|
cache[key] = value
|
127
|
-
rescue TypeError
|
127
|
+
rescue TypeError
|
128
128
|
# GDBM throws a "TypeError: can't convert Float into String"
|
129
129
|
# exception when trying to store a Float. On the other hand, we
|
130
130
|
# don't want to unnecessarily coerce the value to a String for
|
@@ -162,11 +162,12 @@ module Rack; module Throttle
|
|
162
162
|
# @param [Rack::Request] request
|
163
163
|
# @return [Float]
|
164
164
|
def request_start_time(request)
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
165
|
+
# Check whether HTTP_X_REQUEST_START or HTTP_X_QUEUE_START exist and parse its value (for
|
166
|
+
# example, when having nginx in your stack, it's going to be in the "t=\d+" format).
|
167
|
+
if val = (request.env['HTTP_X_REQUEST_START'] || request.env['HTTP_X_QUEUE_START'])
|
168
|
+
val[/(?:^t=)?(\d+)/, 1].to_f / 1000
|
169
|
+
else
|
170
|
+
Time.now.to_f
|
170
171
|
end
|
171
172
|
end
|
172
173
|
|
@@ -174,7 +175,8 @@ module Rack; module Throttle
|
|
174
175
|
# Outputs a `Rate Limit Exceeded` error.
|
175
176
|
#
|
176
177
|
# @return [Array(Integer, Hash, #each)]
|
177
|
-
def rate_limit_exceeded
|
178
|
+
def rate_limit_exceeded(request)
|
179
|
+
options[:rate_limit_exceeded_callback].call(request) if options[:rate_limit_exceeded_callback]
|
178
180
|
headers = respond_to?(:retry_after) ? {'Retry-After' => retry_after.to_f.ceil.to_s} : {}
|
179
181
|
http_error(options[:code] || 403, options[:message] || 'Rate Limit Exceeded', headers)
|
180
182
|
end
|
@@ -188,7 +190,7 @@ module Rack; module Throttle
|
|
188
190
|
# @return [Array(Integer, Hash, #each)]
|
189
191
|
def http_error(code, message = nil, headers = {})
|
190
192
|
[code, {'Content-Type' => 'text/plain; charset=utf-8'}.merge(headers),
|
191
|
-
http_status(code)
|
193
|
+
[http_status(code), (message.nil? ? "\n" : " (#{message})\n")]]
|
192
194
|
end
|
193
195
|
|
194
196
|
##
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Rack; module Throttle
|
2
|
+
##
|
3
|
+
# This rate limiter strategy throttles the application by defining a
|
4
|
+
# maximum number of allowed HTTP requests per minute (by default, 60
|
5
|
+
# requests per minute, which works out to an average of 1 request per
|
6
|
+
# second).
|
7
|
+
#
|
8
|
+
# Note that this strategy doesn't use a sliding time window, but rather
|
9
|
+
# tracks requests per distinct minute. This means that the throttling
|
10
|
+
# counter is reset every minute.
|
11
|
+
#
|
12
|
+
# @example Allowing up to 60 requests/minute
|
13
|
+
# use Rack::Throttle::Minute
|
14
|
+
#
|
15
|
+
# @example Allowing up to 100 requests per hour
|
16
|
+
# use Rack::Throttle::Minute, :max => 100
|
17
|
+
#
|
18
|
+
class Minute < TimeWindow
|
19
|
+
##
|
20
|
+
# @param [#call] app
|
21
|
+
# @param [Hash{Symbol => Object}] options
|
22
|
+
# @option options [Integer] :max (60)
|
23
|
+
def initialize(app, options = {})
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
def max_per_minute
|
29
|
+
@max_per_hour ||= options[:max_per_minute] || options[:max] || 60
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method :max_per_window, :max_per_minute
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
##
|
37
|
+
# @param [Rack::Request] request
|
38
|
+
# @return [String]
|
39
|
+
def cache_key(request)
|
40
|
+
[super, Time.now.strftime('%Y-%m-%dT%H:%M')].join(':')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end; end
|
@@ -8,8 +8,9 @@ module Rack; module Throttle
|
|
8
8
|
# @param [Rack::Request] request
|
9
9
|
# @return [Boolean]
|
10
10
|
def allowed?(request)
|
11
|
+
return true if whitelisted?(request)
|
11
12
|
count = cache_get(key = cache_key(request)).to_i + 1 rescue 1
|
12
|
-
allowed = count <= max_per_window
|
13
|
+
allowed = count <= max_per_window.to_i
|
13
14
|
begin
|
14
15
|
cache_set(key, count)
|
15
16
|
allowed
|
metadata
CHANGED
@@ -1,88 +1,91 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-throttle
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
segments:
|
6
|
-
- 0
|
7
|
-
- 3
|
8
|
-
- 0
|
9
|
-
version: 0.3.0
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
10
5
|
platform: ruby
|
11
|
-
authors:
|
6
|
+
authors:
|
12
7
|
- Arto Bendiken
|
13
|
-
- Brendon Murphy
|
14
8
|
autorequire:
|
15
9
|
bindir: bin
|
16
10
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
dependencies:
|
21
|
-
- !ruby/object:Gem::Dependency
|
11
|
+
date: 2014-11-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
22
14
|
name: rack-test
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
segments:
|
29
|
-
- 0
|
30
|
-
- 5
|
31
|
-
- 3
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
32
19
|
version: 0.5.3
|
33
20
|
type: :development
|
34
|
-
version_requirements: *id001
|
35
|
-
- !ruby/object:Gem::Dependency
|
36
|
-
name: rspec
|
37
21
|
prerelease: false
|
38
|
-
|
39
|
-
requirements:
|
40
|
-
- -
|
41
|
-
- !ruby/object:Gem::Version
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.5.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
46
33
|
version: 1.3.0
|
47
34
|
type: :development
|
48
|
-
|
49
|
-
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.3.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
50
42
|
name: yard
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.5.5
|
48
|
+
type: :development
|
51
49
|
prerelease: false
|
52
|
-
|
53
|
-
requirements:
|
54
|
-
- -
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.5.5
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: timecop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.3.4
|
61
62
|
type: :development
|
62
|
-
version_requirements: *id003
|
63
|
-
- !ruby/object:Gem::Dependency
|
64
|
-
name: rack
|
65
63
|
prerelease: false
|
66
|
-
|
67
|
-
requirements:
|
68
|
-
- -
|
69
|
-
- !ruby/object:Gem::Version
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.3.4
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rack
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
74
75
|
version: 1.0.0
|
75
76
|
type: :runtime
|
76
|
-
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.0.0
|
77
83
|
description: Rack middleware for rate-limiting incoming HTTP requests.
|
78
|
-
email: arto
|
84
|
+
email: arto@bendiken.net
|
79
85
|
executables: []
|
80
|
-
|
81
86
|
extensions: []
|
82
|
-
|
83
87
|
extra_rdoc_files: []
|
84
|
-
|
85
|
-
files:
|
88
|
+
files:
|
86
89
|
- AUTHORS
|
87
90
|
- README
|
88
91
|
- UNLICENSE
|
@@ -91,40 +94,33 @@ files:
|
|
91
94
|
- lib/rack/throttle/hourly.rb
|
92
95
|
- lib/rack/throttle/interval.rb
|
93
96
|
- lib/rack/throttle/limiter.rb
|
97
|
+
- lib/rack/throttle/minute.rb
|
94
98
|
- lib/rack/throttle/time_window.rb
|
95
99
|
- lib/rack/throttle/version.rb
|
96
100
|
- lib/rack/throttle.rb
|
97
|
-
|
98
|
-
|
99
|
-
licenses:
|
101
|
+
homepage: https://github.com/bendiken/rack-throttle
|
102
|
+
licenses:
|
100
103
|
- Public Domain
|
104
|
+
metadata: {}
|
101
105
|
post_install_message:
|
102
106
|
rdoc_options: []
|
103
|
-
|
104
|
-
require_paths:
|
107
|
+
require_paths:
|
105
108
|
- lib
|
106
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- -
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
segments:
|
111
|
-
- 1
|
112
|
-
- 8
|
113
|
-
- 2
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ! '>='
|
112
|
+
- !ruby/object:Gem::Version
|
114
113
|
version: 1.8.2
|
115
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
-
requirements:
|
117
|
-
- -
|
118
|
-
- !ruby/object:Gem::Version
|
119
|
-
|
120
|
-
- 0
|
121
|
-
version: "0"
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ! '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
122
119
|
requirements: []
|
123
|
-
|
124
|
-
|
125
|
-
rubygems_version: 1.3.6
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 2.0.3
|
126
122
|
signing_key:
|
127
|
-
specification_version:
|
123
|
+
specification_version: 4
|
128
124
|
summary: HTTP request rate limiter for Rack applications.
|
129
125
|
test_files: []
|
130
|
-
|
126
|
+
has_rdoc: false
|