viximo-rack-throttle 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS ADDED
@@ -0,0 +1,2 @@
1
+ * Arto Bendiken <arto.bendiken@gmail.com>
2
+ * Brendon Murphy <disposable.20.xternal@spamourmet.com>
data/README ADDED
@@ -0,0 +1,223 @@
1
+ HTTP Request Rate Limiter for Rack Applications
2
+ ===============================================
3
+
4
+ This is [Rack][] middleware that provides logic for rate-limiting incoming
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 3.0 and with
7
+ Sinatra.
8
+
9
+ * <http://github.com/datagraph/rack-throttle>
10
+
11
+ Features
12
+ --------
13
+
14
+ * Throttles a Rack application by enforcing a minimum time interval between
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 (per minute,
17
+ hourly, or daily).
18
+ * Compatible with any Rack application and any Rack-based framework.
19
+ * Stores rate-limiting counters in any key/value store implementation that
20
+ responds to `#[]`/`#[]=` (like Ruby's hashes) or to `#get`/`#set` (like
21
+ memcached or Redis).
22
+ * Compatible with the [gdbm][] binding included in Ruby's standard library.
23
+ * Compatible with the [memcached][], [memcache-client][], [memcache][] and
24
+ [redis][] gems.
25
+ * Compatible with [Heroku][]'s [memcached add-on][Heroku memcache]
26
+ (currently available as a free beta service).
27
+ * Compatible with Ruby 1.8.7 & 1.9
28
+
29
+ Examples
30
+ --------
31
+
32
+ ### Adding throttling to a Rails 3.x application
33
+
34
+ # config/application.rb
35
+ require 'rack/throttle'
36
+
37
+ class Application < Rails::Application
38
+ config.middleware.use Rack::Throttle::Interval
39
+ end
40
+
41
+ ### Adding throttling to a Sinatra application
42
+
43
+ #!/usr/bin/env ruby -rubygems
44
+ require 'sinatra'
45
+ require 'rack/throttle'
46
+
47
+ use Rack::Throttle::Interval
48
+
49
+ get('/hello') { "Hello, world!\n" }
50
+
51
+ ### Adding throttling to a Rackup application
52
+
53
+ #!/usr/bin/env rackup
54
+ require 'rack/throttle'
55
+
56
+ use Rack::Throttle::Interval
57
+
58
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, world!\n"] }
59
+
60
+ ### Enforcing a minimum 3-second interval between requests
61
+
62
+ use Rack::Throttle::Interval, :min => 3.0
63
+
64
+ ### Allowing a maximum of 60 requests per minute
65
+
66
+ use Rack::Throttle::Minute, :max => 60
67
+
68
+ ### Allowing a maximum of 100 requests per hour
69
+
70
+ use Rack::Throttle::Hourly, :max => 100
71
+
72
+ ### Allowing a maximum of 1,000 requests per day
73
+
74
+ use Rack::Throttle::Daily, :max => 1000
75
+
76
+ ### Combining various throttling constraints into one overall policy
77
+
78
+ use Rack::Throttle::Daily, :max => 1000 # requests
79
+ use Rack::Throttle::Hourly, :max => 100 # requests
80
+ use Rack::Throttle::Hourly, :max => 60 # requests
81
+ use Rack::Throttle::Interval, :min => 3.0 # seconds
82
+
83
+ ### Storing the rate-limiting counters in a GDBM database
84
+
85
+ require 'gdbm'
86
+
87
+ use Rack::Throttle::Interval, :cache => GDBM.new('tmp/throttle.db')
88
+
89
+ ### Storing the rate-limiting counters on a Memcached server
90
+
91
+ require 'memcached'
92
+
93
+ use Rack::Throttle::Interval, :cache => Memcached.new, :key_prefix => :throttle
94
+
95
+ ### Storing the rate-limiting counters on a Redis server
96
+
97
+ require 'redis'
98
+
99
+ use Rack::Throttle::Interval, :cache => Redis.new, :key_prefix => :throttle
100
+
101
+ Throttling Strategies
102
+ ---------------------
103
+
104
+ `Rack::Throttle` supports three built-in throttling strategies:
105
+
106
+ * `Rack::Throttle::Interval`: Throttles the application by enforcing a
107
+ minimum interval (by default, 1 second) between subsequent HTTP requests.
108
+ * `Rack::Throttle::Minute`: Throttles the application by defining a
109
+ maximum number of allowed HTTP requests per minute (by default, 60
110
+ requests per minute, which works out to an average of 1 request per
111
+ second).
112
+ * `Rack::Throttle::Hourly`: Throttles the application by defining a
113
+ maximum number of allowed HTTP requests per hour (by default, 3,600
114
+ requests per 60 minutes, which works out to an average of 1 request per
115
+ second).
116
+ * `Rack::Throttle::Daily`: Throttles the application by defining a
117
+ maximum number of allowed HTTP requests per day (by default, 86,400
118
+ requests per 24 hours, which works out to an average of 1 request per
119
+ second).
120
+
121
+ You can fully customize the implementation details of any of these strategies
122
+ by simply subclassing one of the aforementioned default implementations.
123
+ And, of course, should your application-specific requirements be
124
+ significantly more complex than what we've provided for, you can also define
125
+ entirely new kinds of throttling strategies by subclassing the
126
+ `Rack::Throttle::Limiter` base class directly.
127
+
128
+ HTTP Client Identification
129
+ --------------------------
130
+
131
+ The rate-limiting counters stored and maintained by `Rack::Throttle` are
132
+ keyed to unique HTTP clients.
133
+
134
+ By default, HTTP clients are uniquely identified by their IP address as
135
+ returned by `Rack::Request#ip`. If you wish to instead use a more granular,
136
+ application-specific identifier such as a session key or a user account
137
+ name, you need only subclass a throttling strategy implementation and
138
+ override the `#client_identifier` method.
139
+
140
+ HTTP Response Codes and Headers
141
+ -------------------------------
142
+
143
+ ### 403 Forbidden (Rate Limit Exceeded)
144
+
145
+ When a client exceeds their rate limit, `Rack::Throttle` by default returns
146
+ a "403 Forbidden" response with an associated "Rate Limit Exceeded" message
147
+ in the response body.
148
+
149
+ An HTTP 403 response means that the server understood the request, but is
150
+ refusing to respond to it and an accompanying message will explain why.
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.
168
+
169
+ Documentation
170
+ -------------
171
+
172
+ <http://datagraph.rubyforge.org/rack-throttle/>
173
+
174
+ * {Rack::Throttle}
175
+ * {Rack::Throttle::Interval}
176
+ * {Rack::Throttle::Daily}
177
+ * {Rack::Throttle::Hourly}
178
+
179
+ Dependencies
180
+ ------------
181
+
182
+ * [Rack](http://rubygems.org/gems/rack) (>= 1.0.0)
183
+
184
+ Installation
185
+ ------------
186
+
187
+ The recommended installation method is via [RubyGems](http://rubygems.org/).
188
+ To install the latest official release of the gem, do:
189
+
190
+ % [sudo] gem install rack-throttle
191
+
192
+ Download
193
+ --------
194
+
195
+ To get a local working copy of the development repository, do:
196
+
197
+ % git clone git://github.com/datagraph/rack-throttle.git
198
+
199
+ Alternatively, you can download the latest development version as a tarball
200
+ as follows:
201
+
202
+ % wget http://github.com/datagraph/rack-throttle/tarball/master
203
+
204
+ Authors
205
+ -------
206
+
207
+ * [Arto Bendiken](mailto:arto.bendiken@gmail.com) - <http://ar.to/>
208
+ * [Brendon Murphy](mailto:disposable.20.xternal@spamourmet.com>) - <http://www.techfreak.net/>
209
+
210
+ License
211
+ -------
212
+
213
+ `Rack::Throttle` is free and unencumbered public domain software. For more
214
+ information, see <http://unlicense.org/> or the accompanying UNLICENSE file.
215
+
216
+ [Rack]: http://rack.rubyforge.org/
217
+ [gdbm]: http://ruby-doc.org/stdlib/libdoc/gdbm/rdoc/classes/GDBM.html
218
+ [memcached]: http://rubygems.org/gems/memcached
219
+ [memcache-client]: http://rubygems.org/gems/memcache-client
220
+ [memcache]: http://rubygems.org/gems/memcache
221
+ [redis]: http://rubygems.org/gems/redis
222
+ [Heroku]: http://heroku.com/
223
+ [Heroku memcache]: http://docs.heroku.com/memcache
data/UNLICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org/>
@@ -0,0 +1,13 @@
1
+ require 'rack'
2
+
3
+ module Rack
4
+ module Throttle
5
+ autoload :Limiter, 'rack/throttle/limiter'
6
+ autoload :Interval, 'rack/throttle/interval'
7
+ autoload :TimeWindow, 'rack/throttle/time_window'
8
+ autoload :Daily, 'rack/throttle/daily'
9
+ autoload :Hourly, 'rack/throttle/hourly'
10
+ autoload :Minute, 'rack/throttle/minute'
11
+ autoload :VERSION, 'rack/throttle/version'
12
+ end
13
+ end
@@ -0,0 +1,44 @@
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 day (by default, 86,400
5
+ # requests per 24 hours, 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 calendar day. This means that the throttling counter
10
+ # is reset at midnight (according to the server's local timezone) every
11
+ # night.
12
+ #
13
+ # @example Allowing up to 86,400 requests per day
14
+ # use Rack::Throttle::Daily
15
+ #
16
+ # @example Allowing up to 1,000 requests per day
17
+ # use Rack::Throttle::Daily, :max => 1000
18
+ #
19
+ class Daily < TimeWindow
20
+ ##
21
+ # @param [#call] app
22
+ # @param [Hash{Symbol => Object}] options
23
+ # @option options [Integer] :max (86400)
24
+ def initialize(app, options = {})
25
+ super
26
+ end
27
+
28
+ ##
29
+ def max_per_day
30
+ @max_per_hour ||= options[:max_per_day] || options[:max] || 86_400
31
+ end
32
+
33
+ alias_method :max_per_window, :max_per_day
34
+
35
+ protected
36
+
37
+ ##
38
+ # @param [Rack::Request] request
39
+ # @return [String]
40
+ def cache_key(request)
41
+ [super, Time.now.strftime('%Y-%m-%d')].join(':')
42
+ end
43
+ end
44
+ end; end
@@ -0,0 +1,44 @@
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 hour (by default, 3,600
5
+ # requests per 60 minutes, 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 hour. This means that the throttling
10
+ # counter is reset every hour on the hour (according to the server's local
11
+ # timezone).
12
+ #
13
+ # @example Allowing up to 3,600 requests per hour
14
+ # use Rack::Throttle::Hourly
15
+ #
16
+ # @example Allowing up to 100 requests per hour
17
+ # use Rack::Throttle::Hourly, :max => 100
18
+ #
19
+ class Hourly < TimeWindow
20
+ ##
21
+ # @param [#call] app
22
+ # @param [Hash{Symbol => Object}] options
23
+ # @option options [Integer] :max (3600)
24
+ def initialize(app, options = {})
25
+ super
26
+ end
27
+
28
+ ##
29
+ def max_per_hour
30
+ @max_per_hour ||= options[:max_per_hour] || options[:max] || 3_600
31
+ end
32
+
33
+ alias_method :max_per_window, :max_per_hour
34
+
35
+ protected
36
+
37
+ ##
38
+ # @param [Rack::Request] request
39
+ # @return [String]
40
+ def cache_key(request)
41
+ [super, Time.now.strftime('%Y-%m-%dT%H')].join(':')
42
+ end
43
+ end
44
+ end; end
@@ -0,0 +1,63 @@
1
+ module Rack; module Throttle
2
+ ##
3
+ # This rate limiter strategy throttles the application by enforcing a
4
+ # minimum interval (by default, 1 second) between subsequent allowed HTTP
5
+ # requests.
6
+ #
7
+ # @example Allowing up to two requests per second
8
+ # use Rack::Throttle::Interval, :min => 0.5 # 500 ms interval
9
+ #
10
+ # @example Allowing a request every two seconds
11
+ # use Rack::Throttle::Interval, :min => 2.0 # 2000 ms interval
12
+ #
13
+ class Interval < Limiter
14
+ ##
15
+ # @param [#call] app
16
+ # @param [Hash{Symbol => Object}] options
17
+ # @option options [Float] :min (1.0)
18
+ def initialize(app, options = {})
19
+ super
20
+ end
21
+
22
+ ##
23
+ # Returns `true` if sufficient time (equal to or more than
24
+ # {#minimum_interval}) has passed since the last request and the given
25
+ # present `request`.
26
+ #
27
+ # @param [Rack::Request] request
28
+ # @return [Boolean]
29
+ def allowed?(request)
30
+ t1 = request_start_time(request)
31
+ t0 = cache_get(key = cache_key(request)) rescue nil
32
+ allowed = !t0 || (dt = t1 - t0.to_f) >= minimum_interval
33
+ begin
34
+ cache_set(key, t1)
35
+ allowed
36
+ rescue => e
37
+ # If an error occurred while trying to update the timestamp stored
38
+ # in the cache, we will fall back to allowing the request through.
39
+ # This prevents the Rack application blowing up merely due to a
40
+ # backend cache server (Memcached, Redis, etc.) being offline.
41
+ allowed = true
42
+ end
43
+ end
44
+
45
+ ##
46
+ # Returns the number of seconds before the client is allowed to retry an
47
+ # HTTP request.
48
+ #
49
+ # @return [Float]
50
+ def retry_after
51
+ minimum_interval
52
+ end
53
+
54
+ ##
55
+ # Returns the required minimal interval (in terms of seconds) that must
56
+ # elapse between two subsequent HTTP requests.
57
+ #
58
+ # @return [Float]
59
+ def minimum_interval
60
+ @min ||= (@options[:min] || 1.0).to_f
61
+ end
62
+ end
63
+ end; end
@@ -0,0 +1,204 @@
1
+ module Rack; module Throttle
2
+ ##
3
+ # This is the base class for rate limiter implementations.
4
+ #
5
+ # @example Defining a rate limiter subclass
6
+ # class MyLimiter < Limiter
7
+ # def allowed?(request)
8
+ # # TODO: custom logic goes here
9
+ # end
10
+ # end
11
+ #
12
+ class Limiter
13
+ attr_reader :app
14
+ attr_reader :options
15
+
16
+ ##
17
+ # @param [#call] app
18
+ # @param [Hash{Symbol => Object}] options
19
+ # @option options [String] :cache (Hash.new)
20
+ # @option options [String] :key (nil)
21
+ # @option options [String] :key_prefix (nil)
22
+ # @option options [Integer] :code (403)
23
+ # @option options [String] :message ("Rate Limit Exceeded")
24
+ def initialize(app, options = {})
25
+ @app, @options = app, options
26
+ end
27
+
28
+ ##
29
+ # @param [Hash{String => String}] env
30
+ # @return [Array(Integer, Hash, #each)]
31
+ # @see http://rack.rubyforge.org/doc/SPEC.html
32
+ def call(env)
33
+ request = Rack::Request.new(env)
34
+ allowed?(request) ? app.call(env) : rate_limit_exceeded(request)
35
+ end
36
+
37
+ ##
38
+ # Returns `false` if the rate limit has been exceeded for the given
39
+ # `request`, or `true` otherwise.
40
+ #
41
+ # Override this method in subclasses that implement custom rate limiter
42
+ # strategies.
43
+ #
44
+ # @param [Rack::Request] request
45
+ # @return [Boolean]
46
+ def allowed?(request)
47
+ case
48
+ when whitelisted?(request) then true
49
+ when blacklisted?(request) then false
50
+ else true # override in subclasses
51
+ end
52
+ end
53
+
54
+ ##
55
+ # Returns `true` if the originator of the given `request` is whitelisted
56
+ # (not subject to further rate limits).
57
+ #
58
+ # The default implementation always returns `false`. Override this
59
+ # method in a subclass to implement custom whitelisting logic.
60
+ #
61
+ # @param [Rack::Request] request
62
+ # @return [Boolean]
63
+ # @abstract
64
+ def whitelisted?(request)
65
+ false
66
+ end
67
+
68
+ ##
69
+ # Returns `true` if the originator of the given `request` is blacklisted
70
+ # (not honoring rate limits, and thus permanently forbidden access
71
+ # without the need to maintain further rate limit counters).
72
+ #
73
+ # The default implementation always returns `false`. Override this
74
+ # method in a subclass to implement custom blacklisting logic.
75
+ #
76
+ # @param [Rack::Request] request
77
+ # @return [Boolean]
78
+ # @abstract
79
+ def blacklisted?(request)
80
+ false
81
+ end
82
+
83
+ protected
84
+
85
+ ##
86
+ # @return [Hash]
87
+ def cache
88
+ case cache = (options[:cache] ||= {})
89
+ when Proc then cache.call
90
+ else cache
91
+ end
92
+ end
93
+
94
+ ##
95
+ # @param [String] key
96
+ def cache_has?(key)
97
+ case
98
+ when cache.respond_to?(:has_key?)
99
+ cache.has_key?(key)
100
+ when cache.respond_to?(:get)
101
+ cache.get(key) rescue false
102
+ else false
103
+ end
104
+ end
105
+
106
+ ##
107
+ # @param [String] key
108
+ # @return [Object]
109
+ def cache_get(key, default = nil)
110
+ case
111
+ when cache.respond_to?(:[])
112
+ cache[key] || default
113
+ when cache.respond_to?(:get)
114
+ cache.get(key) || default
115
+ end
116
+ end
117
+
118
+ ##
119
+ # @param [String] key
120
+ # @param [Object] value
121
+ # @return [void]
122
+ def cache_set(key, value)
123
+ case
124
+ when cache.respond_to?(:[]=)
125
+ begin
126
+ cache[key] = value
127
+ rescue TypeError => e
128
+ # GDBM throws a "TypeError: can't convert Float into String"
129
+ # exception when trying to store a Float. On the other hand, we
130
+ # don't want to unnecessarily coerce the value to a String for
131
+ # any stores that do support other data types (e.g. in-memory
132
+ # hash objects). So, this is a compromise.
133
+ cache[key] = value.to_s
134
+ end
135
+ when cache.respond_to?(:set)
136
+ cache.set(key, value)
137
+ end
138
+ end
139
+
140
+ ##
141
+ # @param [Rack::Request] request
142
+ # @return [String]
143
+ def cache_key(request)
144
+ id = client_identifier(request)
145
+ case
146
+ when options.has_key?(:key)
147
+ options[:key].call(request)
148
+ when options.has_key?(:key_prefix)
149
+ [options[:key_prefix], id].join(':')
150
+ else id
151
+ end
152
+ end
153
+
154
+ ##
155
+ # @param [Rack::Request] request
156
+ # @return [String]
157
+ def client_identifier(request)
158
+ request.ip.to_s
159
+ end
160
+
161
+ ##
162
+ # @param [Rack::Request] request
163
+ # @return [Float]
164
+ def request_start_time(request)
165
+ case
166
+ when request.env.has_key?('HTTP_X_REQUEST_START')
167
+ request.env['HTTP_X_REQUEST_START'].to_f / 1000
168
+ else
169
+ Time.now.to_f
170
+ end
171
+ end
172
+
173
+ ##
174
+ # Outputs a `Rate Limit Exceeded` error.
175
+ #
176
+ # @return [Array(Integer, Hash, #each)]
177
+ def rate_limit_exceeded(request)
178
+ options[:rate_limit_exceeded_callback].call(request) if options[:rate_limit_exceeded_callback]
179
+ headers = respond_to?(:retry_after) ? {'Retry-After' => retry_after.to_f.ceil.to_s} : {}
180
+ http_error(options[:code] || 403, options[:message] || 'Rate Limit Exceeded', headers)
181
+ end
182
+
183
+ ##
184
+ # Outputs an HTTP `4xx` or `5xx` response.
185
+ #
186
+ # @param [Integer] code
187
+ # @param [String, #to_s] message
188
+ # @param [Hash{String => String}] headers
189
+ # @return [Array(Integer, Hash, #each)]
190
+ def http_error(code, message = nil, headers = {})
191
+ [code, {'Content-Type' => 'text/plain; charset=utf-8'}.merge(headers),
192
+ [http_status(code), (message.nil? ? "\n" : " (#{message})\n")]]
193
+ end
194
+
195
+ ##
196
+ # Returns the standard HTTP status message for the given status `code`.
197
+ #
198
+ # @param [Integer] code
199
+ # @return [String]
200
+ def http_status(code)
201
+ [code, Rack::Utils::HTTP_STATUS_CODES[code]].join(' ')
202
+ end
203
+ end
204
+ end; end
@@ -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
@@ -0,0 +1,21 @@
1
+ module Rack; module Throttle
2
+ ##
3
+ class TimeWindow < Limiter
4
+ ##
5
+ # Returns `true` if fewer than the maximum number of requests permitted
6
+ # for the current window of time have been made.
7
+ #
8
+ # @param [Rack::Request] request
9
+ # @return [Boolean]
10
+ def allowed?(request)
11
+ count = cache_get(key = cache_key(request)).to_i + 1 rescue 1
12
+ allowed = count <= max_per_window.to_i
13
+ begin
14
+ cache_set(key, count)
15
+ allowed
16
+ rescue => e
17
+ allowed = true
18
+ end
19
+ end
20
+ end
21
+ end; end
@@ -0,0 +1,23 @@
1
+ module Rack; module Throttle
2
+ module VERSION
3
+ MAJOR = 0
4
+ MINOR = 4
5
+ TINY = 0
6
+ EXTRA = nil
7
+
8
+ STRING = [MAJOR, MINOR, TINY].join('.')
9
+ STRING << "-#{EXTRA}" if EXTRA
10
+
11
+ ##
12
+ # @return [String]
13
+ def self.to_s() STRING end
14
+
15
+ ##
16
+ # @return [String]
17
+ def self.to_str() STRING end
18
+
19
+ ##
20
+ # @return [Array(Integer, Integer, Integer)]
21
+ def self.to_a() [MAJOR, MINOR, TINY] end
22
+ end
23
+ end; end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: viximo-rack-throttle
3
+ version: !ruby/object:Gem::Version
4
+ hash: 15
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 4
9
+ - 0
10
+ version: 0.4.0
11
+ platform: ruby
12
+ authors:
13
+ - Arto Bendiken
14
+ - Brendon Murphy
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-10-31 00:00:00 -04:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: rack-test
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - "="
29
+ - !ruby/object:Gem::Version
30
+ hash: 13
31
+ segments:
32
+ - 0
33
+ - 5
34
+ - 3
35
+ version: 0.5.3
36
+ type: :development
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: rspec
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - "="
45
+ - !ruby/object:Gem::Version
46
+ hash: 27
47
+ segments:
48
+ - 1
49
+ - 3
50
+ - 0
51
+ version: 1.3.0
52
+ type: :development
53
+ version_requirements: *id002
54
+ - !ruby/object:Gem::Dependency
55
+ name: yard
56
+ prerelease: false
57
+ requirement: &id003 !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 1
63
+ segments:
64
+ - 0
65
+ - 5
66
+ - 5
67
+ version: 0.5.5
68
+ type: :development
69
+ version_requirements: *id003
70
+ - !ruby/object:Gem::Dependency
71
+ name: timecop
72
+ prerelease: false
73
+ requirement: &id004 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - "="
77
+ - !ruby/object:Gem::Version
78
+ hash: 27
79
+ segments:
80
+ - 0
81
+ - 3
82
+ - 4
83
+ version: 0.3.4
84
+ type: :development
85
+ version_requirements: *id004
86
+ - !ruby/object:Gem::Dependency
87
+ name: rack
88
+ prerelease: false
89
+ requirement: &id005 !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 23
95
+ segments:
96
+ - 1
97
+ - 0
98
+ - 0
99
+ version: 1.0.0
100
+ type: :runtime
101
+ version_requirements: *id005
102
+ description: Rack middleware for rate-limiting incoming HTTP requests.
103
+ email: arto.bendiken@gmail.com
104
+ executables: []
105
+
106
+ extensions: []
107
+
108
+ extra_rdoc_files: []
109
+
110
+ files:
111
+ - AUTHORS
112
+ - README
113
+ - UNLICENSE
114
+ - lib/rack/throttle/daily.rb
115
+ - lib/rack/throttle/hourly.rb
116
+ - lib/rack/throttle/interval.rb
117
+ - lib/rack/throttle/limiter.rb
118
+ - lib/rack/throttle/minute.rb
119
+ - lib/rack/throttle/time_window.rb
120
+ - lib/rack/throttle/version.rb
121
+ - lib/rack/throttle.rb
122
+ has_rdoc: false
123
+ homepage: http://github.com/Viximo/rack-throttle
124
+ licenses:
125
+ - Public Domain
126
+ post_install_message:
127
+ rdoc_options: []
128
+
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ none: false
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ hash: 51
137
+ segments:
138
+ - 1
139
+ - 8
140
+ - 2
141
+ version: 1.8.2
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ none: false
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ hash: 3
148
+ segments:
149
+ - 0
150
+ version: "0"
151
+ requirements: []
152
+
153
+ rubyforge_project: datagraph
154
+ rubygems_version: 1.5.3
155
+ signing_key:
156
+ specification_version: 3
157
+ summary: HTTP request rate limiter for Rack applications.
158
+ test_files: []
159
+