railslove-rack-throttle 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 on the minute (according to the server's local
11
+ # timezone).
12
+ #
13
+ # @example Allowing up to 3,600 requests per minute
14
+ # use Rack::Throttle::PerMinute
15
+ #
16
+ # @example Allowing up to 100 requests per minute
17
+ # use Rack::Throttle::PerMinute, :max => 100
18
+ #
19
+ class PerMinute < 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
+ def max_per_minute
29
+ @max_per_minute ||= 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 = 3
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
@@ -0,0 +1,84 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{railslove-rack-throttle}
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Arto Bendiken", "Brendon Murphy", "reddavis"]
12
+ s.date = %q{2010-07-14}
13
+ s.description = %q{Rack middleware for rate-limiting incoming HTTP requests.}
14
+ s.email = %q{reddavis@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ ".yardopts",
22
+ "AUTHORS",
23
+ "README",
24
+ "README.md",
25
+ "Rakefile",
26
+ "UNLICENSE",
27
+ "VERSION",
28
+ "doc/.gitignore",
29
+ "etc/gdbm.ru",
30
+ "etc/hash.ru",
31
+ "etc/memcache-client.ru",
32
+ "etc/memcache.ru",
33
+ "etc/memcached.ru",
34
+ "etc/redis.ru",
35
+ "lib/rack/throttle.rb",
36
+ "lib/rack/throttle/daily.rb",
37
+ "lib/rack/throttle/hourly.rb",
38
+ "lib/rack/throttle/interval.rb",
39
+ "lib/rack/throttle/limiter.rb",
40
+ "lib/rack/throttle/per_minute.rb",
41
+ "lib/rack/throttle/time_window.rb",
42
+ "lib/rack/throttle/version.rb",
43
+ "railslove-rack-throttle.gemspec",
44
+ "spec/daily_spec.rb",
45
+ "spec/hourly_spec.rb",
46
+ "spec/interval_spec.rb",
47
+ "spec/limiter_spec.rb",
48
+ "spec/per_minute_spec.rb",
49
+ "spec/spec_helper.rb"
50
+ ]
51
+ s.homepage = %q{http://github.com/railslove/rack-throttle}
52
+ s.rdoc_options = ["--charset=UTF-8"]
53
+ s.require_paths = ["lib"]
54
+ s.rubygems_version = %q{1.3.6}
55
+ s.summary = %q{Extension of rack-throttle - HTTP request rate limiter for Rack applications.}
56
+ s.test_files = [
57
+ "spec/daily_spec.rb",
58
+ "spec/hourly_spec.rb",
59
+ "spec/interval_spec.rb",
60
+ "spec/limiter_spec.rb",
61
+ "spec/per_minute_spec.rb",
62
+ "spec/spec_helper.rb"
63
+ ]
64
+
65
+ if s.respond_to? :specification_version then
66
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
67
+ s.specification_version = 3
68
+
69
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
70
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
71
+ s.add_development_dependency(%q<rack-test>, [">= 0.5.3"])
72
+ s.add_runtime_dependency(%q<rack>, [">= 1.0.0"])
73
+ else
74
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
75
+ s.add_dependency(%q<rack-test>, [">= 0.5.3"])
76
+ s.add_dependency(%q<rack>, [">= 1.0.0"])
77
+ end
78
+ else
79
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
80
+ s.add_dependency(%q<rack-test>, [">= 0.5.3"])
81
+ s.add_dependency(%q<rack>, [">= 1.0.0"])
82
+ end
83
+ end
84
+
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Rack::Throttle::Daily do
4
+ include Rack::Test::Methods
5
+
6
+ before do
7
+ def app
8
+ @target_app ||= example_target_app
9
+ @app ||= Rack::Throttle::Daily.new(@target_app, :max_per_day => 3)
10
+ end
11
+ end
12
+
13
+ it "should be allowed if not seen this day" do
14
+ get "/foo"
15
+ last_response.body.should show_allowed_response
16
+ end
17
+
18
+ it "should be allowed if seen fewer than the max allowed per day" do
19
+ 2.times { get "/foo" }
20
+ last_response.body.should show_allowed_response
21
+ end
22
+
23
+ it "should not be allowed if seen more times than the max allowed per day" do
24
+ 4.times { get "/foo" }
25
+ last_response.body.should show_throttled_response
26
+ end
27
+
28
+ it "should be allowed if we are in a new day" do
29
+ Time.stub!(:now).and_return(Time.local(2000,"jan",1,0,0,0))
30
+
31
+ 4.times { get "/foo" }
32
+ last_response.body.should show_throttled_response
33
+
34
+ forward_one_minute = Time.now + 60*60*24
35
+ Time.stub!(:now).and_return(forward_one_minute)
36
+
37
+ get "/foo"
38
+ last_response.body.should show_allowed_response
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Rack::Throttle::Hourly do
4
+ include Rack::Test::Methods
5
+
6
+ before do
7
+ def app
8
+ @target_app ||= example_target_app
9
+ @app ||= Rack::Throttle::Hourly.new(@target_app, :max_per_hour => 3)
10
+ end
11
+ end
12
+
13
+ it "should be allowed if not seen this hour" do
14
+ get "/foo"
15
+ last_response.body.should show_allowed_response
16
+ end
17
+
18
+ it "should be allowed if seen fewer than the max allowed per hour" do
19
+ 2.times { get "/foo" }
20
+ last_response.body.should show_allowed_response
21
+ end
22
+
23
+ it "should not be allowed if seen more times than the max allowed per hour" do
24
+ 4.times { get "/foo" }
25
+ last_response.body.should show_throttled_response
26
+ end
27
+
28
+ it "should be allowed if we are in a new hour" do
29
+ Time.stub!(:now).and_return(Time.local(2000,"jan",1,0,0,0))
30
+
31
+ 4.times { get "/foo" }
32
+ last_response.body.should show_throttled_response
33
+
34
+ forward_one_minute = Time.now + 60*60
35
+ Time.stub!(:now).and_return(forward_one_minute)
36
+
37
+ get "/foo"
38
+ last_response.body.should show_allowed_response
39
+ end
40
+ end
@@ -0,0 +1,41 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Rack::Throttle::Interval do
4
+ include Rack::Test::Methods
5
+
6
+ before do
7
+ def app
8
+ @target_app ||= example_target_app
9
+ @app ||= Rack::Throttle::Interval.new(@target_app, :min => 0.1)
10
+ end
11
+ end
12
+
13
+ it "should allow the request if the source has not been seen" do
14
+ get "/foo"
15
+ last_response.body.should show_allowed_response
16
+ end
17
+
18
+ it "should allow the request if the source has not been seen in the current interval" do
19
+ get "/foo"
20
+ sleep 0.2 # Should time travel this instead?
21
+ get "/foo"
22
+ last_response.body.should show_allowed_response
23
+ end
24
+
25
+ it "should not all the request if the source has been seen inside the current interval" do
26
+ 2.times { get "/foo" }
27
+ last_response.body.should show_throttled_response
28
+ end
29
+
30
+ it "should gracefully allow the request if the cache bombs on getting" do
31
+ app.should_receive(:cache_get).and_raise(StandardError)
32
+ get "/foo"
33
+ last_response.body.should show_allowed_response
34
+ end
35
+
36
+ it "should gracefully allow the request if the cache bombs on setting" do
37
+ app.should_receive(:cache_set).and_raise(StandardError)
38
+ get "/foo"
39
+ last_response.body.should show_allowed_response
40
+ end
41
+ end
@@ -0,0 +1,64 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Rack::Throttle::Limiter do
4
+ include Rack::Test::Methods
5
+
6
+ before do
7
+ def app
8
+ @target_app ||= example_target_app
9
+ @app ||= Rack::Throttle::Limiter.new(@target_app)
10
+ end
11
+ end
12
+
13
+ describe "basic calling" do
14
+ it "should return the example app" do
15
+ get "/foo"
16
+ last_response.body.should show_allowed_response
17
+ end
18
+
19
+ it "should call the application if allowed" do
20
+ app.should_receive(:allowed?).and_return(true)
21
+ get "/foo"
22
+ last_response.body.should show_allowed_response
23
+ end
24
+
25
+ it "should give a rate limit exceeded message if not allowed" do
26
+ app.should_receive(:allowed?).and_return(false)
27
+ get "/foo"
28
+ last_response.body.should show_throttled_response
29
+ end
30
+ end
31
+
32
+ describe "allowed?" do
33
+ it "should return true if whitelisted" do
34
+ app.should_receive(:whitelisted?).and_return(true)
35
+ get "/foo"
36
+ last_response.body.should show_allowed_response
37
+ end
38
+
39
+ it "should return false if blacklisted" do
40
+ app.should_receive(:blacklisted?).and_return(true)
41
+ get "/foo"
42
+ last_response.body.should show_throttled_response
43
+ end
44
+
45
+ it "should return true if not whitelisted or blacklisted" do
46
+ app.should_receive(:whitelisted?).and_return(false)
47
+ app.should_receive(:blacklisted?).and_return(false)
48
+ get "/foo"
49
+ last_response.body.should show_allowed_response
50
+ end
51
+
52
+ it "should call proc when false" do
53
+ #proc = mock("test")
54
+ #@app = Rack::Throttle::Limiter.new(@target_app, :on_reject => proc)
55
+ #
56
+ #app.should_receive(:allowed?).and_return(false)
57
+ #proc.should_receive(:call).once
58
+ #
59
+ #get "/foo"
60
+ #
61
+ #@app = Rack::Throttle::Limiter.new(@target_app)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Rack::Throttle::PerMinute do
4
+ include Rack::Test::Methods
5
+
6
+ before do
7
+ def app
8
+ @target_app ||= example_target_app
9
+ @app ||= Rack::Throttle::PerMinute.new(@target_app, :max_per_minute => 3)
10
+ end
11
+ end
12
+
13
+ it "should be allowed if not seen this hour" do
14
+ get "/foo"
15
+ last_response.body.should show_allowed_response
16
+ end
17
+
18
+ it "should be allowed if seen fewer than the max allowed per hour" do
19
+ 2.times { get "/foo" }
20
+ last_response.body.should show_allowed_response
21
+ end
22
+
23
+ it "should not be allowed if seen more times than the max allowed per hour" do
24
+ 4.times { get "/foo" }
25
+ last_response.body.should show_throttled_response
26
+ end
27
+
28
+ it "should be allowed if we are in a new minute" do
29
+ Time.stub!(:now).and_return(Time.local(2000,"jan",1,0,0,0))
30
+
31
+ 4.times { get "/foo" }
32
+ last_response.body.should show_throttled_response
33
+
34
+ forward_one_minute = Time.now + 60
35
+ Time.stub!(:now).and_return(forward_one_minute)
36
+
37
+ get "/foo"
38
+ last_response.body.should show_allowed_response
39
+ end
40
+ end
@@ -0,0 +1,49 @@
1
+ # So that the installed throttle gem doesnt interfere
2
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
3
+
4
+ require 'rubygems'
5
+ require "spec"
6
+ require 'spec/autorun'
7
+ require "rack/test"
8
+ require "rack/throttle"
9
+
10
+ def example_target_app
11
+ @target_app ||= mock("Example Rack App")
12
+ @target_app.stub!(:call).and_return([200, {}, "Example App Body"])
13
+ end
14
+
15
+ Spec::Matchers.define :show_allowed_response do
16
+ match do |body|
17
+ body.include?("Example App Body")
18
+ end
19
+
20
+ failure_message_for_should do
21
+ "expected response to show the allowed response"
22
+ end
23
+
24
+ failure_message_for_should_not do
25
+ "expected response not to show the allowed response"
26
+ end
27
+
28
+ description do
29
+ "expected the allowed response"
30
+ end
31
+ end
32
+
33
+ Spec::Matchers.define :show_throttled_response do
34
+ match do |body|
35
+ body.include?("Rate Limit Exceeded")
36
+ end
37
+
38
+ failure_message_for_should do
39
+ "expected response to show the throttled response"
40
+ end
41
+
42
+ failure_message_for_should_not do
43
+ "expected response not to show the throttled response"
44
+ end
45
+
46
+ description do
47
+ "expected the throttled response"
48
+ end
49
+ end