rate-limiting 1.0.2
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 +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
|