congestion 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 743682fd626adfbbda32326f478c784b65b37681
4
- data.tar.gz: 5a2b05c1e4248f9c0c46f6d6a33be25c1e4341c7
3
+ metadata.gz: 16e2c7689805fd59071ed37c3ca3948706b9523d
4
+ data.tar.gz: 96c12215e6403a4bf5f68f6668f4f5bc7384c8d4
5
5
  SHA512:
6
- metadata.gz: d25cfb8a064c2237c43640eafb1405fa36118b3214f044fd512947d8e3a7feaa897232d90c0231ba9c9bc76ab4b4431daa02a5f834da5fc827ec310adf567b60
7
- data.tar.gz: 607c8c1faf82fd729d08a9538ee2f5feb418b4aa0cb32f62c332d6c1905226db932f66ac1bc43942440dc8c676f7084d6acf74bb3245a9bec8bb52c739d7cebc
6
+ metadata.gz: d68a8e3a27f4cb38bcb4ae72d2ac45f84171abb4c0e5dc86e0f3b19ad61417d70f8957d11d8ef9204933e48ae6aa0eba8f6851847e1b03ff1fafca19500bb729
7
+ data.tar.gz: 982bc455e6a47aba8e950c1c96d0dfed4024bbf65fe92d93a0b6a25ab08892c0d8ebe04fa6b524525c908fac2d4aa091c8c1d2dc1456e492c8852ef7a6d83ab4
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ .DS_Store
data/.travis.yml CHANGED
@@ -1,3 +1,11 @@
1
1
  language: ruby
2
+ sudo: false
3
+ cache: bundler
2
4
  rvm:
3
- - 2.2.1
5
+ - 1.9.3
6
+ - 2.2.0
7
+ - jruby-19mode
8
+ services:
9
+ - redis-server
10
+ jruby_before_script: &jruby_before_script
11
+ - bundle exec jbundle install
data/Guardfile ADDED
@@ -0,0 +1,6 @@
1
+ guard :rspec, cmd: 'bundle exec rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}){ |m| "spec/#{ m[1] }_spec.rb" }
4
+ watch('spec/spec_helper.rb'){ 'spec' }
5
+ watch(%r{^spec/support/(.+)\.rb$}){ 'spec' }
6
+ end
data/README.md CHANGED
@@ -42,20 +42,45 @@ limiter.backoff # => the number of seconds before a request will be permit
42
42
 
43
43
  ### Configuration
44
44
 
45
+ A proc provides a Redis connection:
46
+
47
+ ```ruby
48
+ Congestion.redis = ->{
49
+ Redis.new url: 'redis://:password@host:port/db'
50
+ }
51
+ ```
52
+
53
+ To pool, your Redis connections:
54
+
55
+ ```ruby
56
+ require 'congestion/redis_pool'
57
+
58
+ Congestion::RedisPool.redis_config = {
59
+ url: 'redis://:password@host:port/db'
60
+ }
61
+
62
+ Congestion::RedisPool.pool_size = 10 # number of connections to use
63
+ Congestion::RedisPool.timeout = 10 # seconds before timing out an operation
64
+
65
+ Congestion.redis = Congestion::RedisPool.instance
66
+ ```
67
+
45
68
  Global options can be set with:
46
69
 
47
70
  ```ruby
48
- Congestion.default_options = {
49
- interval: 1, # The timeframe to limit within in seconds
50
- max_in_interval: 1, # The number of allowed requests within the interval
51
- min_delay: 0.0 # The minimum amount of time in seconds between requests
52
- }
71
+ Congestion.default_options = {
72
+ namespace: 'congestion' # The Redis key prefix (e.g. 'congestion:some_key')
73
+ interval: 1, # The timeframe to limit within in seconds
74
+ max_in_interval: 1, # The number of allowed requests within the interval
75
+ min_delay: 0.0, # The minimum amount of time in seconds between requests
76
+ track_rejected: false # True if rejected request count towards the limit
77
+ }
53
78
  ```
54
79
 
55
80
  Per-request options can be set as well:
56
81
 
57
82
  ```ruby
58
- Congestion.request 'some_key', interval: 60, min_delay: 1
83
+ Congestion.request 'some_key', interval: 60, min_delay: 1
59
84
  ```
60
85
 
61
86
  ## Development
data/congestion.gemspec CHANGED
@@ -19,8 +19,14 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ['lib']
21
21
 
22
- spec.add_development_dependency 'bundler', '~> 1.9'
22
+ spec.add_runtime_dependency 'redis', '>= 3.1'
23
+ spec.add_runtime_dependency 'connection_pool', '>= 2.0'
24
+ spec.add_development_dependency 'bundler', '>= 1.5'
23
25
  spec.add_development_dependency 'rake', '~> 10.0'
24
26
  spec.add_development_dependency 'rspec', '~> 3.2'
27
+ spec.add_development_dependency 'rspec-its', '~> 1.2'
28
+ spec.add_development_dependency 'guard-rspec', '~> 4.5'
29
+ spec.add_development_dependency 'timecop', '~> 0.7'
25
30
  spec.add_development_dependency 'pry'
31
+ spec.add_development_dependency 'codeclimate-test-reporter'
26
32
  end
@@ -0,0 +1,104 @@
1
+ module Congestion
2
+ class RateLimiter
3
+ attr_accessor :redis, :key, :options
4
+
5
+ def initialize(redis, key, opts = { })
6
+ self.redis = redis
7
+ self.key = "#{ opts[:namespace] }:#{ key }"
8
+ self.options = opts
9
+ self.options[:interval] *= 1_000
10
+ self.options[:min_delay] *= 1_000
11
+ allowed?
12
+ end
13
+
14
+ def total_requests
15
+ get_requests[1]
16
+ end
17
+
18
+ def first_request
19
+ first = get_requests[2].first
20
+ first ? first.to_i : nil
21
+ end
22
+
23
+ def last_request
24
+ last = get_requests[3].first
25
+ last ? last.to_i : nil
26
+ end
27
+
28
+ def allowed?
29
+ add_request unless rejected?
30
+ !rejected?
31
+ end
32
+
33
+ def rejected?
34
+ too_many? || too_frequent?
35
+ end
36
+
37
+ def too_many?
38
+ total_requests > options[:max_in_interval]
39
+ end
40
+
41
+ def too_frequent?
42
+ last_request && time_since_last_request < options[:min_delay]
43
+ end
44
+
45
+ def backoff
46
+ if too_many? && too_frequent?
47
+ [quantity_backoff, frequency_backoff].max
48
+ elsif too_many?
49
+ quantity_backoff
50
+ elsif too_frequent?
51
+ frequency_backoff
52
+ else
53
+ 0
54
+ end
55
+ end
56
+
57
+ protected
58
+
59
+ def current_time
60
+ @current_time ||= (Time.now.utc.to_f * 1_000).round
61
+ end
62
+
63
+ def time_since_last_request
64
+ current_time - last_request
65
+ end
66
+
67
+ def time_since_first_request
68
+ current_time - first_request
69
+ end
70
+
71
+ def expired_at
72
+ current_time - options[:interval]
73
+ end
74
+
75
+ def quantity_backoff
76
+ millis = options[:interval] - time_since_first_request
77
+ (millis / 1_000.0).ceil
78
+ end
79
+
80
+ def frequency_backoff
81
+ millis = options[:min_delay] - time_since_last_request
82
+ (millis / 1_000.0).ceil
83
+ end
84
+
85
+ def add_request
86
+ unless options[:track_rejected]
87
+ redis.zadd key, current_time, current_time
88
+ end
89
+ end
90
+
91
+ def get_requests
92
+ @requests ||= redis.multi do |t|
93
+ t.zremrangebyscore key, 0, expired_at # [0] - clear old requests
94
+ t.zcount key, '-inf', '+inf' # [1] - number of requests
95
+ t.zrange key, 0, 0 # [2] - first request
96
+ t.zrange key, -1, -1 # [3] - last request
97
+ t.pexpire key, options[:interval] # [4] - expire request key
98
+
99
+ # [5] - Add the request if tracking rejected
100
+ t.zadd(key, current_time, current_time) if options[:track_rejected]
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,32 @@
1
+ require 'redis'
2
+ require 'connection_pool'
3
+
4
+ module Congestion
5
+ class RedisPool
6
+ class << self
7
+ attr_accessor :redis_config
8
+ attr_accessor :pool_size
9
+ attr_accessor :timeout
10
+ end
11
+
12
+ self.redis_config = { }
13
+ self.pool_size = 5
14
+ self.timeout = 5
15
+
16
+ attr_accessor :pool
17
+
18
+ def self.instance
19
+ @instance ||= new
20
+ @redis_pool ||= ->{ @instance.pool.with{ |redis| redis } }
21
+ end
22
+
23
+ private
24
+ def initialize
25
+ pool_config = { size: self.class.pool_size, timeout: self.class.timeout }
26
+
27
+ self.pool = ConnectionPool.new(pool_config) do
28
+ Redis.new self.class.redis_config
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,3 +1,3 @@
1
1
  module Congestion
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
data/lib/congestion.rb CHANGED
@@ -1,4 +1,26 @@
1
+ require 'redis'
1
2
  require 'congestion/version'
3
+ require 'congestion/rate_limiter'
2
4
 
3
5
  module Congestion
6
+ class << self
7
+ attr_accessor :default_options
8
+ attr_accessor :redis
9
+ end
10
+
11
+ self.default_options = {
12
+ namespace: 'congestion', # Redis key namespace
13
+ interval: 1, # 1 second
14
+ max_in_interval: 1, # 1 / second
15
+ min_delay: 0, # none
16
+ track_rejected: false
17
+ }
18
+
19
+ self.redis = ->{
20
+ Redis.new
21
+ }
22
+
23
+ def self.request(key, opts = { })
24
+ RateLimiter.new redis.call, key, default_options.merge(opts)
25
+ end
4
26
  end
metadata CHANGED
@@ -1,29 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: congestion
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Parrish
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-04-09 00:00:00.000000000 Z
11
+ date: 2015-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: connection_pool
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: bundler
15
43
  requirement: !ruby/object:Gem::Requirement
16
44
  requirements:
17
- - - "~>"
45
+ - - ">="
18
46
  - !ruby/object:Gem::Version
19
- version: '1.9'
47
+ version: '1.5'
20
48
  type: :development
21
49
  prerelease: false
22
50
  version_requirements: !ruby/object:Gem::Requirement
23
51
  requirements:
24
- - - "~>"
52
+ - - ">="
25
53
  - !ruby/object:Gem::Version
26
- version: '1.9'
54
+ version: '1.5'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: rake
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +80,48 @@ dependencies:
52
80
  - - "~>"
53
81
  - !ruby/object:Gem::Version
54
82
  version: '3.2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec-its
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.2'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: guard-rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '4.5'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '4.5'
111
+ - !ruby/object:Gem::Dependency
112
+ name: timecop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.7'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.7'
55
125
  - !ruby/object:Gem::Dependency
56
126
  name: pry
57
127
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +136,20 @@ dependencies:
66
136
  - - ">="
67
137
  - !ruby/object:Gem::Version
68
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: codeclimate-test-reporter
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
69
153
  description: Redis rate limiter that provides both time-based limits and quantity-based
70
154
  limits
71
155
  email:
@@ -79,6 +163,7 @@ files:
79
163
  - ".travis.yml"
80
164
  - CODE_OF_CONDUCT.md
81
165
  - Gemfile
166
+ - Guardfile
82
167
  - LICENSE.txt
83
168
  - README.md
84
169
  - Rakefile
@@ -86,6 +171,8 @@ files:
86
171
  - bin/setup
87
172
  - congestion.gemspec
88
173
  - lib/congestion.rb
174
+ - lib/congestion/rate_limiter.rb
175
+ - lib/congestion/redis_pool.rb
89
176
  - lib/congestion/version.rb
90
177
  homepage: https://github.com/parrish/Congestion
91
178
  licenses: