curbit 0.1.2 → 0.2.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.
- data/.gitignore +6 -0
- data/README.rdoc +18 -0
- data/Rakefile +1 -0
- data/VERSION +1 -0
- data/curbit.gemspec +41 -10
- data/lib/curbit.rb +28 -0
- data/test/conditional_controller_test.rb +67 -0
- data/test/standard_controller_test.rb +6 -1
- metadata +25 -17
data/.gitignore
ADDED
data/README.rdoc
CHANGED
@@ -65,6 +65,24 @@ instance as an argument.
|
|
65
65
|
because the Proc is not bound to the controller instance when it's
|
66
66
|
defined. This way, you can at least have access to stuff you might need.)
|
67
67
|
|
68
|
+
=== Conditional rate limiting
|
69
|
+
|
70
|
+
You may want to apply rate limiting only in certain conditions, for example, if
|
71
|
+
a user is authenticated. Curbit provides the :if and :unless parameters that
|
72
|
+
both accept a symbol or Proc. If a symbol is passed then Curbit attempts
|
73
|
+
to execute a method using the symbol as the method name. In the case of the :if parameter,
|
74
|
+
the rate limit is applied if the method returns true. For the :unless paramter, the rate
|
75
|
+
limit is not applied if the method returns true.
|
76
|
+
|
77
|
+
rate_limit :invite,
|
78
|
+
:max_calls => 2,
|
79
|
+
:time_limit => 30.seconds,
|
80
|
+
:wait_time => 1.minute,
|
81
|
+
:unless => :logged_in?
|
82
|
+
|
83
|
+
In this case, the rate limiting is being used with the acts_as_authenticated method logged_in?
|
84
|
+
so that the rate limiting is only applied if the user is not logged in.
|
85
|
+
|
68
86
|
=== Custom message
|
69
87
|
|
70
88
|
You might like to customize the messages returned by CurbIt.
|
data/Rakefile
CHANGED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
data/curbit.gemspec
CHANGED
@@ -1,31 +1,62 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
1
4
|
# -*- encoding: utf-8 -*-
|
2
5
|
|
3
6
|
Gem::Specification.new do |s|
|
4
7
|
s.name = %q{curbit}
|
5
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
6
9
|
|
7
|
-
s.required_rubygems_version = Gem::Requirement.new(">=
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
11
|
s.authors = ["Scott Sayles"]
|
9
|
-
s.date = %q{
|
10
|
-
s.description = %q{
|
12
|
+
s.date = %q{2010-01-26}
|
13
|
+
s.description = %q{Rate limiting for Rails action controllers.}
|
11
14
|
s.email = %q{ssayles@users.sourceforge.net}
|
12
|
-
s.extra_rdoc_files = [
|
13
|
-
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"LICENSE",
|
22
|
+
"Manifest",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"curbit.gemspec",
|
27
|
+
"init.rb",
|
28
|
+
"lib/curbit.rb",
|
29
|
+
"test/conditional_controller_test.rb",
|
30
|
+
"test/custom_key_controller_test.rb",
|
31
|
+
"test/custom_message_format_controller.rb",
|
32
|
+
"test/standard_controller_test.rb",
|
33
|
+
"test/test_helper.rb",
|
34
|
+
"test/test_rails_helper.rb"
|
35
|
+
]
|
14
36
|
s.homepage = %q{http://github.com/ssayles/curbit}
|
15
|
-
s.rdoc_options = ["--
|
37
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
16
38
|
s.require_paths = ["lib"]
|
17
|
-
s.rubyforge_project = %q{curbit}
|
18
39
|
s.rubygems_version = %q{1.3.5}
|
19
|
-
s.summary = %q{
|
20
|
-
s.test_files = [
|
40
|
+
s.summary = %q{Rate limiting for Rails action controllers.}
|
41
|
+
s.test_files = [
|
42
|
+
"test/conditional_controller_test.rb",
|
43
|
+
"test/custom_key_controller_test.rb",
|
44
|
+
"test/custom_message_format_controller.rb",
|
45
|
+
"test/standard_controller_test.rb",
|
46
|
+
"test/test_helper.rb",
|
47
|
+
"test/test_rails_helper.rb"
|
48
|
+
]
|
21
49
|
|
22
50
|
if s.respond_to? :specification_version then
|
23
51
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
52
|
s.specification_version = 3
|
25
53
|
|
26
54
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
55
|
+
s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
27
56
|
else
|
57
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
28
58
|
end
|
29
59
|
else
|
60
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
30
61
|
end
|
31
62
|
end
|
data/lib/curbit.rb
CHANGED
@@ -20,6 +20,8 @@ module Curbit
|
|
20
20
|
# * +wait_time+ - The time to wait if :max_calls has been reached before being able to pass.
|
21
21
|
# * +message+ - The message to render to the client if the call is being limited. The message will be rendered as a correspondingly formatted response with a default status if given a String. If the argument is a symbol, a method with the same name will be invoked with the specified wait_time (in seconds). The called method should take care of rendering the response.
|
22
22
|
# * +status+ - The response status to set when the call is being limited.
|
23
|
+
# * +if+ - A symbol representing a method or Proc that returns true if the rate limiting should be applied.
|
24
|
+
# * +unless+ - A symbol representing a method or Proc that returns true if the rate limiting should NOT be applied.
|
23
25
|
#
|
24
26
|
# ==== Examples
|
25
27
|
#
|
@@ -64,6 +66,9 @@ module Curbit
|
|
64
66
|
def rate_limit_opts_valid?(opts = {})
|
65
67
|
new_opts = {:status => 503}.merge! opts
|
66
68
|
opts.merge! new_opts
|
69
|
+
if opts.key?(:if) and opts.key?(:unless)
|
70
|
+
raise ":unless and :if are mutually exclusive parameters"
|
71
|
+
end
|
67
72
|
if !opts.key?(:max_calls) or !opts.key?(:time_limit) or !opts.key?(:wait_time)
|
68
73
|
raise ":max_calls, :time_limit, and :wait_time are required parameters"
|
69
74
|
end
|
@@ -80,7 +85,28 @@ module Curbit
|
|
80
85
|
"#{CacheKeyPrefix}_#{self.class.name}_#{method}_#{key}"
|
81
86
|
end
|
82
87
|
|
88
|
+
def rate_limit_conditional(opts)
|
89
|
+
if opts.key?(:unless)
|
90
|
+
if opts[:unless].is_a? Proc
|
91
|
+
return true if opts[:unless].call(self)
|
92
|
+
elsif opts[:unless].is_a? Symbol
|
93
|
+
return true if self.send(opts[:unless])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
if opts.key?(:if)
|
97
|
+
if opts[:if].is_a? Proc
|
98
|
+
return true unless opts[:if].call(self)
|
99
|
+
elsif opts[:if].is_a? Symbol
|
100
|
+
return true unless self.send(opts[:if])
|
101
|
+
end
|
102
|
+
end
|
103
|
+
return false
|
104
|
+
end
|
105
|
+
|
83
106
|
def rate_limit_filter(method, opts)
|
107
|
+
|
108
|
+
return true if rate_limit_conditional(opts)
|
109
|
+
|
84
110
|
key = get_key(opts[:key])
|
85
111
|
unless (key)
|
86
112
|
return true
|
@@ -97,6 +123,8 @@ module Curbit
|
|
97
123
|
val[:count] = count + 1
|
98
124
|
started_waiting = val[:started_waiting]
|
99
125
|
|
126
|
+
# did we start making the user wait before being allowed to make
|
127
|
+
# another call?
|
100
128
|
if started_waiting
|
101
129
|
# did we exceed the wait time?
|
102
130
|
if Time.now.to_i > (started_waiting.to_i + opts[:wait_time])
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'test_rails_helper'
|
3
|
+
|
4
|
+
class ConditionalController < ActionController::Base
|
5
|
+
|
6
|
+
include Curbit::Controller
|
7
|
+
|
8
|
+
attr_accessor :rendered
|
9
|
+
|
10
|
+
def index
|
11
|
+
render :text => 'index action'
|
12
|
+
end
|
13
|
+
|
14
|
+
def show
|
15
|
+
render :text => 'show action'
|
16
|
+
end
|
17
|
+
|
18
|
+
rate_limit :index, :max_calls => 2,
|
19
|
+
:time_limit => 30.seconds,
|
20
|
+
:wait_time => 2.minute,
|
21
|
+
:if => :logged_in?
|
22
|
+
|
23
|
+
rate_limit :show, :max_calls => 2,
|
24
|
+
:time_limit => 30.seconds,
|
25
|
+
:wait_time => 2.minute,
|
26
|
+
:unless => :logged_in?
|
27
|
+
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def logged_in?
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
class ConditionalControllerTest < ActionController::TestCase
|
39
|
+
tests ConditionalController
|
40
|
+
|
41
|
+
context "When calling a rate limited method with " do
|
42
|
+
setup {
|
43
|
+
@env = {'HTTP_X_FORWARDED_FOR' => '192.168.1.123'}
|
44
|
+
@request.stubs(:env).returns(@env)
|
45
|
+
cache_value = {:started => Time.now.to_i - 15.seconds,
|
46
|
+
:count => 2
|
47
|
+
}
|
48
|
+
Rails.cache.stubs(:read).returns(cache_value)
|
49
|
+
Rails.cache.stubs(:write)
|
50
|
+
}
|
51
|
+
context ":if, it" do
|
52
|
+
should "should call a method named by the symbol" do
|
53
|
+
get :index
|
54
|
+
assert_equal "503 Service Unavailable", @response.status
|
55
|
+
end
|
56
|
+
end
|
57
|
+
context ":unless, it" do
|
58
|
+
should "should call a method named by the symbol" do
|
59
|
+
get :show
|
60
|
+
assert_equal "show action", @response.body
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
@@ -86,11 +86,16 @@ class CurbiControllerTest < ActionController::TestCase
|
|
86
86
|
}
|
87
87
|
context "and max calls has been exceeded for the current time limit" do
|
88
88
|
setup {
|
89
|
+
# emulate the state needed to indicate that we've started
|
90
|
+
# waiting on calls
|
89
91
|
cache_value = {:started => Time.now.to_i - 15.seconds,
|
90
92
|
:count => 1
|
91
93
|
}
|
92
94
|
Rails.cache.stubs(:read).returns(cache_value)
|
93
|
-
Rails.cache.stubs(:write)
|
95
|
+
Rails.cache.stubs(:write).with() {|key, val, duration|
|
96
|
+
cache_value[:count] = val[:count]
|
97
|
+
true;
|
98
|
+
}
|
94
99
|
}
|
95
100
|
context ", the call" do
|
96
101
|
should "be blocked" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: curbit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scott Sayles
|
@@ -9,11 +9,20 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-01-26 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
16
|
-
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: thoughtbot-shoulda
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: Rate limiting for Rails action controllers.
|
17
26
|
email: ssayles@users.sourceforge.net
|
18
27
|
executables: []
|
19
28
|
|
@@ -22,32 +31,29 @@ extensions: []
|
|
22
31
|
extra_rdoc_files:
|
23
32
|
- LICENSE
|
24
33
|
- README.rdoc
|
25
|
-
- lib/curbit.rb
|
26
34
|
files:
|
35
|
+
- .gitignore
|
27
36
|
- LICENSE
|
37
|
+
- Manifest
|
28
38
|
- README.rdoc
|
29
39
|
- Rakefile
|
40
|
+
- VERSION
|
41
|
+
- curbit.gemspec
|
30
42
|
- init.rb
|
31
43
|
- lib/curbit.rb
|
44
|
+
- test/conditional_controller_test.rb
|
32
45
|
- test/custom_key_controller_test.rb
|
33
46
|
- test/custom_message_format_controller.rb
|
34
47
|
- test/standard_controller_test.rb
|
35
48
|
- test/test_helper.rb
|
36
49
|
- test/test_rails_helper.rb
|
37
|
-
- Manifest
|
38
|
-
- curbit.gemspec
|
39
50
|
has_rdoc: true
|
40
51
|
homepage: http://github.com/ssayles/curbit
|
41
52
|
licenses: []
|
42
53
|
|
43
54
|
post_install_message:
|
44
55
|
rdoc_options:
|
45
|
-
- --
|
46
|
-
- --inline-source
|
47
|
-
- --title
|
48
|
-
- Curbit
|
49
|
-
- --main
|
50
|
-
- README.rdoc
|
56
|
+
- --charset=UTF-8
|
51
57
|
require_paths:
|
52
58
|
- lib
|
53
59
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -60,17 +66,19 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
66
|
requirements:
|
61
67
|
- - ">="
|
62
68
|
- !ruby/object:Gem::Version
|
63
|
-
version: "
|
69
|
+
version: "0"
|
64
70
|
version:
|
65
71
|
requirements: []
|
66
72
|
|
67
|
-
rubyforge_project:
|
73
|
+
rubyforge_project:
|
68
74
|
rubygems_version: 1.3.5
|
69
75
|
signing_key:
|
70
76
|
specification_version: 3
|
71
|
-
summary:
|
77
|
+
summary: Rate limiting for Rails action controllers.
|
72
78
|
test_files:
|
79
|
+
- test/conditional_controller_test.rb
|
73
80
|
- test/custom_key_controller_test.rb
|
81
|
+
- test/custom_message_format_controller.rb
|
74
82
|
- test/standard_controller_test.rb
|
75
83
|
- test/test_helper.rb
|
76
84
|
- test/test_rails_helper.rb
|