rack-throttle 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -1,13 +1,28 @@
1
- HTTP Request Rate Limiter for Rack
2
- ==================================
1
+ HTTP Request Rate Limiter for Rack Applications
2
+ ===============================================
3
3
 
4
4
  This is [Rack][] middleware that provides logic for rate-limiting incoming
5
- HTTP requests to your Rack application. You can use `Rack::Throttle` with
6
- any Ruby web framework based on Rack, including with Ruby on Rails 3.0 and
7
- with Sinatra.
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
8
 
9
9
  * <http://github.com/datagraph/rack-throttle>
10
10
 
11
+ Features
12
+ --------
13
+
14
+ * Throttles a Rack application by enforcing a minimum interval (by default,
15
+ 1 second) between subsequent HTTP requests from a particular client.
16
+ * Compatible with any Rack application and any Rack-based framework.
17
+ * Stores rate-limiting counters in any key/value store implementation that
18
+ responds to `#[]`/`#[]=` (like Ruby's hashes) or to `#get`/`#set` (like
19
+ memcached or Redis).
20
+ * Compatible with the [gdbm][] binding included in Ruby's standard library.
21
+ * Compatible with the [memcached][], [memcache-client][], [memcache][] and
22
+ [redis][] gems.
23
+ * Compatible with [Heroku][]'s [memcached add-on][Heroku memcache]
24
+ (currently available as a free beta service).
25
+
11
26
  Examples
12
27
  --------
13
28
 
@@ -23,10 +38,62 @@ Examples
23
38
 
24
39
  use Rack::Throttle::Interval, :min => 3.0
25
40
 
41
+ ### Using GDBM to store rate-limiting counters
42
+
43
+ require 'gdbm'
44
+ use Rack::Throttle::Interval, :cache => GDBM.new('tmp/throttle.db')
45
+
26
46
  ### Using Memcached to store rate-limiting counters
27
47
 
48
+ require 'memcached'
28
49
  use Rack::Throttle::Interval, :cache => Memcached.new, :key_prefix => :throttle
29
50
 
51
+ ### Using Redis to store rate-limiting counters
52
+
53
+ require 'redis'
54
+ use Rack::Throttle::Interval, :cache => Redis.new, :key_prefix => :throttle
55
+
56
+ HTTP Client Identification
57
+ --------------------------
58
+
59
+ The rate-limiting counters stored and maintained by `Rack::Throttle` are
60
+ keyed to unique HTTP clients.
61
+
62
+ By default, HTTP clients are uniquely identified by their IP address as
63
+ returned by `Rack::Request#ip`. If you wish to instead use a more granular,
64
+ application-specific identifier such as a session key or a user account
65
+ name, you need only subclass `Rack::Throttle::Interval` and override the
66
+ `#client_identifier` method.
67
+
68
+ HTTP Response Codes and Headers
69
+ -------------------------------
70
+
71
+ ### 403 Forbidden (Rate Limit Exceeded)
72
+
73
+ When a client exceeds their rate limit, `Rack::Throttle` by default returns
74
+ a "403 Forbidden" response with an associated "Rate Limit Exceeded" message
75
+ in the response body.
76
+
77
+ An HTTP 403 response means that the server understood the request, but is
78
+ refusing to respond to it and an accompanying message will explain why.
79
+ This indicates an error on the client's part in exceeding the rate limits
80
+ outlined in the acceptable use policy for the site, service, or API.
81
+
82
+ ### 503 Service Unavailable (Rate Limit Exceeded)
83
+
84
+ However, there is an unfortunately widespread practice of instead returning
85
+ a "503 Service Unavailable" response when a client exceeds the set rate
86
+ limits. This is actually technically incorrect because it indicates an
87
+ error on the server's part, which is certainly not the case with rate
88
+ limiting - it was the client that committed the oops, not the server.
89
+
90
+ An HTTP 503 response would be correct in situations where the server was
91
+ genuinely overloaded and couldn't handle more requests, but for rate
92
+ limiting an HTTP 403 response is more appropriate. Nonetheless, if you think
93
+ otherwise, `Rack::Throttle` does allow you to override the returned HTTP
94
+ status code by passing in a `:code => 503` option when constructing a
95
+ `Rack::Throttle::Limiter` instance.
96
+
30
97
  Documentation
31
98
  -------------
32
99
 
@@ -73,4 +140,11 @@ License
73
140
  `Rack::Throttle` is free and unencumbered public domain software. For more
74
141
  information, see <http://unlicense.org/> or the accompanying UNLICENSE file.
75
142
 
76
- [Rack]: http://rack.rubyforge.org/
143
+ [Rack]: http://rack.rubyforge.org/
144
+ [gdbm]: http://ruby-doc.org/stdlib/libdoc/gdbm/rdoc/classes/GDBM.html
145
+ [memcached]: http://rubygems.org/gems/memcached
146
+ [memcache-client]: http://rubygems.org/gems/memcache-client
147
+ [memcache]: http://rubygems.org/gems/memcache
148
+ [redis]: http://rubygems.org/gems/redis
149
+ [Heroku]: http://heroku.com/
150
+ [Heroku memcache]: http://docs.heroku.com/memcache
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -29,7 +29,7 @@ module Rack; module Throttle
29
29
  def allowed?(request)
30
30
  t1 = request_start_time(request)
31
31
  t0 = cache_get(key = cache_key(request)) rescue nil
32
- allowed = !t0 || (t1 - t0.to_f) >= minimum_interval
32
+ allowed = !t0 || (dt = t1 - t0.to_f) >= minimum_interval
33
33
  begin
34
34
  cache_set(key, t1)
35
35
  allowed
@@ -42,6 +42,15 @@ module Rack; module Throttle
42
42
  end
43
43
  end
44
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
+
45
54
  ##
46
55
  # Returns the required minimal interval (in terms of seconds) that must
47
56
  # elapse between two subsequent HTTP requests.
@@ -122,7 +122,16 @@ module Rack; module Throttle
122
122
  def cache_set(key, value)
123
123
  case
124
124
  when cache.respond_to?(:[]=)
125
- cache[key] = value
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
126
135
  when cache.respond_to?(:set)
127
136
  cache.set(key, value)
128
137
  end
@@ -164,22 +173,21 @@ module Rack; module Throttle
164
173
  ##
165
174
  # Outputs a `Rate Limit Exceeded` error.
166
175
  #
167
- # @param [Integer] code
168
- # @param [String] message
169
176
  # @return [Array(Integer, Hash, #each)]
170
- def rate_limit_exceeded(code = nil, message = nil)
171
- http_error(code || options[:code] || 403,
172
- message || options[:message] || 'Rate Limit Exceeded')
177
+ def rate_limit_exceeded
178
+ headers = respond_to?(:retry_after) ? {'Retry-After' => retry_after.to_f.ceil.to_s} : {}
179
+ http_error(options[:code] || 403, options[:message] || 'Rate Limit Exceeded', headers)
173
180
  end
174
181
 
175
182
  ##
176
183
  # Outputs an HTTP `4xx` or `5xx` response.
177
184
  #
178
- # @param [Integer] code
179
- # @param [String, #to_s] message
185
+ # @param [Integer] code
186
+ # @param [String, #to_s] message
187
+ # @param [Hash{String => String}] headers
180
188
  # @return [Array(Integer, Hash, #each)]
181
- def http_error(code, message = nil)
182
- [code, {'Content-Type' => 'text/plain; charset=utf-8'},
189
+ def http_error(code, message = nil, headers = {})
190
+ [code, {'Content-Type' => 'text/plain; charset=utf-8'}.merge(headers),
183
191
  http_status(code) + (message.nil? ? "\n" : " (#{message})\n")]
184
192
  end
185
193
 
@@ -1,7 +1,7 @@
1
1
  module Rack; module Throttle
2
2
  module VERSION
3
3
  MAJOR = 0
4
- MINOR = 1
4
+ MINOR = 2
5
5
  TINY = 0
6
6
  EXTRA = nil
7
7
 
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
7
+ - 2
8
8
  - 0
9
- version: 0.1.0
9
+ version: 0.2.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Arto Bendiken
@@ -73,7 +73,7 @@ dependencies:
73
73
  version: 1.0.0
74
74
  type: :runtime
75
75
  version_requirements: *id004
76
- description: Rack middleware for rate-limiting HTTP requests.
76
+ description: Rack middleware for rate-limiting incoming HTTP requests.
77
77
  email: arto.bendiken@gmail.com
78
78
  executables: []
79
79