ratelimit 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/ratelimit.rb +110 -0
  2. metadata +70 -0
data/lib/ratelimit.rb ADDED
@@ -0,0 +1,110 @@
1
+ require 'redis'
2
+ require 'redis-namespace'
3
+
4
+ class Ratelimit
5
+
6
+ # Create a RateLimit object.
7
+ #
8
+ # @param [String] key A name to uniquely identify this rate limit. For example, 'emails'
9
+ # @param [Redis] redis Redis instance to use. One is created if nothing is passed.
10
+ # @param [Hash] options Options hash
11
+ # @option options [Integer] :bucket_span (600) Time span to track in seconds
12
+ # @option options [Integer] :bucket_interval (5) How many seconds each bucket represents
13
+ # @option options [Integer] :bucket_expiry (1200) How long we keep data in each bucket before it is auto expired.
14
+ #
15
+ # @return [RateLimit] RateLimit instance
16
+ #
17
+ def initialize(key, redis = nil, options = {}) #bucket_span = 600, bucket_interval = 5, bucket_expiry = 1200, redis = nil)
18
+ @key = key
19
+ @bucket_span = options[:bucket_span] || 600
20
+ @bucket_interval = options[:bucket_interval] || 5
21
+ @bucket_expiry = options[:bucket_expiry] || 1200
22
+ @bucket_count = (@bucket_span / @bucket_interval).round
23
+ @redis = redis
24
+ end
25
+
26
+ # Add to the counter for a given subject.
27
+ #
28
+ # @param [String] subject A unique key to identify the subject. For example, 'user@foo.com'
29
+ def add(subject)
30
+ bucket = get_bucket
31
+ subject = @key + ":" + subject
32
+ redis.multi do
33
+ redis.hincrby(subject, bucket, 1)
34
+ redis.hdel(subject, (bucket + 1) % @bucket_count)
35
+ redis.hdel(subject, (bucket + 2) % @bucket_count)
36
+ redis.expire(subject, @bucket_expiry)
37
+ end
38
+ end
39
+
40
+ # Returns the count for a given subject and interval
41
+ #
42
+ # @param [String] subject Subject for the count
43
+ # @param [Integer] interval How far back (in seconds) to retrieve activity.
44
+ def count(subject, interval)
45
+ bucket = get_bucket
46
+ interval = [interval, @bucket_interval].max
47
+ count = (interval / @bucket_interval).floor
48
+ subject = @key + ":" + subject
49
+ counts = redis.multi do
50
+ redis.hget(subject, bucket)
51
+ count.downto(1) do
52
+ bucket -= 1
53
+ redis.hget(subject, (bucket + @bucket_count) % @bucket_count)
54
+ end
55
+ end
56
+ return counts.inject(0) {|a, i| a += i.to_i}
57
+ end
58
+
59
+ # Check if the rate limit has been exceeded.
60
+ #
61
+ # @param [String] subject Subject to check
62
+ # @param [Hash] options Options hash
63
+ # @option options [Integer] :interval How far back to retrieve activity.
64
+ # @option options [Integer] :threshold Maximum number of actions
65
+ def exceeded?(subject, options = {})
66
+ return count(subject, options[:interval]) >= options[:threshold]
67
+ end
68
+
69
+ # Check if the rate limit is within bounds
70
+ #
71
+ # @param [String] subject Subject to check
72
+ # @param [Hash] options Options hash
73
+ # @option options [Integer] :interval How far back to retrieve activity.
74
+ # @option options [Integer] :threshold Maximum number of actions
75
+ def within_bounds?(subject, options = {})
76
+ return !exceeded?(subject, options)
77
+ end
78
+
79
+ # Execute a block once the rate limit is within bounds
80
+ # *WARNING* This will block the current thread until the rate limit is within bounds.
81
+ #
82
+ # @param [String] subject Subject for this rate limit
83
+ # @param [Hash] options Options hash
84
+ # @option options [Integer] :interval How far back to retrieve activity.
85
+ # @option options [Integer] :threshold Maximum number of actions
86
+ # @yield The block to be run
87
+ #
88
+ # @example Send an email as long as we haven't send 5 in the last 10 minutes
89
+ # ratelimit.exec_with_threshold(email, [:threshold => 5, :interval => 600]) do
90
+ # send_another_email
91
+ # end
92
+ def exec_within_threshold(subject, options = {}, &block)
93
+ options[:threshold] ||= 30
94
+ options[:interval] ||= 30
95
+ while exceeded?(subject, options)
96
+ sleep @bucket_interval
97
+ end
98
+ yield(self)
99
+ end
100
+
101
+ private
102
+
103
+ def get_bucket(time = Time.now.to_i)
104
+ ((time % @bucket_span) / @bucket_interval).floor
105
+ end
106
+
107
+ def redis
108
+ @redis ||= Redis::Namespace.new(:ratelimit, :redis => @redis || Redis.new)
109
+ end
110
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ratelimit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - E.J. Finneran
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-29 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: redis
16
+ requirement: &70236973620740 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 2.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70236973620740
25
+ - !ruby/object:Gem::Dependency
26
+ name: redis-namespace
27
+ requirement: &70236973620200 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 1.0.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70236973620200
36
+ description: This library uses Redis to track the number of actions for a given subject
37
+ over a flexible time frame.
38
+ email:
39
+ - ej.finneran@gmail.com
40
+ executables: []
41
+ extensions: []
42
+ extra_rdoc_files: []
43
+ files:
44
+ - lib/ratelimit.rb
45
+ homepage: http://github.com/ejfinneran/ratelimit
46
+ licenses: []
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 1.8.11
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: Rate limiting backed by redis
69
+ test_files: []
70
+ has_rdoc: