rack-ratelimiter 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ # Built gems
2
+ pkg
3
+ *.gem
4
+
5
+ # Sublimetext
6
+ *.sublime-project
7
+ .lock
8
+ .bundle
9
+ .ru
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rack-limiter.gemspec
4
+ gemspec
@@ -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!
@@ -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
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'test'
8
+ t.pattern = 'test/*_test.rb'
9
+ t.verbose = true
10
+ end
11
+
12
+ task :default => [:test]
@@ -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,3 @@
1
+ module RateLimiter
2
+ VERSION = '0.0.2'
3
+ 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
@@ -0,0 +1,6 @@
1
+ require "rubygems"
2
+ require 'redgreen'
3
+ require "rack/test"
4
+ require "test/test_helper"
5
+ require "lib/rack/rate_limiter"
6
+
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