improved-rack-throttle 0.7.0 → 0.7.1
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/Gemfile +1 -0
- data/Gemfile.lock +7 -5
- data/README.md +25 -8
- data/improved-rack-throttle.gemspec +6 -3
- data/lib/rack/throttle/limiters/sliding_window.rb +10 -3
- data/lib/rack/throttle/matchers/matcher.rb +13 -5
- data/lib/rack/throttle/matchers/method_matcher.rb +9 -0
- data/lib/rack/throttle/matchers/url_matcher.rb +10 -1
- data/lib/rack/throttle/matchers/user_agent_matcher.rb +10 -1
- data/lib/rack/throttle/version.rb +1 -1
- data/spec/limiters/sliding_window_spec.rb +1 -1
- metadata +69 -18
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -8,13 +8,14 @@ GEM
|
|
8
8
|
git (>= 1.2.5)
|
9
9
|
rake
|
10
10
|
rdoc
|
11
|
-
json (1.7.
|
12
|
-
rack (1.4.
|
11
|
+
json (1.7.6)
|
12
|
+
rack (1.4.4)
|
13
13
|
rack-test (0.6.2)
|
14
14
|
rack (>= 1.0)
|
15
|
-
rake (0.
|
15
|
+
rake (10.0.3)
|
16
16
|
rdoc (3.12)
|
17
17
|
json (~> 1.4)
|
18
|
+
redcarpet (2.2.2)
|
18
19
|
rspec (2.11.0)
|
19
20
|
rspec-core (~> 2.11.0)
|
20
21
|
rspec-expectations (~> 2.11.0)
|
@@ -23,8 +24,8 @@ GEM
|
|
23
24
|
rspec-expectations (2.11.3)
|
24
25
|
diff-lcs (~> 1.1.3)
|
25
26
|
rspec-mocks (2.11.3)
|
26
|
-
timecop (0.5.
|
27
|
-
yard (0.8.
|
27
|
+
timecop (0.5.9.1)
|
28
|
+
yard (0.8.3)
|
28
29
|
|
29
30
|
PLATFORMS
|
30
31
|
ruby
|
@@ -34,6 +35,7 @@ DEPENDENCIES
|
|
34
35
|
rack (>= 1.0.0)
|
35
36
|
rack-test (~> 0.6.2)
|
36
37
|
rake
|
38
|
+
redcarpet
|
37
39
|
rspec (~> 2.11.0)
|
38
40
|
timecop (~> 0.5.2)
|
39
41
|
yard (>= 0.5.5)
|
data/README.md
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
HTTP Request Rate Limiter for Rack Applications
|
2
2
|
===============================================
|
3
3
|
|
4
|
-
|
4
|
+
[](https://codeclimate.com/github/bensomers/improved-rack-throttle)
|
5
|
+
[](https://gemnasium.com/bensomers/improved-rack-throttle)
|
6
|
+
|
7
|
+
This is a [Rack][] middleware that provides logic for rate-limiting incoming
|
5
8
|
HTTP requests to Rack applications. You can use `Rack::Throttle` with any
|
6
9
|
Ruby web framework based on Rack, including with Ruby on Rails 3.0 and with
|
7
10
|
Sinatra.
|
8
11
|
|
9
|
-
* <http://github.com/
|
12
|
+
* <http://github.com/bensomers/improved-rack-throttle>
|
10
13
|
|
11
14
|
Features
|
12
15
|
--------
|
@@ -26,7 +29,6 @@ Features
|
|
26
29
|
* Compatible with the [memcached][], [memcache-client][], [memcache][] and
|
27
30
|
[redis][] gems.
|
28
31
|
* Compatible with [Heroku][]'s [memcached add-on][Heroku memcache]
|
29
|
-
(currently available as a free beta service).
|
30
32
|
|
31
33
|
Examples
|
32
34
|
--------
|
@@ -71,6 +73,10 @@ Examples
|
|
71
73
|
|
72
74
|
use Rack::Throttle::Daily, :max => 1000
|
73
75
|
|
76
|
+
### Allowing 1 request per second, with bursts of up to 5 requests
|
77
|
+
|
78
|
+
use Rack::Throttle::SlidingWindow, :average => 1, :burst => 5
|
79
|
+
|
74
80
|
### Combining various throttling constraints into one overall policy
|
75
81
|
|
76
82
|
use Rack::Throttle::Daily, :max => 1000 # requests
|
@@ -102,7 +108,7 @@ Examples
|
|
102
108
|
Throttling Strategies
|
103
109
|
---------------------
|
104
110
|
|
105
|
-
`Rack::Throttle` supports
|
111
|
+
`Rack::Throttle` supports four built-in throttling strategies:
|
106
112
|
|
107
113
|
* `Rack::Throttle::Interval`: Throttles the application by enforcing a
|
108
114
|
minimum interval (by default, 1 second) between subsequent HTTP requests.
|
@@ -114,6 +120,12 @@ Throttling Strategies
|
|
114
120
|
maximum number of allowed HTTP requests per day (by default, 86,400
|
115
121
|
requests per 24 hours, which works out to an average of 1 request per
|
116
122
|
second).
|
123
|
+
* `Rack::Throttle::SlidingWindow`: Throttles the application by defining
|
124
|
+
an average request rate, and a burst allowance that clients can hit
|
125
|
+
(as long as they stay within the average). By default, this is 1
|
126
|
+
request per second, with bursts of up to 5 requests at a time. Users
|
127
|
+
who exceed the average and who have used up their burst will have all
|
128
|
+
of their requests denied until they comply with the policy.
|
117
129
|
|
118
130
|
You can fully customize the implementation details of any of these strategies
|
119
131
|
by simply subclassing one of the aforementioned default implementations.
|
@@ -125,7 +137,7 @@ entirely new kinds of throttling strategies by subclassing the
|
|
125
137
|
Scoping Rules
|
126
138
|
-------------
|
127
139
|
Rack::Throttle ships with a Rack::Throttle::Matcher base class, and three
|
128
|
-
implementations
|
140
|
+
implementations. Rack::Throttle::UrlMatcher and
|
129
141
|
Rack::Throttle::UserAgentMatcher allow you to pass in regular expressions
|
130
142
|
for request path and user agent, while Rack::Throttle::MethodMatcher
|
131
143
|
will filter by request method when passed a Symbol :get, :post, :put, or
|
@@ -142,7 +154,7 @@ keyed to unique HTTP clients.
|
|
142
154
|
By default, HTTP clients are uniquely identified by their IP address as
|
143
155
|
returned by `Rack::Request#ip`. If you wish to instead use a more granular,
|
144
156
|
application-specific identifier such as a session key or a user account
|
145
|
-
name, you
|
157
|
+
name, you can subclass a throttling strategy implementation and
|
146
158
|
override the `#client_identifier` method.
|
147
159
|
|
148
160
|
HTTP Response Codes and Headers
|
@@ -176,14 +188,19 @@ status code by passing in a `:code => 503` option when constructing a
|
|
176
188
|
|
177
189
|
Documentation
|
178
190
|
-------------
|
179
|
-
|
191
|
+
UNDER DEVELOPMENT
|
180
192
|
|
181
|
-
<http://
|
193
|
+
<http://rubydoc.info/gems/improved-rack-throttle>
|
182
194
|
|
183
195
|
* {Rack::Throttle}
|
184
196
|
* {Rack::Throttle::Interval}
|
185
197
|
* {Rack::Throttle::Daily}
|
186
198
|
* {Rack::Throttle::Hourly}
|
199
|
+
* {Rack::Throttle::SlidingWindow}
|
200
|
+
* {Rack::Throttle::Matcher}
|
201
|
+
* {Rack::Throttle::MethodMatcher}
|
202
|
+
* {Rack::Throttle::UrlMatcher}
|
203
|
+
* {Rack::Throttle::UserAgentMatcher}
|
187
204
|
|
188
205
|
Dependencies
|
189
206
|
------------
|
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "improved-rack-throttle"
|
8
|
-
s.version = "0.7.
|
8
|
+
s.version = "0.7.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Ben Somers", "Arto Bendiken", "Brendon Murphy"]
|
12
|
-
s.date = "
|
12
|
+
s.date = "2013-05-09"
|
13
13
|
s.description = "Rack middleware for rate-limiting incoming HTTP requests."
|
14
14
|
s.email = "somers.ben@gmail.com"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -55,7 +55,7 @@ Gem::Specification.new do |s|
|
|
55
55
|
s.homepage = "http://github.com/bensomers/improved-rack-throttle"
|
56
56
|
s.licenses = ["Public Domain"]
|
57
57
|
s.require_paths = ["lib"]
|
58
|
-
s.rubygems_version = "1.8.
|
58
|
+
s.rubygems_version = "1.8.25"
|
59
59
|
s.summary = "HTTP request rate limiter for Rack applications."
|
60
60
|
|
61
61
|
if s.respond_to? :specification_version then
|
@@ -67,6 +67,7 @@ Gem::Specification.new do |s|
|
|
67
67
|
s.add_development_dependency(%q<rack-test>, ["~> 0.6.2"])
|
68
68
|
s.add_development_dependency(%q<rspec>, ["~> 2.11.0"])
|
69
69
|
s.add_development_dependency(%q<yard>, [">= 0.5.5"])
|
70
|
+
s.add_development_dependency(%q<redcarpet>, [">= 0"])
|
70
71
|
s.add_development_dependency(%q<rake>, [">= 0"])
|
71
72
|
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
72
73
|
else
|
@@ -75,6 +76,7 @@ Gem::Specification.new do |s|
|
|
75
76
|
s.add_dependency(%q<rack-test>, ["~> 0.6.2"])
|
76
77
|
s.add_dependency(%q<rspec>, ["~> 2.11.0"])
|
77
78
|
s.add_dependency(%q<yard>, [">= 0.5.5"])
|
79
|
+
s.add_dependency(%q<redcarpet>, [">= 0"])
|
78
80
|
s.add_dependency(%q<rake>, [">= 0"])
|
79
81
|
s.add_dependency(%q<jeweler>, [">= 0"])
|
80
82
|
end
|
@@ -84,6 +86,7 @@ Gem::Specification.new do |s|
|
|
84
86
|
s.add_dependency(%q<rack-test>, ["~> 0.6.2"])
|
85
87
|
s.add_dependency(%q<rspec>, ["~> 2.11.0"])
|
86
88
|
s.add_dependency(%q<yard>, [">= 0.5.5"])
|
89
|
+
s.add_dependency(%q<redcarpet>, [">= 0"])
|
87
90
|
s.add_dependency(%q<rake>, [">= 0"])
|
88
91
|
s.add_dependency(%q<jeweler>, [">= 0"])
|
89
92
|
end
|
@@ -18,9 +18,8 @@ module Rack; module Throttle
|
|
18
18
|
end
|
19
19
|
|
20
20
|
##
|
21
|
-
# Returns `true` if
|
22
|
-
#
|
23
|
-
# present `request`.
|
21
|
+
# Returns `true` if the request conforms to the
|
22
|
+
# specified :average and :burst rules
|
24
23
|
#
|
25
24
|
# @param [Rack::Request] request
|
26
25
|
# @return [Boolean]
|
@@ -45,10 +44,18 @@ module Rack; module Throttle
|
|
45
44
|
end
|
46
45
|
end
|
47
46
|
|
47
|
+
###
|
48
|
+
# LeakyBucket is an internal class used to implement the
|
49
|
+
# SlidingWindow limiter strategy. It is a (slightly tweaked)
|
50
|
+
# implementation of the {http://en.wikipedia.org/wiki/Leaky_bucket
|
51
|
+
# Leaky Bucket Algorithm}.
|
48
52
|
class LeakyBucket
|
49
53
|
attr_accessor :maximum, :outflow
|
50
54
|
attr_reader :count, :last_touched
|
51
55
|
|
56
|
+
##
|
57
|
+
# @param [Integer] maximum
|
58
|
+
# @param [Float] outflow
|
52
59
|
def initialize(maximum, outflow)
|
53
60
|
@maximum, @outflow = maximum, outflow
|
54
61
|
@count, @last_touched = 0, Time.now
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module Rack; module Throttle
|
2
2
|
###
|
3
|
-
# This is the base class for matcher implementations
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
3
|
+
# This is the base class for matcher implementations.
|
4
|
+
# Implementations are provided for User Agent, URL, and request
|
5
|
+
# method. Subclass Matcher if you want to provide a custom
|
6
|
+
# implementation.
|
7
7
|
class Matcher
|
8
8
|
attr_reader :rule
|
9
9
|
|
@@ -11,11 +11,19 @@ module Rack; module Throttle
|
|
11
11
|
@rule = rule
|
12
12
|
end
|
13
13
|
|
14
|
-
#
|
14
|
+
# Must be implemented in a subclass.
|
15
|
+
# MUST return true or false.
|
16
|
+
# @return [Boolean]
|
17
|
+
# @abstract
|
15
18
|
def match?
|
16
19
|
true
|
17
20
|
end
|
18
21
|
|
22
|
+
# Must be implemented in a subclass.
|
23
|
+
# Used to produce a unique key in our cache store.
|
24
|
+
# Typically of the form "xx-"
|
25
|
+
# @return [String]
|
26
|
+
# @abstract
|
19
27
|
def identifier
|
20
28
|
""
|
21
29
|
end
|
@@ -1,12 +1,21 @@
|
|
1
1
|
module Rack; module Throttle
|
2
2
|
###
|
3
|
+
# MethodMatchers are used to restrict throttling based on the HTTP
|
4
|
+
# method used by the request. For instance, you may only care about
|
5
|
+
# throttling POST requests on a login form; GET requests are just
|
6
|
+
# fine.
|
3
7
|
# MethodMatchers take Symbol objects of :get, :put, :post, or :delete
|
4
8
|
class MethodMatcher < Matcher
|
9
|
+
##
|
10
|
+
# @param [Rack::Request] request
|
11
|
+
# @return [Boolean]
|
5
12
|
def match?(request)
|
6
13
|
rack_method = :"#{@rule}?"
|
7
14
|
request.send(rack_method)
|
8
15
|
end
|
9
16
|
|
17
|
+
##
|
18
|
+
# @return [String]
|
10
19
|
def identifier
|
11
20
|
"meth-#{@rule}"
|
12
21
|
end
|
@@ -1,11 +1,20 @@
|
|
1
1
|
module Rack; module Throttle
|
2
2
|
###
|
3
|
-
# UrlMatchers
|
3
|
+
# UrlMatchers are used to restrict requests based on the URL
|
4
|
+
# requested. For instance, you may care about limiting requests
|
5
|
+
# to a machine-consumed API, but not be concerned about requests
|
6
|
+
# coming from browsers.
|
7
|
+
# UrlMatchers take Regexp object to matcha gainst the request path.
|
4
8
|
class UrlMatcher < Matcher
|
9
|
+
###
|
10
|
+
# @param [Rack::Request] request
|
11
|
+
# @return [Boolean]
|
5
12
|
def match?(request)
|
6
13
|
!!(@rule =~ request.path)
|
7
14
|
end
|
8
15
|
|
16
|
+
###
|
17
|
+
# @return [String]
|
9
18
|
def identifier
|
10
19
|
"url-" + @rule.inspect
|
11
20
|
end
|
@@ -1,11 +1,20 @@
|
|
1
1
|
module Rack; module Throttle
|
2
2
|
###
|
3
|
-
#
|
3
|
+
# User Agent Matchers are used to restrict requests based on the User
|
4
|
+
# Agent supplied by the requester. For instance, you may care about
|
5
|
+
# limiting a specific API consumer who reliably uses a known User-Agent.
|
6
|
+
# UserAgentMatchers take Regexp objects to match against the
|
7
|
+
# User-Agent.
|
4
8
|
class UserAgentMatcher < Matcher
|
9
|
+
###
|
10
|
+
# @param [Rack::Request] request
|
11
|
+
# @return [Boolean]
|
5
12
|
def match?(request)
|
6
13
|
!!(@rule =~ request.user_agent)
|
7
14
|
end
|
8
15
|
|
16
|
+
###
|
17
|
+
# @return [String]
|
9
18
|
def identifier
|
10
19
|
"ua-" + @rule.inspect
|
11
20
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: improved-rack-throttle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,11 +11,11 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2013-05-09 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rack
|
18
|
-
requirement:
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
19
|
none: false
|
20
20
|
requirements:
|
21
21
|
- - ! '>='
|
@@ -23,10 +23,15 @@ dependencies:
|
|
23
23
|
version: 1.0.0
|
24
24
|
type: :runtime
|
25
25
|
prerelease: false
|
26
|
-
version_requirements:
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ! '>='
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 1.0.0
|
27
32
|
- !ruby/object:Gem::Dependency
|
28
33
|
name: timecop
|
29
|
-
requirement:
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
30
35
|
none: false
|
31
36
|
requirements:
|
32
37
|
- - ~>
|
@@ -34,10 +39,15 @@ dependencies:
|
|
34
39
|
version: 0.5.2
|
35
40
|
type: :development
|
36
41
|
prerelease: false
|
37
|
-
version_requirements:
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.5.2
|
38
48
|
- !ruby/object:Gem::Dependency
|
39
49
|
name: rack-test
|
40
|
-
requirement:
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
41
51
|
none: false
|
42
52
|
requirements:
|
43
53
|
- - ~>
|
@@ -45,10 +55,15 @@ dependencies:
|
|
45
55
|
version: 0.6.2
|
46
56
|
type: :development
|
47
57
|
prerelease: false
|
48
|
-
version_requirements:
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ~>
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 0.6.2
|
49
64
|
- !ruby/object:Gem::Dependency
|
50
65
|
name: rspec
|
51
|
-
requirement:
|
66
|
+
requirement: !ruby/object:Gem::Requirement
|
52
67
|
none: false
|
53
68
|
requirements:
|
54
69
|
- - ~>
|
@@ -56,21 +71,47 @@ dependencies:
|
|
56
71
|
version: 2.11.0
|
57
72
|
type: :development
|
58
73
|
prerelease: false
|
59
|
-
version_requirements:
|
74
|
+
version_requirements: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ~>
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 2.11.0
|
60
80
|
- !ruby/object:Gem::Dependency
|
61
81
|
name: yard
|
62
|
-
requirement:
|
82
|
+
requirement: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: 0.5.5
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
91
|
none: false
|
64
92
|
requirements:
|
65
93
|
- - ! '>='
|
66
94
|
- !ruby/object:Gem::Version
|
67
95
|
version: 0.5.5
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: redcarpet
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
68
104
|
type: :development
|
69
105
|
prerelease: false
|
70
|
-
version_requirements:
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ! '>='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
71
112
|
- !ruby/object:Gem::Dependency
|
72
113
|
name: rake
|
73
|
-
requirement:
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
74
115
|
none: false
|
75
116
|
requirements:
|
76
117
|
- - ! '>='
|
@@ -78,10 +119,15 @@ dependencies:
|
|
78
119
|
version: '0'
|
79
120
|
type: :development
|
80
121
|
prerelease: false
|
81
|
-
version_requirements:
|
122
|
+
version_requirements: !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ! '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
82
128
|
- !ruby/object:Gem::Dependency
|
83
129
|
name: jeweler
|
84
|
-
requirement:
|
130
|
+
requirement: !ruby/object:Gem::Requirement
|
85
131
|
none: false
|
86
132
|
requirements:
|
87
133
|
- - ! '>='
|
@@ -89,7 +135,12 @@ dependencies:
|
|
89
135
|
version: '0'
|
90
136
|
type: :development
|
91
137
|
prerelease: false
|
92
|
-
version_requirements:
|
138
|
+
version_requirements: !ruby/object:Gem::Requirement
|
139
|
+
none: false
|
140
|
+
requirements:
|
141
|
+
- - ! '>='
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
93
144
|
description: Rack middleware for rate-limiting incoming HTTP requests.
|
94
145
|
email: somers.ben@gmail.com
|
95
146
|
executables: []
|
@@ -147,7 +198,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
147
198
|
version: '0'
|
148
199
|
segments:
|
149
200
|
- 0
|
150
|
-
hash:
|
201
|
+
hash: -4496223408765979053
|
151
202
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
203
|
none: false
|
153
204
|
requirements:
|
@@ -156,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
156
207
|
version: '0'
|
157
208
|
requirements: []
|
158
209
|
rubyforge_project:
|
159
|
-
rubygems_version: 1.8.
|
210
|
+
rubygems_version: 1.8.25
|
160
211
|
signing_key:
|
161
212
|
specification_version: 3
|
162
213
|
summary: HTTP request rate limiter for Rack applications.
|