traffic_jam 1.0.0
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 +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:
|