improved-rack-throttle 0.7.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/bensomers/improved-rack-throttle)
|
5
|
+
[![Dependency Status](https://gemnasium.com/bensomers/improved-rack-throttle.png)](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.
|