rack-ratelimiter 0.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 +9 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +22 -0
- data/README.md +22 -0
- data/Rakefile +12 -0
- data/lib/rack/rate_limiter.rb +80 -0
- data/lib/rack/rate_limiter/version.rb +3 -0
- data/rack-ratelimiter.gemspec +25 -0
- data/test/limiter_test.rb +64 -0
- data/test/test_helper.rb +6 -0
- metadata +127 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rack-limiter (0.0.1)
|
5
|
+
chronic (>= 0.4.3)
|
6
|
+
rack (>= 1.0.0)
|
7
|
+
redis-namespace (~> 1.0.2)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: http://rubygems.org/
|
11
|
+
specs:
|
12
|
+
chronic (0.4.3)
|
13
|
+
rack (1.3.0)
|
14
|
+
redis (2.2.1)
|
15
|
+
redis-namespace (1.0.3)
|
16
|
+
redis (< 3.0.0)
|
17
|
+
|
18
|
+
PLATFORMS
|
19
|
+
ruby
|
20
|
+
|
21
|
+
DEPENDENCIES
|
22
|
+
rack-limiter!
|
data/README.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Rack Limiter
|
2
|
+
============
|
3
|
+
Simple redis-backed rack middleware to limit incoming http requests. Extracted from Screenfunk.com
|
4
|
+
|
5
|
+
## Configuration for Rails apps
|
6
|
+
|
7
|
+
Add rack-ratelimiter to your Gemfile
|
8
|
+
|
9
|
+
gem 'rack-racklimiter', :git => "git://github.com/krmdrms/rack-ratelimiter.git",:require => 'rack/rack_limiter'
|
10
|
+
|
11
|
+
This will limit all incoming requests
|
12
|
+
|
13
|
+
config.middleware.use "Rack::RateLimiter", :interval => 60, :max_requests => 50, :redis_namespace => 'rack_limiter'
|
14
|
+
|
15
|
+
We have Public API @screenfunk which runs on the same rails instance with different domain
|
16
|
+
|
17
|
+
config.middleware.use "Rack::RateLimiter", :interval => 60, :max_requests => 50, :redis_namespace => 'rack_limiter', :limit => {:domain => 'api.screenfunk.com'}
|
18
|
+
|
19
|
+
with :limit parameter server only listens given domain.
|
20
|
+
|
21
|
+
#License
|
22
|
+
(The MIT License) Copyright © 2011 Kerem Durmus
|
data/Rakefile
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require "rack"
|
2
|
+
require "redis/namespace"
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
class RateLimiter
|
7
|
+
def initialize(app, options={})
|
8
|
+
@app = app
|
9
|
+
@options ={
|
10
|
+
:redis => nil,
|
11
|
+
:limit => nil,
|
12
|
+
:max_requests => 60,
|
13
|
+
:interval => 60, # in seconds
|
14
|
+
:redis_namespace => 'rate_limiter'
|
15
|
+
}.merge(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env, options={})
|
19
|
+
request = Rack::Request.new(env)
|
20
|
+
status, headers, body = @app.call(env)
|
21
|
+
|
22
|
+
return status, headers, body unless rate_limited?(request)
|
23
|
+
|
24
|
+
begin
|
25
|
+
key = generate_key(request)
|
26
|
+
redis.setex(key, @options[:interval], @options[:max_requests]) unless redis.exists(key)
|
27
|
+
@rate_remaining = redis.decr(key)
|
28
|
+
|
29
|
+
return limit_exceeded! if @rate_remaining < 0
|
30
|
+
rescue ::Errno::ECONNREFUSED
|
31
|
+
return [status, headers, body]
|
32
|
+
end
|
33
|
+
|
34
|
+
headers.merge!({'X-RateLimit-Limit' => @options[:max_requests].to_s,
|
35
|
+
'X-RateLimit-Remaining' => @rate_remaining.to_s
|
36
|
+
})
|
37
|
+
|
38
|
+
return status, headers, body
|
39
|
+
end
|
40
|
+
|
41
|
+
def generate_key(request)
|
42
|
+
"#{client_id(request)}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def client_id(request)
|
46
|
+
request.ip.to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
def rate_limited?(request)
|
50
|
+
limit = @options[:limit]
|
51
|
+
|
52
|
+
unless limit[:domain].nil?
|
53
|
+
return request.host == limit[:domain]
|
54
|
+
end
|
55
|
+
|
56
|
+
return false
|
57
|
+
end
|
58
|
+
|
59
|
+
def limit_exceeded!
|
60
|
+
code = 403
|
61
|
+
body = {:error => {:code => 403, :message => "Rate Limit Exceeded"}}.to_json
|
62
|
+
[code,
|
63
|
+
{'Content-Type' => 'application/json; charset=utf-8',
|
64
|
+
'Content-Lenght' => body.size.to_s
|
65
|
+
}, body]
|
66
|
+
end
|
67
|
+
|
68
|
+
def redis
|
69
|
+
if @options[:redis].nil?
|
70
|
+
begin
|
71
|
+
@redis = Redis::Namespace.new(@options[:redis_namespace], :redis => Redis.new(:thread_safe => true))
|
72
|
+
rescue Errno::ECONNREFUSED
|
73
|
+
end
|
74
|
+
else
|
75
|
+
@redis = @options[:redis]
|
76
|
+
end
|
77
|
+
@redis
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rack/rate_limiter/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rack-ratelimiter"
|
7
|
+
s.version = RateLimiter::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Kerem Durmus"]
|
10
|
+
s.email = ["kerem@keremdurmus.com"]
|
11
|
+
s.homepage = "http://github.com/krmdrms/rack-ratelimiter"
|
12
|
+
s.summary = %q{Simple rate-limit middleware}
|
13
|
+
s.description = %q{Redis backed rack middleware for rate-limiting http requests}
|
14
|
+
|
15
|
+
s.rubyforge_project = "rack-ratelimiter"
|
16
|
+
|
17
|
+
s.required_rubygems_version = ">= 1.3.6"
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency 'rack', '>= 1.0.0'
|
23
|
+
s.add_dependency 'redis-namespace', '>= 0.10.0'
|
24
|
+
s.add_dependency "chronic", ">= 0.4.3"
|
25
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "test/test_helper"
|
2
|
+
|
3
|
+
class RackLimiterTest < Test::Unit::TestCase
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
def app
|
7
|
+
@app = TestApp.new
|
8
|
+
@rack = Rack::RateLimiter.new(@app, {
|
9
|
+
:interval => 60,
|
10
|
+
:max_requests => 5,
|
11
|
+
:redis_namespace => 'rack_limiter_test',
|
12
|
+
:limit => {:domain => 'api.example.com'}
|
13
|
+
}
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def redis
|
18
|
+
@redis = Redis::Namespace.new('rack_limiter_test', :redis => Redis.new)
|
19
|
+
end
|
20
|
+
|
21
|
+
def flush_redis!
|
22
|
+
redis.keys("*").each do |key|
|
23
|
+
redis.del(key)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def setup
|
28
|
+
flush_redis!
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_request
|
32
|
+
get 'http://example.com/'
|
33
|
+
|
34
|
+
assert_equal 200, last_response.status
|
35
|
+
assert_equal "Hello, World!", last_response.body
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_rate_limited_request
|
39
|
+
5.times do
|
40
|
+
get 'http://api.example.com/'
|
41
|
+
assert_equal 200, last_response.status
|
42
|
+
end
|
43
|
+
|
44
|
+
get 'http://api.example.com/'
|
45
|
+
body = {:error => {:code => 403, :message => "Rate Limit Exceeded"}}.to_json
|
46
|
+
assert_equal 403, last_response.status
|
47
|
+
assert_equal body, last_response.body
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_rate_limited?
|
51
|
+
10.times do
|
52
|
+
get 'http://hello.example.com/'
|
53
|
+
assert_equal 200, last_response.status
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class TestApp
|
58
|
+
def call(env)
|
59
|
+
[200, {
|
60
|
+
'Content-Type' => 'text/html; charset=utf-8',
|
61
|
+
}, ["Hello, World!"]]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-ratelimiter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Kerem Durmus
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-07-07 00:00:00 +03:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rack
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
- 0
|
34
|
+
version: 1.0.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: redis-namespace
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 55
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 10
|
49
|
+
- 0
|
50
|
+
version: 0.10.0
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: chronic
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 9
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
- 4
|
65
|
+
- 3
|
66
|
+
version: 0.4.3
|
67
|
+
type: :runtime
|
68
|
+
version_requirements: *id003
|
69
|
+
description: Redis backed rack middleware for rate-limiting http requests
|
70
|
+
email:
|
71
|
+
- kerem@keremdurmus.com
|
72
|
+
executables: []
|
73
|
+
|
74
|
+
extensions: []
|
75
|
+
|
76
|
+
extra_rdoc_files: []
|
77
|
+
|
78
|
+
files:
|
79
|
+
- .gitignore
|
80
|
+
- Gemfile
|
81
|
+
- Gemfile.lock
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- lib/rack/rate_limiter.rb
|
85
|
+
- lib/rack/rate_limiter/version.rb
|
86
|
+
- rack-ratelimiter.gemspec
|
87
|
+
- test/limiter_test.rb
|
88
|
+
- test/test_helper.rb
|
89
|
+
has_rdoc: true
|
90
|
+
homepage: http://github.com/krmdrms/rack-ratelimiter
|
91
|
+
licenses: []
|
92
|
+
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
hash: 3
|
104
|
+
segments:
|
105
|
+
- 0
|
106
|
+
version: "0"
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
hash: 23
|
113
|
+
segments:
|
114
|
+
- 1
|
115
|
+
- 3
|
116
|
+
- 6
|
117
|
+
version: 1.3.6
|
118
|
+
requirements: []
|
119
|
+
|
120
|
+
rubyforge_project: rack-ratelimiter
|
121
|
+
rubygems_version: 1.5.3
|
122
|
+
signing_key:
|
123
|
+
specification_version: 3
|
124
|
+
summary: Simple rate-limit middleware
|
125
|
+
test_files:
|
126
|
+
- test/limiter_test.rb
|
127
|
+
- test/test_helper.rb
|