rack-throttle 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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