rate-limiting 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/init.rb +1 -0
- data/lib/rate-limiting/version.rb +5 -0
- data/lib/rate_limiting.rb +125 -0
- data/lib/rule.rb +57 -0
- data/rate-limiting.gemspec +29 -0
- data/readme.md +50 -0
- data/spec/fixed/rpd_spec.rb +17 -0
- data/spec/fixed/rph_spec.rb +16 -0
- data/spec/fixed/rpm_spec.rb +17 -0
- data/spec/frequency/rpd_spec.rb +16 -0
- data/spec/frequency/rph_spec.rb +16 -0
- data/spec/frequency/rpm_spec.rb +17 -0
- data/spec/headers_spec.rb +54 -0
- data/spec/html_request_spec.rb +19 -0
- data/spec/json_request_spec.rb +10 -0
- data/spec/spec_helper.rb +63 -0
- data/spec/token_spec.rb +36 -0
- data/spec/xml_request_spec.rb +13 -0
- metadata +127 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'rate_limiting'
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require "json"
|
2
|
+
require "rule"
|
3
|
+
|
4
|
+
class RateLimiting
|
5
|
+
|
6
|
+
def initialize(app, &block)
|
7
|
+
@app = app
|
8
|
+
@rules = []
|
9
|
+
@cache = {}
|
10
|
+
block.call(self)
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
request = Rack::Request.new(env)
|
15
|
+
(limit_header = allowed?(request)) ? respond(env, limit_header) : rate_limit_exceeded(env['HTTP_ACCEPT'])
|
16
|
+
end
|
17
|
+
|
18
|
+
def respond(env, limit_header)
|
19
|
+
status, header, response = @app.call(env)
|
20
|
+
(limit_header.class == Hash) ? [status, header.merge(limit_header), response] : [status, header, response]
|
21
|
+
end
|
22
|
+
|
23
|
+
def rate_limit_exceeded(accept)
|
24
|
+
case accept.gsub(/;.*/, "").split(',')[0]
|
25
|
+
when "text/xml" then message, type = xml_error("403", "Rate Limit Exceeded"), "text/xml"
|
26
|
+
when "application/json" then message, type = ["Rate Limit Exceeded"].to_json, "application/json"
|
27
|
+
else
|
28
|
+
message, type = ["Rate Limit Exceeded"], "text/html"
|
29
|
+
end
|
30
|
+
[403, {"Content-Type" => type}, message]
|
31
|
+
end
|
32
|
+
|
33
|
+
def define_rule(options)
|
34
|
+
@rules << Rule.new(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_cache(cache)
|
38
|
+
@cache = cache
|
39
|
+
end
|
40
|
+
|
41
|
+
def cache
|
42
|
+
case @cache
|
43
|
+
when Proc then @cache.call
|
44
|
+
else @cache
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def cache_has?(key)
|
49
|
+
case
|
50
|
+
when cache.respond_to?(:has_key?)
|
51
|
+
cache.has_key?(key)
|
52
|
+
when cache.respond_to?(:get)
|
53
|
+
cache.get(key) rescue false
|
54
|
+
else false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def cache_get(key)
|
59
|
+
case
|
60
|
+
when cache.respond_to?(:[])
|
61
|
+
return cache[key]
|
62
|
+
when cache.respond_to?(:get)
|
63
|
+
return cache.get(key) || nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def cache_set(key, value)
|
68
|
+
case
|
69
|
+
when cache.respond_to?(:[])
|
70
|
+
begin
|
71
|
+
cache[key] = value
|
72
|
+
rescue TypeError => e
|
73
|
+
cache[key] = value.to_s
|
74
|
+
end
|
75
|
+
when cache.respond_to?(:set)
|
76
|
+
cache.set(key, value)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def allowed?(request)
|
81
|
+
if rule = find_matching_rule(request)
|
82
|
+
apply_rule(request, rule)
|
83
|
+
else
|
84
|
+
true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def find_matching_rule(request)
|
89
|
+
@rules.each do |rule|
|
90
|
+
return rule if request.path =~ rule.match
|
91
|
+
end
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def apply_rule(request, rule)
|
96
|
+
key = rule.get_key(request)
|
97
|
+
if cache_has?(key)
|
98
|
+
record = cache_get(key)
|
99
|
+
if (reset = record.split(':')[1]) > Time.now.strftime("%d%m%y%H%M%S")
|
100
|
+
if (times = record.split(':')[0].to_i) < rule.limit
|
101
|
+
response = get_header(times + 1, reset, rule.limit)
|
102
|
+
record = record.gsub(/.*:/, "#{times + 1}:")
|
103
|
+
else
|
104
|
+
return false
|
105
|
+
end
|
106
|
+
else
|
107
|
+
response = get_header(1, reset = rule.get_expiration, rule.limit)
|
108
|
+
cache_set(key, "1:" + rule.get_expiration)
|
109
|
+
end
|
110
|
+
else
|
111
|
+
response = get_header(1, reset = rule.get_expiration, rule.limit)
|
112
|
+
cache_set(key, "1:" + rule.get_expiration)
|
113
|
+
end
|
114
|
+
response
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_header(times, reset, limit)
|
118
|
+
{'x-RateLimit-Limit' => limit.to_s, 'x-RateLimit-Remaining' => (limit - times).to_s, 'x-RateLimit-Reset' => reset.to_s }
|
119
|
+
end
|
120
|
+
|
121
|
+
def xml_error(code, message)
|
122
|
+
"<?xml version=\"1.0\"?>\n<error>\n <code>#{code}</code>\n <message>#{message}</message>\n</error>"
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
data/lib/rule.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
class Rule
|
2
|
+
|
3
|
+
def initialize(options)
|
4
|
+
default_options = {
|
5
|
+
:match => /.*/,
|
6
|
+
:metric => :rph,
|
7
|
+
:type => :frequency,
|
8
|
+
:limit => 100,
|
9
|
+
:per_ip => true,
|
10
|
+
:token => false
|
11
|
+
}
|
12
|
+
@options = default_options.merge(options)
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def match
|
17
|
+
@options[:match].class == String ? Regexp.new(@options[:match] + "$") : @options[:match]
|
18
|
+
end
|
19
|
+
|
20
|
+
def limit
|
21
|
+
(@options[:type] == :frequency ? 1 : @options[:limit])
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_expiration
|
25
|
+
(Time.now + ( @options[:type] == :frequency ? get_frequency : get_fixed )).strftime("%d%m%y%H%M%S")
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_frequency
|
29
|
+
case @options[:metric]
|
30
|
+
when :rpd
|
31
|
+
return (86400/@options[:limit] == 0 ? 1 : 86400/@options[:limit])
|
32
|
+
when :rph
|
33
|
+
return (3600/@options[:limit] == 0 ? 1 : 3600/@options[:limit])
|
34
|
+
when :rpm
|
35
|
+
return (60/@options[:limit] == 0 ? 1 : 60/@options[:limit])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_fixed
|
40
|
+
case @options[:metric]
|
41
|
+
when :rpd
|
42
|
+
return 86400
|
43
|
+
when :rph
|
44
|
+
return 3600
|
45
|
+
when :rpm
|
46
|
+
return 60
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_key(request)
|
51
|
+
key = request.path
|
52
|
+
key = key + request.ip.to_s if @options[:per_ip]
|
53
|
+
key = key + request.params[@options[:token].to_s] if @options[:token]
|
54
|
+
key
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rate-limiting/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rate-limiting"
|
7
|
+
s.version = Rate::Limiting::VERSION
|
8
|
+
s.authors = ["alepaez, pnegri"]
|
9
|
+
s.email = ["alexandre@iugu.com.br"]
|
10
|
+
s.homepage = "https://github.com/iugu/rate-limiting"
|
11
|
+
s.summary = %q{Rack Rate-Limit Gem}
|
12
|
+
s.description = %q{Easy way to Rate Limit your Rack app}
|
13
|
+
|
14
|
+
s.rubyforge_project = "rate-limiting"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
|
25
|
+
s.add_development_dependency "rspec"
|
26
|
+
s.add_development_dependency "rack-test"
|
27
|
+
s.add_dependency "json"
|
28
|
+
|
29
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
Rate Limiting
|
2
|
+
===============
|
3
|
+
|
4
|
+
|
5
|
+
How to use it
|
6
|
+
----------------
|
7
|
+
|
8
|
+
**Adding to Rails 3.x**
|
9
|
+
|
10
|
+
\# config/application.rb
|
11
|
+
|
12
|
+
> class Application < Rails::Application
|
13
|
+
>
|
14
|
+
> config.middleware.use RateLimiting do |r|
|
15
|
+
>
|
16
|
+
> r.define_rule( :match => '/resource', :type => :fixed, :metric => :rph, :limit => 300 )
|
17
|
+
>
|
18
|
+
> end
|
19
|
+
>
|
20
|
+
> end
|
21
|
+
|
22
|
+
Rule Options
|
23
|
+
----------------
|
24
|
+
|
25
|
+
**match**
|
26
|
+
|
27
|
+
Accepts aimed resource path or Regexp like '/resource' or "/resource/.*"
|
28
|
+
|
29
|
+
**metric**
|
30
|
+
|
31
|
+
:rpd - Requests per Day
|
32
|
+
|
33
|
+
:rph - Requests per Hour
|
34
|
+
|
35
|
+
:rpm - Requests per Minute
|
36
|
+
|
37
|
+
**type**
|
38
|
+
|
39
|
+
:frequency - 1 request per (time/limit)
|
40
|
+
|
41
|
+
:fixed - limit requests per time
|
42
|
+
|
43
|
+
**token**
|
44
|
+
|
45
|
+
:foo - limit by request parameter 'foo'
|
46
|
+
|
47
|
+
**per_ip**
|
48
|
+
|
49
|
+
Boolean, true = limit by IP
|
50
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Fixed/rpd rule request" do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
it 'should be allowed if not exceed limit' do
|
7
|
+
get '/fixed/rpd', {}, {'HTTP_ACCEPT' => "text/html"}
|
8
|
+
last_response.body.should show_allowed_response
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should not be allowed if exceed limit' do
|
12
|
+
2.times { get '/fixed/rpd', {}, {'HTTP_ACCEPT' => "text/html"} }
|
13
|
+
last_response.body.should show_not_allowed_response
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Fixed/rph rule request" do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
it 'should be allowed if not exceed limit' do
|
7
|
+
get '/fixed/rph', {}, {'HTTP_ACCEPT' => "text/html"}
|
8
|
+
last_response.body.should show_allowed_response
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should not be allowed if exceed limit' do
|
12
|
+
2.times { get '/fixed/rph', {}, {'HTTP_ACCEPT' => "text/html"} }
|
13
|
+
last_response.body.should show_not_allowed_response
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Fixed/rpm rule request" do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
it 'should be allowed if not exceed limit' do
|
7
|
+
get '/fixed/rpm', {}, {'HTTP_ACCEPT' => "text/html"}
|
8
|
+
last_response.body.should show_allowed_response
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should not be allowed if exceed limit' do
|
12
|
+
2.times { get '/fixed/rpm', {}, {'HTTP_ACCEPT' => "text/html"} }
|
13
|
+
last_response.body.should show_not_allowed_response
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Frequency/rpd rule request" do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
it 'should be allowed if not exceed 1 request per min' do
|
7
|
+
get '/freq/rpd', {}, {'HTTP_ACCEPT' => "text/html"}
|
8
|
+
last_response.body.should show_allowed_response
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should not be allowed if exceed 1 request per min' do
|
12
|
+
2.times { get '/freq/rpd', {}, {'HTTP_ACCEPT' => "text/html"} }
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Frequency/rph rule request" do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
it 'should be allowed if not exceed 1 request per min' do
|
7
|
+
get '/freq/rph', {}, {'HTTP_ACCEPT' => "text/html"}
|
8
|
+
last_response.body.should show_allowed_response
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should not be allowed if exceed 1 request per min' do
|
12
|
+
2.times { get '/freq/rph', {}, {'HTTP_ACCEPT' => "text/html"} }
|
13
|
+
last_response.body.should show_not_allowed_response
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Frequency/rpm rule request" do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
it 'should be allowed if not exceed 1 request per min' do
|
7
|
+
get '/freq/rpm', {}, {'HTTP_ACCEPT' => "text/html"}
|
8
|
+
last_response.body.should show_allowed_response
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should not be allowed if exceed 1 request per min' do
|
12
|
+
2.times { get '/freq/rpm', {}, {'HTTP_ACCEPT' => "text/html"} }
|
13
|
+
last_response.body.should show_not_allowed_response
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "response headers" do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
context "limited request" do
|
7
|
+
before(:each) do
|
8
|
+
get '/header', {}, {'HTTP_ACCEPT' => 'text/html'}
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should have x-RateLimit-Limit' do
|
12
|
+
last_response.header.should include "x-RateLimit-Limit"
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should have x-RateLimit-Remaining' do
|
16
|
+
last_response.header.should include "x-RateLimit-Remaining"
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should have x-RateLimit-Reset' do
|
20
|
+
last_response.header.should include "x-RateLimit-Reset"
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should have the right limit' do
|
24
|
+
last_response.header['x-RateLimit-Limit'].should == 1
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should have the right remaining' do
|
28
|
+
last_response.header['x-RateLimit-Remaining'].should == 0
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
context "not limited request" do
|
34
|
+
|
35
|
+
before(:each) do
|
36
|
+
get '/not_limited'
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should have x-RateLimit-Limit' do
|
40
|
+
last_response.header.should_not include "x-RateLimit-Limit"
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should have x-RateLimit-Remaining' do
|
44
|
+
last_response.header.should_not include "x-RateLimit-Remaining"
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should have x-RateLimit-Reset' do
|
48
|
+
last_response.header.should_not include "x-RateLimit-Reset"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "html request" do
|
4
|
+
|
5
|
+
include Rack::Test::Methods
|
6
|
+
|
7
|
+
it 'should receive allowed' do
|
8
|
+
app.should_receive(:allowed?).twice
|
9
|
+
get '/test', {}, {'HTTP_ACCEPT' => "text/html"}
|
10
|
+
get '/test2', {}, {'HTTP_ACCEPT' => "text/html"}
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should receive allowed' do
|
14
|
+
2.times { get '/html', {}, {'HTTP_ACCEPT' => "text/html"} }
|
15
|
+
last_response.content_type.should == "text/html"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'rack/test'
|
3
|
+
require 'rate_limiting'
|
4
|
+
|
5
|
+
def test_app
|
6
|
+
@test_app ||= mock("Test Rack App")
|
7
|
+
@test_app.stub!(:call).with(anything()).and_return([200, {}, "Test App Body"])
|
8
|
+
@test_app
|
9
|
+
end
|
10
|
+
|
11
|
+
def app
|
12
|
+
@test_app ||= test_app
|
13
|
+
@app ||= RateLimiting.new(@test_app) do |r|
|
14
|
+
r.define_rule(:match => '/html', :limit => 1)
|
15
|
+
r.define_rule(:match => '/json', :metric => :rph, :type => :frequency, :limit => 60)
|
16
|
+
r.define_rule(:match => '/xml', :metric => :rph, :type => :frequency, :limit => 60)
|
17
|
+
r.define_rule(:match => '/token/ip', :limit => 1, :token => :id, :per_ip => true)
|
18
|
+
r.define_rule(:match => '/token', :limit => 1, :token => :id, :per_ip => false)
|
19
|
+
r.define_rule(:match => '/fixed/rpm', :metric => :rpm, :type => :fixed, :limit => 1)
|
20
|
+
r.define_rule(:match => '/fixed/rph', :metric => :rph, :type => :fixed, :limit => 1)
|
21
|
+
r.define_rule(:match => '/fixed/rpd', :metric => :rpd, :type => :fixed, :limit => 1)
|
22
|
+
r.define_rule(:match => '/freq/rpm', :metric => :rpm, :type => :frequency, :limit => 1)
|
23
|
+
r.define_rule(:match => '/freq/rph', :metric => :rph, :type => :frequency, :limit => 60)
|
24
|
+
r.define_rule(:match => '/freq/rpd', :metric => :rpd, :type => :frequency, :limit => 1440)
|
25
|
+
r.define_rule(:match => '/header', :metric => :rph, :type => :frequency, :limit => 60)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
Spec::Matchers.define :show_allowed_response do
|
30
|
+
match do |body|
|
31
|
+
body.include?("Test App Body")
|
32
|
+
end
|
33
|
+
|
34
|
+
failure_message_for_should do
|
35
|
+
"expected response to show the allowed response"
|
36
|
+
end
|
37
|
+
|
38
|
+
failure_message_for_should_not do
|
39
|
+
"expected response not to show the allowed response"
|
40
|
+
end
|
41
|
+
|
42
|
+
description do
|
43
|
+
"expected the allowed response"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
Spec::Matchers.define :show_not_allowed_response do
|
48
|
+
match do |body|
|
49
|
+
body.include?("Rate Limit Exceeded")
|
50
|
+
end
|
51
|
+
|
52
|
+
failure_message_for_should do
|
53
|
+
"expected response to show the not allowed response"
|
54
|
+
end
|
55
|
+
|
56
|
+
failure_message_for_should_not do
|
57
|
+
"expected response not to show the not allowed response"
|
58
|
+
end
|
59
|
+
|
60
|
+
description do
|
61
|
+
"expected the not allowed response"
|
62
|
+
end
|
63
|
+
end
|
data/spec/token_spec.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "defined token rule" do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
it 'should allow diferent ids' do
|
7
|
+
get '/token', { :id => "1" }, {'HTTP_ACCEPT' => "text/html"}
|
8
|
+
get '/token', { :id => "2" }, {'HTTP_ACCEPT' => "text/html"}
|
9
|
+
last_response.body.should show_allowed_response
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should not allow equal ids' do
|
13
|
+
2.times { get '/token', { :id => "1" }, {'HTTP_ACCEPT' => "text/html"} }
|
14
|
+
last_response.body.should show_not_allowed_response
|
15
|
+
end
|
16
|
+
|
17
|
+
context "+ per_ip" do
|
18
|
+
|
19
|
+
it 'should allow diferent ids' do
|
20
|
+
get '/token/ip', { :id => "1" }, {'HTTP_ACCEPT' => "text/html"}
|
21
|
+
get '/token/ip', { :id => "2" }, {'HTTP_ACCEPT' => "text/html"}
|
22
|
+
last_response.body.should show_allowed_response
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should not allow equal ids' do
|
26
|
+
2.times { get '/token/ip', { :id => "1" }, {'HTTP_ACCEPT' => "text/html"} }
|
27
|
+
last_response.body.should show_not_allowed_response
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rate-limiting
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- alepaez, pnegri
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rack-test
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: json
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Easy way to Rate Limit your Rack app
|
63
|
+
email:
|
64
|
+
- alexandre@iugu.com.br
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- .rspec
|
71
|
+
- Gemfile
|
72
|
+
- Rakefile
|
73
|
+
- init.rb
|
74
|
+
- lib/rate-limiting/version.rb
|
75
|
+
- lib/rate_limiting.rb
|
76
|
+
- lib/rule.rb
|
77
|
+
- rate-limiting.gemspec
|
78
|
+
- readme.md
|
79
|
+
- spec/fixed/rpd_spec.rb
|
80
|
+
- spec/fixed/rph_spec.rb
|
81
|
+
- spec/fixed/rpm_spec.rb
|
82
|
+
- spec/frequency/rpd_spec.rb
|
83
|
+
- spec/frequency/rph_spec.rb
|
84
|
+
- spec/frequency/rpm_spec.rb
|
85
|
+
- spec/headers_spec.rb
|
86
|
+
- spec/html_request_spec.rb
|
87
|
+
- spec/json_request_spec.rb
|
88
|
+
- spec/spec_helper.rb
|
89
|
+
- spec/token_spec.rb
|
90
|
+
- spec/xml_request_spec.rb
|
91
|
+
homepage: https://github.com/iugu/rate-limiting
|
92
|
+
licenses: []
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ! '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project: rate-limiting
|
111
|
+
rubygems_version: 1.8.23
|
112
|
+
signing_key:
|
113
|
+
specification_version: 3
|
114
|
+
summary: Rack Rate-Limit Gem
|
115
|
+
test_files:
|
116
|
+
- spec/fixed/rpd_spec.rb
|
117
|
+
- spec/fixed/rph_spec.rb
|
118
|
+
- spec/fixed/rpm_spec.rb
|
119
|
+
- spec/frequency/rpd_spec.rb
|
120
|
+
- spec/frequency/rph_spec.rb
|
121
|
+
- spec/frequency/rpm_spec.rb
|
122
|
+
- spec/headers_spec.rb
|
123
|
+
- spec/html_request_spec.rb
|
124
|
+
- spec/json_request_spec.rb
|
125
|
+
- spec/spec_helper.rb
|
126
|
+
- spec/token_spec.rb
|
127
|
+
- spec/xml_request_spec.rb
|