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 CHANGED
@@ -7,6 +7,7 @@ group :development, :test do
7
7
  gem 'rack-test', '~> 0.6.2'
8
8
  gem 'rspec', '~> 2.11.0'
9
9
  gem 'yard' , '>= 0.5.5'
10
+ gem 'redcarpet'
10
11
  gem 'rake'
11
12
  gem 'jeweler'
12
13
  end
@@ -8,13 +8,14 @@ GEM
8
8
  git (>= 1.2.5)
9
9
  rake
10
10
  rdoc
11
- json (1.7.5)
12
- rack (1.4.1)
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.9.2.2)
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.2)
27
- yard (0.8.2.1)
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
- This is [Rack][] middleware that provides logic for rate-limiting incoming
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/datagraph/rack-throttle>
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 three built-in throttling strategies:
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 of it. Rack::Throttle::UrlMatcher and
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 need only subclass a throttling strategy implementation and
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
- OUT OF DATE
191
+ UNDER DEVELOPMENT
180
192
 
181
- <http://datagraph.rubyforge.org/rack-throttle/>
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.0"
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 = "2012-10-05"
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.17"
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 sufficient time (equal to or more than
22
- # {#minimum_interval}) has passed since the last request and the given
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
- # Implements IP, user agent, url, and request method based matching
5
- # e.g. implement throttling rules that discriminate by ip, user agent, url, request method,
6
- # or any combination thereof
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
- # MUST return true or false
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 take Regexp objects and compare the request path against them
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
- # IpMatchers take RegExp objects and compare the request ip against them
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
@@ -2,7 +2,7 @@ module Rack; module Throttle
2
2
  module VERSION
3
3
  MAJOR = 0
4
4
  MINOR = 7
5
- TINY = 0
5
+ TINY = 1
6
6
  EXTRA = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY].join('.')
@@ -9,8 +9,8 @@ describe Rack::Throttle::SlidingWindow do
9
9
  end
10
10
 
11
11
  before(:each) do
12
- @time = Time.now
13
12
  Timecop.freeze
13
+ @time = Time.now
14
14
  end
15
15
 
16
16
  after(:each) do
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.0
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: 2012-10-05 00:00:00.000000000 Z
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: &70296124961520 !ruby/object:Gem::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: *70296124961520
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: &70296124960800 !ruby/object:Gem::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: *70296124960800
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: &70296124960000 !ruby/object:Gem::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: *70296124960000
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: &70296124959520 !ruby/object:Gem::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: *70296124959520
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: &70296124958980 !ruby/object:Gem::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: *70296124958980
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: &70296124958220 !ruby/object:Gem::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: *70296124958220
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: &70296124957640 !ruby/object:Gem::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: *70296124957640
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: 3964473901145440251
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.17
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.