rack-throttle 0.3.0 → 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.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MDNkYTliNDNmN2ExYWQ1OWY3NjkyZDNkZTk0OTQ0ZGZiODk1ZWFiNw==
5
+ data.tar.gz: !binary |-
6
+ ODdlYzYyOGQwYzY2OGZiNDgyNzc4NWY3OTEzNDc2ZDFlYzZhZjE1ZA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NjUxNmJmMTBlNWU1MDk3ZjM2Zjc1YmEzNWJlYTYyYzBlMWQxNDFiOTgzMGMx
10
+ YjM1OGQ5Mzk1ZjRlZDQxYzQxZjNiZDdlN2IzMjM2NDU3ZGRjZWUwZGJiYmQw
11
+ ZDQ3NGYxNDNhZGJhZmQ3MzcyZGU4ZDgwYTg5OTE1NmNmYzZhMDg=
12
+ data.tar.gz: !binary |-
13
+ OTRmNWNlN2VkYWU0ODAyMTAzMmRjMmE0ZTllMmE3OTliODA5MmFkMTQxOGRm
14
+ YjUzOTc0MTNlZjliZmZhN2Q2NWMyNzliYTk1MGMyMTJhYTliMDE5M2I1ZDdh
15
+ NTRmMWNlYzEyMDhiOWNhYzY0YmFhZGFiZWQxNjI0ZmRjMWE3Nzk=
data/AUTHORS CHANGED
@@ -1,2 +1 @@
1
- * Arto Bendiken <arto.bendiken@gmail.com>
2
- * Brendon Murphy <disposable.20.xternal@spamourmet.com>
1
+ * Arto Bendiken <arto@bendiken.net>
data/README CHANGED
@@ -3,18 +3,18 @@ HTTP Request Rate Limiter for Rack Applications
3
3
 
4
4
  This is [Rack][] middleware that provides logic for rate-limiting incoming
5
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
6
+ Ruby web framework based on Rack, including with Ruby on Rails and with
7
7
  Sinatra.
8
8
 
9
- * <http://github.com/datagraph/rack-throttle>
9
+ * <https://github.com/bendiken/rack-throttle>
10
10
 
11
11
  Features
12
12
  --------
13
13
 
14
14
  * Throttles a Rack application by enforcing a minimum time interval between
15
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 (hourly
17
- or daily).
16
+ a maximum number of allowed HTTP requests per a given time period (per minute,
17
+ hourly, or daily).
18
18
  * Compatible with any Rack application and any Rack-based framework.
19
19
  * Stores rate-limiting counters in any key/value store implementation that
20
20
  responds to `#[]`/`#[]=` (like Ruby's hashes) or to `#get`/`#set` (like
@@ -28,18 +28,42 @@ Features
28
28
  Examples
29
29
  --------
30
30
 
31
- ### Adding throttling to a Rackup application
31
+ ### Adding throttling to a Rails application
32
32
 
33
+ # config/application.rb
33
34
  require 'rack/throttle'
35
+
36
+ class Application < Rails::Application
37
+ config.middleware.use Rack::Throttle::Interval
38
+ end
34
39
 
40
+ ### Adding throttling to a Sinatra application
41
+
42
+ #!/usr/bin/env ruby -rubygems
43
+ require 'sinatra'
44
+ require 'rack/throttle'
45
+
35
46
  use Rack::Throttle::Interval
47
+
48
+ get('/hello') { "Hello, world!\n" }
36
49
 
50
+ ### Adding throttling to a Rackup application
51
+
52
+ #!/usr/bin/env rackup
53
+ require 'rack/throttle'
54
+
55
+ use Rack::Throttle::Interval
56
+
37
57
  run lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, world!\n"] }
38
58
 
39
59
  ### Enforcing a minimum 3-second interval between requests
40
60
 
41
61
  use Rack::Throttle::Interval, :min => 3.0
42
62
 
63
+ ### Allowing a maximum of 60 requests per minute
64
+
65
+ use Rack::Throttle::Minute, :max => 60
66
+
43
67
  ### Allowing a maximum of 100 requests per hour
44
68
 
45
69
  use Rack::Throttle::Hourly, :max => 100
@@ -52,6 +76,7 @@ Examples
52
76
 
53
77
  use Rack::Throttle::Daily, :max => 1000 # requests
54
78
  use Rack::Throttle::Hourly, :max => 100 # requests
79
+ use Rack::Throttle::Minute, :max => 60 # requests
55
80
  use Rack::Throttle::Interval, :min => 3.0 # seconds
56
81
 
57
82
  ### Storing the rate-limiting counters in a GDBM database
@@ -136,16 +161,6 @@ otherwise, `Rack::Throttle` does allow you to override the returned HTTP
136
161
  status code by passing in a `:code => 503` option when constructing a
137
162
  `Rack::Throttle::Limiter` instance.
138
163
 
139
- Documentation
140
- -------------
141
-
142
- <http://datagraph.rubyforge.org/rack-throttle/>
143
-
144
- * {Rack::Throttle}
145
- * {Rack::Throttle::Interval}
146
- * {Rack::Throttle::Daily}
147
- * {Rack::Throttle::Hourly}
148
-
149
164
  Dependencies
150
165
  ------------
151
166
 
@@ -154,34 +169,47 @@ Dependencies
154
169
  Installation
155
170
  ------------
156
171
 
157
- The recommended installation method is via RubyGems. To install the latest
158
- official release, do:
172
+ The recommended installation method is via [RubyGems](http://rubygems.org/).
173
+ To install the latest official release of the gem, do:
159
174
 
160
175
  % [sudo] gem install rack-throttle
161
176
 
162
- Download
163
- --------
164
-
165
- To get a local working copy of the development repository, do:
177
+ Authors
178
+ -------
166
179
 
167
- % git clone git://github.com/datagraph/rack-throttle.git
180
+ * [Arto Bendiken](https://gratipay.com/bendiken) - <http://ar.to/>
168
181
 
169
- Alternatively, you can download the latest development version as a tarball
170
- as follows:
182
+ Contributors
183
+ ------------
171
184
 
172
- % wget http://github.com/datagraph/rack-throttle/tarball/master
185
+ * [Brendon Murphy](https://github.com/bemurphy)
186
+ * [Hendrik Kleinwaechter](https://github.com/hendricius)
187
+ * [Karel Minarik](https://github.com/karmi)
188
+ * [Keita Urashima](https://github.com/ursm)
189
+ * [Leonid Beder](https://github.com/lbeder)
190
+ * [TJ Singleton](https://github.com/tjsingleton)
191
+ * [Winfield Peterson](https://github.com/wpeterson)
173
192
 
174
- Authors
175
- -------
193
+ Contributing
194
+ ------------
176
195
 
177
- * [Arto Bendiken](mailto:arto.bendiken@gmail.com) - <http://ar.to/>
178
- * [Brendon Murphy](mailto:disposable.20.xternal@spamourmet.com>) - <http://www.techfreak.net/>
196
+ * Do your best to adhere to the existing coding conventions and idioms.
197
+ * Don't use hard tabs, and don't leave trailing whitespace on any line.
198
+ Before committing, run `git diff --check` to make sure of this.
199
+ * Do document every method you add using [YARD][] annotations. Read the
200
+ [tutorial][YARD-GS] or just look at the existing code for examples.
201
+ * Don't touch the gemspec or `VERSION` files. If you need to change them,
202
+ do so on your private branch only.
203
+ * Do feel free to add yourself to the `CREDITS` file and the
204
+ corresponding list in the the `README`. Alphabetical order applies.
205
+ * Don't touch the `AUTHORS` file. If your contributions are significant
206
+ enough, be assured we will eventually add you in there.
179
207
 
180
208
  License
181
209
  -------
182
210
 
183
- `Rack::Throttle` is free and unencumbered public domain software. For more
184
- information, see <http://unlicense.org/> or the accompanying UNLICENSE file.
211
+ This is free and unencumbered public domain software. For more information,
212
+ see <http://unlicense.org/> or the accompanying `UNLICENSE` file.
185
213
 
186
214
  [Rack]: http://rack.rubyforge.org/
187
215
  [gdbm]: http://ruby-doc.org/stdlib/libdoc/gdbm/rdoc/classes/GDBM.html
@@ -191,3 +219,5 @@ information, see <http://unlicense.org/> or the accompanying UNLICENSE file.
191
219
  [redis]: http://rubygems.org/gems/redis
192
220
  [Heroku]: http://heroku.com/
193
221
  [Heroku memcache]: http://docs.heroku.com/memcache
222
+ [YARD]: http://yardoc.org/
223
+ [YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 0.4.0
@@ -7,6 +7,7 @@ module Rack
7
7
  autoload :TimeWindow, 'rack/throttle/time_window'
8
8
  autoload :Daily, 'rack/throttle/daily'
9
9
  autoload :Hourly, 'rack/throttle/hourly'
10
+ autoload :Minute, 'rack/throttle/minute'
10
11
  autoload :VERSION, 'rack/throttle/version'
11
12
  end
12
13
  end
@@ -31,7 +31,7 @@ module Rack; module Throttle
31
31
  # @see http://rack.rubyforge.org/doc/SPEC.html
32
32
  def call(env)
33
33
  request = Rack::Request.new(env)
34
- allowed?(request) ? app.call(env) : rate_limit_exceeded
34
+ allowed?(request) ? app.call(env) : rate_limit_exceeded(request)
35
35
  end
36
36
 
37
37
  ##
@@ -124,7 +124,7 @@ module Rack; module Throttle
124
124
  when cache.respond_to?(:[]=)
125
125
  begin
126
126
  cache[key] = value
127
- rescue TypeError => e
127
+ rescue TypeError
128
128
  # GDBM throws a "TypeError: can't convert Float into String"
129
129
  # exception when trying to store a Float. On the other hand, we
130
130
  # don't want to unnecessarily coerce the value to a String for
@@ -162,11 +162,12 @@ module Rack; module Throttle
162
162
  # @param [Rack::Request] request
163
163
  # @return [Float]
164
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
165
+ # Check whether HTTP_X_REQUEST_START or HTTP_X_QUEUE_START exist and parse its value (for
166
+ # example, when having nginx in your stack, it's going to be in the "t=\d+" format).
167
+ if val = (request.env['HTTP_X_REQUEST_START'] || request.env['HTTP_X_QUEUE_START'])
168
+ val[/(?:^t=)?(\d+)/, 1].to_f / 1000
169
+ else
170
+ Time.now.to_f
170
171
  end
171
172
  end
172
173
 
@@ -174,7 +175,8 @@ module Rack; module Throttle
174
175
  # Outputs a `Rate Limit Exceeded` error.
175
176
  #
176
177
  # @return [Array(Integer, Hash, #each)]
177
- def rate_limit_exceeded
178
+ def rate_limit_exceeded(request)
179
+ options[:rate_limit_exceeded_callback].call(request) if options[:rate_limit_exceeded_callback]
178
180
  headers = respond_to?(:retry_after) ? {'Retry-After' => retry_after.to_f.ceil.to_s} : {}
179
181
  http_error(options[:code] || 403, options[:message] || 'Rate Limit Exceeded', headers)
180
182
  end
@@ -188,7 +190,7 @@ module Rack; module Throttle
188
190
  # @return [Array(Integer, Hash, #each)]
189
191
  def http_error(code, message = nil, headers = {})
190
192
  [code, {'Content-Type' => 'text/plain; charset=utf-8'}.merge(headers),
191
- http_status(code) + (message.nil? ? "\n" : " (#{message})\n")]
193
+ [http_status(code), (message.nil? ? "\n" : " (#{message})\n")]]
192
194
  end
193
195
 
194
196
  ##
@@ -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
@@ -8,8 +8,9 @@ module Rack; module Throttle
8
8
  # @param [Rack::Request] request
9
9
  # @return [Boolean]
10
10
  def allowed?(request)
11
+ return true if whitelisted?(request)
11
12
  count = cache_get(key = cache_key(request)).to_i + 1 rescue 1
12
- allowed = count <= max_per_window
13
+ allowed = count <= max_per_window.to_i
13
14
  begin
14
15
  cache_set(key, count)
15
16
  allowed
metadata CHANGED
@@ -1,88 +1,91 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: rack-throttle
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 3
8
- - 0
9
- version: 0.3.0
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
10
5
  platform: ruby
11
- authors:
6
+ authors:
12
7
  - Arto Bendiken
13
- - Brendon Murphy
14
8
  autorequire:
15
9
  bindir: bin
16
10
  cert_chain: []
17
-
18
- date: 2010-03-22 00:00:00 +01:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
11
+ date: 2014-11-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
22
14
  name: rack-test
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- segments:
29
- - 0
30
- - 5
31
- - 3
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
32
19
  version: 0.5.3
33
20
  type: :development
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: rspec
37
21
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
39
- requirements:
40
- - - ">="
41
- - !ruby/object:Gem::Version
42
- segments:
43
- - 1
44
- - 3
45
- - 0
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.5.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
46
33
  version: 1.3.0
47
34
  type: :development
48
- version_requirements: *id002
49
- - !ruby/object:Gem::Dependency
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.3.0
41
+ - !ruby/object:Gem::Dependency
50
42
  name: yard
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: 0.5.5
48
+ type: :development
51
49
  prerelease: false
52
- requirement: &id003 !ruby/object:Gem::Requirement
53
- requirements:
54
- - - ">="
55
- - !ruby/object:Gem::Version
56
- segments:
57
- - 0
58
- - 5
59
- - 3
60
- version: 0.5.3
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.5.5
55
+ - !ruby/object:Gem::Dependency
56
+ name: timecop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.3.4
61
62
  type: :development
62
- version_requirements: *id003
63
- - !ruby/object:Gem::Dependency
64
- name: rack
65
63
  prerelease: false
66
- requirement: &id004 !ruby/object:Gem::Requirement
67
- requirements:
68
- - - ">="
69
- - !ruby/object:Gem::Version
70
- segments:
71
- - 1
72
- - 0
73
- - 0
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.3.4
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
74
75
  version: 1.0.0
75
76
  type: :runtime
76
- version_requirements: *id004
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: 1.0.0
77
83
  description: Rack middleware for rate-limiting incoming HTTP requests.
78
- email: arto.bendiken@gmail.com
84
+ email: arto@bendiken.net
79
85
  executables: []
80
-
81
86
  extensions: []
82
-
83
87
  extra_rdoc_files: []
84
-
85
- files:
88
+ files:
86
89
  - AUTHORS
87
90
  - README
88
91
  - UNLICENSE
@@ -91,40 +94,33 @@ files:
91
94
  - lib/rack/throttle/hourly.rb
92
95
  - lib/rack/throttle/interval.rb
93
96
  - lib/rack/throttle/limiter.rb
97
+ - lib/rack/throttle/minute.rb
94
98
  - lib/rack/throttle/time_window.rb
95
99
  - lib/rack/throttle/version.rb
96
100
  - lib/rack/throttle.rb
97
- has_rdoc: false
98
- homepage: http://github.com/datagraph
99
- licenses:
101
+ homepage: https://github.com/bendiken/rack-throttle
102
+ licenses:
100
103
  - Public Domain
104
+ metadata: {}
101
105
  post_install_message:
102
106
  rdoc_options: []
103
-
104
- require_paths:
107
+ require_paths:
105
108
  - lib
106
- required_ruby_version: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- segments:
111
- - 1
112
- - 8
113
- - 2
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ! '>='
112
+ - !ruby/object:Gem::Version
114
113
  version: 1.8.2
115
- required_rubygems_version: !ruby/object:Gem::Requirement
116
- requirements:
117
- - - ">="
118
- - !ruby/object:Gem::Version
119
- segments:
120
- - 0
121
- version: "0"
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
122
119
  requirements: []
123
-
124
- rubyforge_project: datagraph
125
- rubygems_version: 1.3.6
120
+ rubyforge_project:
121
+ rubygems_version: 2.0.3
126
122
  signing_key:
127
- specification_version: 3
123
+ specification_version: 4
128
124
  summary: HTTP request rate limiter for Rack applications.
129
125
  test_files: []
130
-
126
+ has_rdoc: false