congestion 0.0.1 → 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.
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: