traffic_jam 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/traffic_jam.rb +63 -0
- data/lib/traffic_jam/configuration.rb +63 -0
- data/lib/traffic_jam/errors.rb +14 -0
- data/lib/traffic_jam/limit.rb +178 -0
- data/lib/traffic_jam/limit_group.rb +129 -0
- data/lib/traffic_jam/scripts.rb +14 -0
- data/scripts/increment.lua +46 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d94e33ae19bb7a6f3780230e0236a937dde90cb5
|
4
|
+
data.tar.gz: aba261c52a890bf62392c0fda67742af28d9828c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aee5755e8a2585c309523cd1851ed2dbb81ae93e179e3605a0bdc945a02ca05ed3bc72e915bf2bfdb4574052cd3aaa3be6434a64e108186d8a1ec284d6c64e55
|
7
|
+
data.tar.gz: fafd3000ef48ddfde67bae79e072429f07cf8d11f088bcb418a754f3bfd5779cc6554fe4ebba6146cd9e98b2076b5618874d48ff28f7a4efb21679ddd27ae8b6
|
data/lib/traffic_jam.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'digest/md5'
|
3
|
+
require_relative 'traffic_jam/errors'
|
4
|
+
require_relative 'traffic_jam/configuration'
|
5
|
+
require_relative 'traffic_jam/limit'
|
6
|
+
require_relative 'traffic_jam/limit_group'
|
7
|
+
|
8
|
+
|
9
|
+
module TrafficJam
|
10
|
+
include Errors
|
11
|
+
|
12
|
+
@config = Configuration.new(
|
13
|
+
key_prefix: 'traffic_jam',
|
14
|
+
hash_length: 22
|
15
|
+
)
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_reader :config
|
19
|
+
|
20
|
+
# Configure library in a block.
|
21
|
+
#
|
22
|
+
# @yield [TrafficJam::Configuration]
|
23
|
+
def configure
|
24
|
+
yield config
|
25
|
+
end
|
26
|
+
|
27
|
+
# Create limit with registed max/period.
|
28
|
+
#
|
29
|
+
# @param action [Symbol] registered action name
|
30
|
+
# @param value [String] limit target value
|
31
|
+
# @return [TrafficJam::Limit]
|
32
|
+
def limit(action, value)
|
33
|
+
limits = config.limits(action.to_sym)
|
34
|
+
TrafficJam::Limit.new(action, value, **limits)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Reset all limits associated with the given action. If action is omitted or
|
38
|
+
# nil, this will reset all limits.
|
39
|
+
#
|
40
|
+
# @note Not recommended for use in production.
|
41
|
+
# @param action [Symbol] action to reset limits for
|
42
|
+
# @return [nil]
|
43
|
+
def reset_all(action: nil)
|
44
|
+
prefix =
|
45
|
+
if action.nil?
|
46
|
+
"#{config.key_prefix}:*"
|
47
|
+
else
|
48
|
+
"#{config.key_prefix}:#{action}:*"
|
49
|
+
end
|
50
|
+
config.redis.keys(prefix).each do |key|
|
51
|
+
config.redis.del(key)
|
52
|
+
end
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
%w( exceeded? increment increment! decrement reset used remaining )
|
57
|
+
.each do |method|
|
58
|
+
define_method(method) do |action, value, *args|
|
59
|
+
limit(action, value).send(method, *args)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module TrafficJam
|
2
|
+
# Configuration for TrafficJam library.
|
3
|
+
#
|
4
|
+
# @see TrafficJam#configure
|
5
|
+
class Configuration
|
6
|
+
OPTIONS = %i( key_prefix hash_length redis )
|
7
|
+
|
8
|
+
# @!attribute redis
|
9
|
+
# @return [Redis] the connected Redis client the library uses
|
10
|
+
# @!attribute key_prefix
|
11
|
+
# @return [String] the prefix of all limit keys in Redis
|
12
|
+
# @!attribute hash_length
|
13
|
+
# @return [String] the number of characters to use from the Base64 encoded
|
14
|
+
# hashes of the limit values
|
15
|
+
attr_accessor *OPTIONS
|
16
|
+
|
17
|
+
def initialize(options = {})
|
18
|
+
OPTIONS.each do |option|
|
19
|
+
self.send("#{option}=", options[option])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Register a default cap and period with an action name. For use with
|
24
|
+
# {TrafficJam.limit}.
|
25
|
+
#
|
26
|
+
# @param action [Symbol] action name
|
27
|
+
# @param max [Integer] limit cap
|
28
|
+
# @param period [Fixnum] limit period in seconds
|
29
|
+
def register(action, max, period)
|
30
|
+
@limits ||= {}
|
31
|
+
@limits[action.to_sym] = { max: max, period: period }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get the limit cap registered to an action.
|
35
|
+
#
|
36
|
+
# @see #register
|
37
|
+
# @return [Integer] limit cap
|
38
|
+
def max(action)
|
39
|
+
limits(action)[:max]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get the limit period registered to an action.
|
43
|
+
#
|
44
|
+
# @see #register
|
45
|
+
# @return [Integer] limit period in seconds
|
46
|
+
def period(action)
|
47
|
+
limits(action)[:period]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get registered limit parameters for an action.
|
51
|
+
#
|
52
|
+
# @see #register
|
53
|
+
# @param action [Symbol] action name
|
54
|
+
# @return [Hash] max and period parameters in a hash
|
55
|
+
# @raise [TrafficJam::LimitNotFound] if action is not registered
|
56
|
+
def limits(action)
|
57
|
+
@limits ||= {}
|
58
|
+
limits = @limits[action.to_sym]
|
59
|
+
raise TrafficJam::LimitNotFound.new(action) if limits.nil?
|
60
|
+
limits
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module TrafficJam
|
2
|
+
module Errors
|
3
|
+
class LimitNotFound < StandardError; end
|
4
|
+
|
5
|
+
class LimitExceededError < StandardError
|
6
|
+
attr_accessor :limit
|
7
|
+
|
8
|
+
def initialize(limit)
|
9
|
+
super("Rate limit exceeded: #{limit.action}")
|
10
|
+
@limit = limit
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require_relative 'scripts'
|
2
|
+
|
3
|
+
module TrafficJam
|
4
|
+
# This class represents a rate limit on an action, value pair. For example, if
|
5
|
+
# rate limiting the number of requests per IP address, the action could be
|
6
|
+
# +:requests+ and the value would be the IP address. The class exposes atomic
|
7
|
+
# increment operations and allows querying of the current amount used and
|
8
|
+
# amount remaining.
|
9
|
+
class Limit
|
10
|
+
# @!attribute [r] action
|
11
|
+
# @return [Symbol] the name of the action being rate limited.
|
12
|
+
# @!attribute [r] value
|
13
|
+
# @return [String] the target of the limit. The value should be a string
|
14
|
+
# or convertible to a distinct string when +to_s+ is called. If you
|
15
|
+
# would like to use objects that can be converted to a unique string,
|
16
|
+
# like a database-mapped object with an ID, you can implement
|
17
|
+
# +to_rate_limit_value+ on the object, which returns a deterministic
|
18
|
+
# string unique to that object.
|
19
|
+
# @!attribute [r] max
|
20
|
+
# @return [Integer] the integral cap of the limit amount.
|
21
|
+
# @!attribute [r] period
|
22
|
+
# @return [Integer] the duration of the limit in seconds. Regardless of
|
23
|
+
# the current amount used, after the period passes, the amount used will
|
24
|
+
# be 0.
|
25
|
+
attr_reader :action, :max, :period, :value
|
26
|
+
|
27
|
+
# Constructor takes an action name as a symbol, a maximum cap, and the
|
28
|
+
# period of limit. +max+ and +period+ are required keyword arguments.
|
29
|
+
#
|
30
|
+
# @param action [Symbol] action name
|
31
|
+
# @param value [String] limit target value
|
32
|
+
# @param max [Integer] required limit maximum
|
33
|
+
# @param period [Integer] required limit period in seconds
|
34
|
+
# @raise [ArgumentError] if max or period is nil
|
35
|
+
def initialize(action, value, max: nil, period: nil)
|
36
|
+
raise ArgumentError('Max is required') if max.nil?
|
37
|
+
raise ArgumentError('Period is required') if period.nil?
|
38
|
+
@action, @value, @max, @period = action, value, max, period
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return whether incrementing by the given amount would exceed limit. Does
|
42
|
+
# not change amount used.
|
43
|
+
#
|
44
|
+
# @param amount [Integer]
|
45
|
+
# @return [Boolean]
|
46
|
+
def exceeded?(amount = 1)
|
47
|
+
used + amount > max
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return itself if incrementing by the given amount would exceed limit,
|
51
|
+
# otherwise nil. Does not change amount used.
|
52
|
+
#
|
53
|
+
# @return [TrafficJam::Limit, nil]
|
54
|
+
def limit_exceeded(amount = 1)
|
55
|
+
self if exceeded?(amount)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Increment the amount used by the given number. Does not perform increment
|
59
|
+
# if the operation would exceed the limit. Returns whether the operation was
|
60
|
+
# successful. Time of increment can be specified optionally with a keyword
|
61
|
+
# argument, which is useful for rolling back with a decrement.
|
62
|
+
#
|
63
|
+
# @param amount [Integer] amount to increment by
|
64
|
+
# @param time [Time] time when increment occurs
|
65
|
+
# @return [Boolean] true if increment succeded and false if incrementing
|
66
|
+
# would exceed the limit
|
67
|
+
def increment(amount = 1, time: Time.now)
|
68
|
+
return amount <= 0 if max.zero?
|
69
|
+
|
70
|
+
if amount != amount.to_i
|
71
|
+
raise ArgumentError.new("Amount must be an integer")
|
72
|
+
end
|
73
|
+
|
74
|
+
timestamp = (time.to_f * 1000).round
|
75
|
+
argv = [timestamp, amount.to_i, max, period * 1000]
|
76
|
+
|
77
|
+
result =
|
78
|
+
begin
|
79
|
+
redis.evalsha(
|
80
|
+
Scripts::INCREMENT_SCRIPT_HASH, keys: [key], argv: argv)
|
81
|
+
rescue Redis::CommandError => e
|
82
|
+
redis.eval(Scripts::INCREMENT_SCRIPT, keys: [key], argv: argv)
|
83
|
+
end
|
84
|
+
|
85
|
+
!!result
|
86
|
+
end
|
87
|
+
|
88
|
+
# Increment the amount used by the given number. Does not perform increment
|
89
|
+
# if the operation would exceed the limit. Raises an exception if the
|
90
|
+
# operation is unsuccessful. Time of# increment can be specified optionally
|
91
|
+
# with a keyword argument, which is useful for rolling back with a
|
92
|
+
# decrement.
|
93
|
+
#
|
94
|
+
# @param amount [Integer] amount to increment by
|
95
|
+
# @param time [Time] time when increment occurs
|
96
|
+
# @return [nil]
|
97
|
+
# @raise [TrafficJam::LimitExceededError] if incrementing would exceed the
|
98
|
+
# limit
|
99
|
+
def increment!(amount = 1, time: Time.now)
|
100
|
+
if !increment(amount, time: time)
|
101
|
+
raise TrafficJam::LimitExceededError.new(self)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Decrement the amount used by the given number. Time of decrement can be
|
106
|
+
# specified optionally with a keyword argument, which is useful for rolling
|
107
|
+
# back an increment operation at a certain time.
|
108
|
+
#
|
109
|
+
# @param amount [Integer] amount to increment by
|
110
|
+
# @param time [Time] time when increment occurs
|
111
|
+
# @return [true]
|
112
|
+
def decrement(amount = 1, time: Time.now)
|
113
|
+
increment(-amount, time: time)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Reset amount used to 0.
|
117
|
+
#
|
118
|
+
# @return [nil]
|
119
|
+
def reset
|
120
|
+
redis.del(key)
|
121
|
+
nil
|
122
|
+
end
|
123
|
+
|
124
|
+
# Return amount of limit used, taking time drift into account.
|
125
|
+
#
|
126
|
+
# @return [Integer] amount used
|
127
|
+
def used
|
128
|
+
return 0 if max.zero?
|
129
|
+
|
130
|
+
obj = redis.hgetall(key)
|
131
|
+
timestamp = obj['timestamp']
|
132
|
+
amount = obj['amount']
|
133
|
+
if timestamp && amount
|
134
|
+
time_passed = Time.now.to_f - timestamp.to_i / 1000.0
|
135
|
+
drift = max * time_passed / period
|
136
|
+
last_amount = [amount.to_f, max].min
|
137
|
+
[(last_amount - drift).ceil, 0].max
|
138
|
+
else
|
139
|
+
0
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Return amount of limit remaining, taking time drift into account.
|
144
|
+
#
|
145
|
+
# @return [Integer] amount remaining
|
146
|
+
def remaining
|
147
|
+
max - used
|
148
|
+
end
|
149
|
+
|
150
|
+
def flatten
|
151
|
+
[self]
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
def config
|
156
|
+
TrafficJam.config
|
157
|
+
end
|
158
|
+
|
159
|
+
def redis
|
160
|
+
config.redis
|
161
|
+
end
|
162
|
+
|
163
|
+
def key
|
164
|
+
if @key.nil?
|
165
|
+
converted_value =
|
166
|
+
begin
|
167
|
+
value.to_rate_limit_value
|
168
|
+
rescue NoMethodError
|
169
|
+
value
|
170
|
+
end
|
171
|
+
hash = Digest::MD5.base64digest(converted_value.to_s)
|
172
|
+
hash = hash[0...config.hash_length]
|
173
|
+
@key = "#{config.key_prefix}:#{action}:#{hash}"
|
174
|
+
end
|
175
|
+
@key
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module TrafficJam
|
2
|
+
# A limit group is a way of enforcing a cap over a set of limits with the
|
3
|
+
# guarantee that either all limits will be incremented or none. This is useful
|
4
|
+
# if you must check multiple limits before allowing an action to be taken.
|
5
|
+
# Limit groups can contain other limit groups.
|
6
|
+
class LimitGroup
|
7
|
+
attr_reader :limits
|
8
|
+
|
9
|
+
# Creates a limit group from a collection of limits or other limit groups.
|
10
|
+
#
|
11
|
+
# @param limits [Array<TrafficJam::Limit>] either an array or splat of
|
12
|
+
# limits or other limit groups
|
13
|
+
# @param ignore_nil_values [Boolean] silently drop limits with a nil value
|
14
|
+
def initialize(*limits, ignore_nil_values: false)
|
15
|
+
@limits = limits.flatten
|
16
|
+
@ignore_nil_values = ignore_nil_values
|
17
|
+
if @ignore_nil_values
|
18
|
+
@limits.reject! do |limit|
|
19
|
+
limit.respond_to?(:value) && limit.value.nil?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Add a limit to the group.
|
25
|
+
#
|
26
|
+
# @param limit [TrafficJam::Limit, TrafficJam::LimitGroup]
|
27
|
+
def <<(limit)
|
28
|
+
if !(@ignore_nil_values && limit.value.nil?)
|
29
|
+
limits << limit
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Attempt to increment the limits by the given amount. Does not increment
|
34
|
+
# if incrementing would exceed any limit.
|
35
|
+
#
|
36
|
+
# @param amount [Integer] amount to increment by
|
37
|
+
# @param time [Time] optional time of increment
|
38
|
+
# @return [Boolean] whether increment operation was successful
|
39
|
+
def increment(amount = 1, time: Time.now)
|
40
|
+
exceeded_index = limits.find_index do |limit|
|
41
|
+
!limit.increment(amount, time: time)
|
42
|
+
end
|
43
|
+
if exceeded_index
|
44
|
+
limits[0...exceeded_index].each do |limit|
|
45
|
+
limit.decrement(amount, time: time)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
exceeded_index.nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
# Increment the limits by the given amount. Raises an error and does not
|
52
|
+
# increment if doing so would exceed any limit.
|
53
|
+
#
|
54
|
+
# @param amount [Integer] amount to increment by
|
55
|
+
# @param time [Time] optional time of increment
|
56
|
+
# @return [nil]
|
57
|
+
# @raise [TrafficJam::LimitExceededError] if increment would exceed any
|
58
|
+
# limits
|
59
|
+
def increment!(amount = 1, time: Time.now)
|
60
|
+
exception = nil
|
61
|
+
exceeded_index = limits.find_index do |limit|
|
62
|
+
begin
|
63
|
+
limit.increment!(amount, time: time)
|
64
|
+
rescue TrafficJam::LimitExceededError => e
|
65
|
+
exception = e
|
66
|
+
true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
if exceeded_index
|
70
|
+
limits[0...exceeded_index].each do |limit|
|
71
|
+
limit.decrement(amount, time: time)
|
72
|
+
end
|
73
|
+
raise exception
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Decrement the limits by the given amount.
|
78
|
+
#
|
79
|
+
# @param amount [Integer] amount to decrement by
|
80
|
+
# @param time [Time] optional time of decrement
|
81
|
+
# @return [true]
|
82
|
+
def decrement(amount = 1, time: Time.now)
|
83
|
+
limits.all? { |limit| limit.decrement(amount, time: time) }
|
84
|
+
end
|
85
|
+
|
86
|
+
# Return whether incrementing by the given amount would exceed any limit.
|
87
|
+
# Does not change amount used.
|
88
|
+
#
|
89
|
+
# @param amount [Integer]
|
90
|
+
# @return [Boolean] whether any limit would be exceeded
|
91
|
+
def exceeded?(amount = 1)
|
92
|
+
limits.any? { |limit| limit.exceeded?(amount) }
|
93
|
+
end
|
94
|
+
|
95
|
+
# Return the first limit to be exceeded if incrementing by the given amount,
|
96
|
+
# or nil otherwise. Does not change amount used for any limit.
|
97
|
+
#
|
98
|
+
# @param amount [Integer]
|
99
|
+
# @return [TrafficJam::Limit, nil]
|
100
|
+
def limit_exceeded(amount = 1)
|
101
|
+
limits.each do |limit|
|
102
|
+
limit_exceeded = limit.limit_exceeded(amount)
|
103
|
+
return limit_exceeded if limit_exceeded
|
104
|
+
end
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
|
108
|
+
# Resets all limits to 0.
|
109
|
+
def reset
|
110
|
+
limits.each(&:reset)
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
114
|
+
# Return minimum amount remaining of any limit.
|
115
|
+
#
|
116
|
+
# @return [Integer] amount remaining in limit group
|
117
|
+
def remaining
|
118
|
+
limits.map(&:remaining).min
|
119
|
+
end
|
120
|
+
|
121
|
+
# Return flattened list of limit. Will return list limits even if this group
|
122
|
+
# contains nested limit groups.
|
123
|
+
#
|
124
|
+
# @return [Array<TrafficJam::Limit>] list of limits
|
125
|
+
def flatten
|
126
|
+
limits.map(&:flatten).flatten
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module TrafficJam
|
4
|
+
module Scripts
|
5
|
+
def self.load(name)
|
6
|
+
scripts_dir = File.join(File.dirname(__FILE__), '..', '..', 'scripts')
|
7
|
+
File.read(File.join(scripts_dir, "#{name}.lua"))
|
8
|
+
end
|
9
|
+
private_class_method :load
|
10
|
+
|
11
|
+
INCREMENT_SCRIPT = load('increment')
|
12
|
+
INCREMENT_SCRIPT_HASH = Digest::SHA1.hexdigest(INCREMENT_SCRIPT)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
local arg_timestamp = tonumber(ARGV[1])
|
2
|
+
local arg_amount = tonumber(ARGV[2])
|
3
|
+
local arg_max = tonumber(ARGV[3])
|
4
|
+
local arg_period = tonumber(ARGV[4])
|
5
|
+
|
6
|
+
local old_timestamp = redis.call("HGET", KEYS[1], "timestamp")
|
7
|
+
|
8
|
+
local new_amount
|
9
|
+
local new_timestamp
|
10
|
+
|
11
|
+
if not old_timestamp
|
12
|
+
then
|
13
|
+
new_amount = arg_amount
|
14
|
+
new_timestamp = arg_timestamp
|
15
|
+
else
|
16
|
+
local time_diff = arg_timestamp - tonumber(old_timestamp)
|
17
|
+
local drift_amount = time_diff * arg_max / arg_period
|
18
|
+
if time_diff < 0
|
19
|
+
then
|
20
|
+
local incr_amount = arg_amount + drift_amount
|
21
|
+
if incr_amount <= 0
|
22
|
+
then
|
23
|
+
return true
|
24
|
+
end
|
25
|
+
local old_amount = tonumber(redis.call("HGET", KEYS[1], "amount"))
|
26
|
+
old_amount = math.min(old_amount, arg_max)
|
27
|
+
new_amount = old_amount + incr_amount
|
28
|
+
new_timestamp = old_timestamp
|
29
|
+
else
|
30
|
+
local old_amount = tonumber(redis.call("HGET", KEYS[1], "amount"))
|
31
|
+
old_amount = math.min(old_amount, arg_max)
|
32
|
+
local current_amount = math.max(old_amount - drift_amount, 0)
|
33
|
+
new_amount = current_amount + arg_amount
|
34
|
+
new_timestamp = arg_timestamp
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
if new_amount > arg_max
|
39
|
+
then
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
|
43
|
+
redis.call("HSET", KEYS[1], "amount", new_amount)
|
44
|
+
redis.call("HSET", KEYS[1], "timestamp", new_timestamp)
|
45
|
+
redis.call("EXPIRE", KEYS[1], arg_period)
|
46
|
+
return true
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: traffic_jam
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jim Posen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-05-05 00:00:00.000000000 Z
|
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.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description: Library for Redis-backed time-based rate limiting
|
42
|
+
email: jimpo@coinbase.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- lib/traffic_jam.rb
|
48
|
+
- lib/traffic_jam/configuration.rb
|
49
|
+
- lib/traffic_jam/errors.rb
|
50
|
+
- lib/traffic_jam/limit.rb
|
51
|
+
- lib/traffic_jam/limit_group.rb
|
52
|
+
- lib/traffic_jam/scripts.rb
|
53
|
+
- scripts/increment.lua
|
54
|
+
homepage: ''
|
55
|
+
licenses:
|
56
|
+
- MIT
|
57
|
+
metadata: {}
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 2.2.2
|
75
|
+
signing_key:
|
76
|
+
specification_version: 4
|
77
|
+
summary: Library for time-based rate limiting
|
78
|
+
test_files: []
|
79
|
+
has_rdoc:
|