viximo-rack-throttle 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.
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
+