improved-rack-throttle 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rack", "~> 1.0.0"
4
+
5
+ group :development, :test do
6
+ gem 'timecop', '~> 0.5.2'
7
+ gem 'rack-test', '~> 0.6.2'
8
+ gem 'rspec', '~> 2.11.0'
9
+ gem 'yard' , '>= 0.5.5'
10
+ gem 'rake'
11
+ gem 'jeweler'
12
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,39 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ git (1.2.5)
6
+ jeweler (1.8.4)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rdoc
11
+ json (1.7.5)
12
+ rack (1.0.1)
13
+ rack-test (0.6.2)
14
+ rack (>= 1.0)
15
+ rake (0.9.2.2)
16
+ rdoc (3.12)
17
+ json (~> 1.4)
18
+ rspec (2.11.0)
19
+ rspec-core (~> 2.11.0)
20
+ rspec-expectations (~> 2.11.0)
21
+ rspec-mocks (~> 2.11.0)
22
+ rspec-core (2.11.1)
23
+ rspec-expectations (2.11.3)
24
+ diff-lcs (~> 1.1.3)
25
+ rspec-mocks (2.11.3)
26
+ timecop (0.5.2)
27
+ yard (0.8.2.1)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ jeweler
34
+ rack (~> 1.0.0)
35
+ rack-test (~> 0.6.2)
36
+ rake
37
+ rspec (~> 2.11.0)
38
+ timecop (~> 0.5.2)
39
+ yard (>= 0.5.5)
data/README.md ADDED
@@ -0,0 +1,213 @@
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 (hourly
17
+ 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
+
28
+ Examples
29
+ --------
30
+
31
+ ### Adding throttling to a Rails 3.x application
32
+
33
+ # config/application.rb
34
+ require 'rack/throttle'
35
+
36
+ class Application < Rails::Application
37
+ config.middleware.use Rack::Throttle::Interval
38
+ end
39
+
40
+ ### Adding throttling to a Sinatra application
41
+
42
+ #!/usr/bin/env ruby -rubygems
43
+ require 'sinatra'
44
+ require 'rack/throttle'
45
+
46
+ use Rack::Throttle::Interval
47
+
48
+ get('/hello') { "Hello, world!\n" }
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
+
57
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, world!\n"] }
58
+
59
+ ### Enforcing a minimum 3-second interval between requests
60
+
61
+ use Rack::Throttle::Interval, :min => 3.0
62
+
63
+ ### Allowing a maximum of 100 requests per hour
64
+
65
+ use Rack::Throttle::Hourly, :max => 100
66
+
67
+ ### Allowing a maximum of 1,000 requests per day
68
+
69
+ use Rack::Throttle::Daily, :max => 1000
70
+
71
+ ### Combining various throttling constraints into one overall policy
72
+
73
+ use Rack::Throttle::Daily, :max => 1000 # requests
74
+ use Rack::Throttle::Hourly, :max => 100 # requests
75
+ use Rack::Throttle::Interval, :min => 3.0 # seconds
76
+
77
+ ### Storing the rate-limiting counters in a GDBM database
78
+
79
+ require 'gdbm'
80
+
81
+ use Rack::Throttle::Interval, :cache => GDBM.new('tmp/throttle.db')
82
+
83
+ ### Storing the rate-limiting counters on a Memcached server
84
+
85
+ require 'memcached'
86
+
87
+ use Rack::Throttle::Interval, :cache => Memcached.new, :key_prefix => :throttle
88
+
89
+ ### Storing the rate-limiting counters on a Redis server
90
+
91
+ require 'redis'
92
+
93
+ use Rack::Throttle::Interval, :cache => Redis.new, :key_prefix => :throttle
94
+
95
+ Throttling Strategies
96
+ ---------------------
97
+
98
+ `Rack::Throttle` supports three built-in throttling strategies:
99
+
100
+ * `Rack::Throttle::Interval`: Throttles the application by enforcing a
101
+ minimum interval (by default, 1 second) between subsequent HTTP requests.
102
+ * `Rack::Throttle::Hourly`: Throttles the application by defining a
103
+ maximum number of allowed HTTP requests per hour (by default, 3,600
104
+ requests per 60 minutes, which works out to an average of 1 request per
105
+ second).
106
+ * `Rack::Throttle::Daily`: Throttles the application by defining a
107
+ maximum number of allowed HTTP requests per day (by default, 86,400
108
+ requests per 24 hours, which works out to an average of 1 request per
109
+ second).
110
+
111
+ You can fully customize the implementation details of any of these strategies
112
+ by simply subclassing one of the aforementioned default implementations.
113
+ And, of course, should your application-specific requirements be
114
+ significantly more complex than what we've provided for, you can also define
115
+ entirely new kinds of throttling strategies by subclassing the
116
+ `Rack::Throttle::Limiter` base class directly.
117
+
118
+ HTTP Client Identification
119
+ --------------------------
120
+
121
+ The rate-limiting counters stored and maintained by `Rack::Throttle` are
122
+ keyed to unique HTTP clients.
123
+
124
+ By default, HTTP clients are uniquely identified by their IP address as
125
+ returned by `Rack::Request#ip`. If you wish to instead use a more granular,
126
+ application-specific identifier such as a session key or a user account
127
+ name, you need only subclass a throttling strategy implementation and
128
+ override the `#client_identifier` method.
129
+
130
+ HTTP Response Codes and Headers
131
+ -------------------------------
132
+
133
+ ### 403 Forbidden (Rate Limit Exceeded)
134
+
135
+ When a client exceeds their rate limit, `Rack::Throttle` by default returns
136
+ a "403 Forbidden" response with an associated "Rate Limit Exceeded" message
137
+ in the response body.
138
+
139
+ An HTTP 403 response means that the server understood the request, but is
140
+ refusing to respond to it and an accompanying message will explain why.
141
+ This indicates an error on the client's part in exceeding the rate limits
142
+ outlined in the acceptable use policy for the site, service, or API.
143
+
144
+ ### 503 Service Unavailable (Rate Limit Exceeded)
145
+
146
+ However, there exists a widespread practice of instead returning a "503
147
+ Service Unavailable" response when a client exceeds the set rate limits.
148
+ This is technically dubious because it indicates an error on the server's
149
+ part, which is certainly not the case with rate limiting - it was the client
150
+ that committed the oops, not the server.
151
+
152
+ An HTTP 503 response would be correct in situations where the server was
153
+ genuinely overloaded and couldn't handle more requests, but for rate
154
+ limiting an HTTP 403 response is more appropriate. Nonetheless, if you think
155
+ otherwise, `Rack::Throttle` does allow you to override the returned HTTP
156
+ status code by passing in a `:code => 503` option when constructing a
157
+ `Rack::Throttle::Limiter` instance.
158
+
159
+ Documentation
160
+ -------------
161
+
162
+ <http://datagraph.rubyforge.org/rack-throttle/>
163
+
164
+ * {Rack::Throttle}
165
+ * {Rack::Throttle::Interval}
166
+ * {Rack::Throttle::Daily}
167
+ * {Rack::Throttle::Hourly}
168
+
169
+ Dependencies
170
+ ------------
171
+
172
+ * [Rack](http://rubygems.org/gems/rack) (>= 1.0.0)
173
+
174
+ Installation
175
+ ------------
176
+
177
+ The recommended installation method is via [RubyGems](http://rubygems.org/).
178
+ To install the latest official release of the gem, do:
179
+
180
+ % [sudo] gem install rack-throttle
181
+
182
+ Download
183
+ --------
184
+
185
+ To get a local working copy of the development repository, do:
186
+
187
+ % git clone git://github.com/datagraph/rack-throttle.git
188
+
189
+ Alternatively, you can download the latest development version as a tarball
190
+ as follows:
191
+
192
+ % wget http://github.com/datagraph/rack-throttle/tarball/master
193
+
194
+ Authors
195
+ -------
196
+
197
+ * [Arto Bendiken](mailto:arto.bendiken@gmail.com) - <http://ar.to/>
198
+ * [Brendon Murphy](mailto:disposable.20.xternal@spamourmet.com>) - <http://www.techfreak.net/>
199
+
200
+ License
201
+ -------
202
+
203
+ `Rack::Throttle` is free and unencumbered public domain software. For more
204
+ information, see <http://unlicense.org/> or the accompanying UNLICENSE file.
205
+
206
+ [Rack]: http://rack.rubyforge.org/
207
+ [gdbm]: http://ruby-doc.org/stdlib/libdoc/gdbm/rdoc/classes/GDBM.html
208
+ [memcached]: http://rubygems.org/gems/memcached
209
+ [memcache-client]: http://rubygems.org/gems/memcache-client
210
+ [memcache]: http://rubygems.org/gems/memcache
211
+ [redis]: http://rubygems.org/gems/redis
212
+ [Heroku]: http://heroku.com/
213
+ [Heroku memcache]: http://docs.heroku.com/memcache
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ $:.push File.join(File.dirname(__FILE__),'lib')
3
+
4
+ require 'rubygems'
5
+ require 'bundler'
6
+ begin
7
+ Bundler.setup(:default, :development)
8
+ rescue Bundler::BundlerError => e
9
+ $stderr.puts e.message
10
+ $stderr.puts "Run `bundle install` to install missing gems"
11
+ exit e.status_code
12
+ end
13
+ require 'rake'
14
+
15
+ require 'rack/throttle'
16
+ require 'jeweler'
17
+ Jeweler::Tasks.new do |gem|
18
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
19
+ gem.name = "improved-rack-throttle"
20
+ gem.version = Rack::Throttle::VERSION
21
+ gem.homepage = "http://github.com/bensomers/improved-rack-throttle"
22
+ gem.license = "Public Domain"
23
+ gem.summary = %Q{HTTP request rate limiter for Rack applications.}
24
+ gem.description = %Q{Rack middleware for rate-limiting incoming HTTP requests.}
25
+ gem.email = "somers.ben@gmail.com"
26
+ gem.authors = ["Ben Somers", "Arto Bendiken", "Brendon Murphy"]
27
+ # dependencies defined in Gemfile
28
+ end
29
+ Jeweler::RubygemsDotOrgTasks.new
30
+
31
+ task :default => :spec
32
+
33
+ require 'rdoc/task'
34
+ Rake::RDocTask.new do |rdoc|
35
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
36
+
37
+ rdoc.rdoc_dir = 'rdoc'
38
+ rdoc.title = "improved-rack-throttle #{version}"
39
+ rdoc.rdoc_files.include('README*')
40
+ rdoc.rdoc_files.include('lib/**/*.rb')
41
+ end
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/>
data/doc/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ rdoc
2
+ yard
data/etc/gdbm.ru ADDED
@@ -0,0 +1,7 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'rack/throttle'
3
+ require 'gdbm'
4
+
5
+ use Rack::Throttle::Interval, :min => 3.0, :cache => GDBM.new('/tmp/throttle.db')
6
+
7
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, world!\n"] }
data/etc/hash.ru ADDED
@@ -0,0 +1,6 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'rack/throttle'
3
+
4
+ use Rack::Throttle::Interval, :min => 3.0, :cache => {}
5
+
6
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, world!\n"] }
@@ -0,0 +1,8 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'rack/throttle'
3
+ gem 'memcache-client'
4
+ require 'memcache'
5
+
6
+ use Rack::Throttle::Interval, :min => 3.0, :cache => MemCache.new('localhost:11211')
7
+
8
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, world!\n"] }
data/etc/memcache.ru ADDED
@@ -0,0 +1,8 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'rack/throttle'
3
+ gem 'memcache'
4
+ require 'memcache'
5
+
6
+ use Rack::Throttle::Interval, :min => 3.0, :cache => Memcache.new(:server => 'localhost:11211')
7
+
8
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, world!\n"] }
data/etc/memcached.ru ADDED
@@ -0,0 +1,8 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'rack/throttle'
3
+ gem 'memcached'
4
+ require 'memcached'
5
+
6
+ use Rack::Throttle::Interval, :min => 3.0, :cache => Memcached.new
7
+
8
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, world!\n"] }
data/etc/redis.ru ADDED
@@ -0,0 +1,8 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'rack/throttle'
3
+ gem 'redis'
4
+ require 'redis'
5
+
6
+ use Rack::Throttle::Interval, :min => 3.0, :cache => Redis.new
7
+
8
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, world!\n"] }
@@ -0,0 +1,85 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "improved-rack-throttle"
8
+ s.version = "0.5.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ben Somers", "Arto Bendiken", "Brendon Murphy"]
12
+ s.date = "2012-10-03"
13
+ s.description = "Rack middleware for rate-limiting incoming HTTP requests."
14
+ s.email = "somers.ben@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "UNLICENSE",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ "README.md",
21
+ "Rakefile",
22
+ "UNLICENSE",
23
+ "doc/.gitignore",
24
+ "etc/gdbm.ru",
25
+ "etc/hash.ru",
26
+ "etc/memcache-client.ru",
27
+ "etc/memcache.ru",
28
+ "etc/memcached.ru",
29
+ "etc/redis.ru",
30
+ "lib/rack/throttle.rb",
31
+ "lib/rack/throttle/daily.rb",
32
+ "lib/rack/throttle/hourly.rb",
33
+ "lib/rack/throttle/interval.rb",
34
+ "lib/rack/throttle/limiter.rb",
35
+ "lib/rack/throttle/matcher.rb",
36
+ "lib/rack/throttle/matchers/ip_matcher.rb",
37
+ "lib/rack/throttle/matchers/method_matcher.rb",
38
+ "lib/rack/throttle/matchers/url_matcher.rb",
39
+ "lib/rack/throttle/time_window.rb",
40
+ "lib/rack/throttle/version.rb",
41
+ "spec/daily_spec.rb",
42
+ "spec/hourly_spec.rb",
43
+ "spec/interval_spec.rb",
44
+ "spec/limiter_spec.rb",
45
+ "spec/method_matcher_spec.rb",
46
+ "spec/spec_helper.rb",
47
+ "spec/url_matcher_spec.rb"
48
+ ]
49
+ s.homepage = "http://github.com/bensomers/improved-rack-throttle"
50
+ s.licenses = ["Public Domain"]
51
+ s.require_paths = ["lib"]
52
+ s.rubygems_version = "1.8.17"
53
+ s.summary = "HTTP request rate limiter for Rack applications."
54
+
55
+ if s.respond_to? :specification_version then
56
+ s.specification_version = 3
57
+
58
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
59
+ s.add_runtime_dependency(%q<rack>, ["~> 1.0.0"])
60
+ s.add_development_dependency(%q<timecop>, ["~> 0.5.2"])
61
+ s.add_development_dependency(%q<rack-test>, ["~> 0.6.2"])
62
+ s.add_development_dependency(%q<rspec>, ["~> 2.11.0"])
63
+ s.add_development_dependency(%q<yard>, [">= 0.5.5"])
64
+ s.add_development_dependency(%q<rake>, [">= 0"])
65
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
66
+ else
67
+ s.add_dependency(%q<rack>, ["~> 1.0.0"])
68
+ s.add_dependency(%q<timecop>, ["~> 0.5.2"])
69
+ s.add_dependency(%q<rack-test>, ["~> 0.6.2"])
70
+ s.add_dependency(%q<rspec>, ["~> 2.11.0"])
71
+ s.add_dependency(%q<yard>, [">= 0.5.5"])
72
+ s.add_dependency(%q<rake>, [">= 0"])
73
+ s.add_dependency(%q<jeweler>, [">= 0"])
74
+ end
75
+ else
76
+ s.add_dependency(%q<rack>, ["~> 1.0.0"])
77
+ s.add_dependency(%q<timecop>, ["~> 0.5.2"])
78
+ s.add_dependency(%q<rack-test>, ["~> 0.6.2"])
79
+ s.add_dependency(%q<rspec>, ["~> 2.11.0"])
80
+ s.add_dependency(%q<yard>, [">= 0.5.5"])
81
+ s.add_dependency(%q<rake>, [">= 0"])
82
+ s.add_dependency(%q<jeweler>, [">= 0"])
83
+ end
84
+ end
85
+