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.
@@ -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