redis_token_bucket 0.1.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/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +12 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +149 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/demo.rb +109 -0
- data/lib/redis_token_bucket.rb +8 -0
- data/lib/redis_token_bucket/limiter.lua +73 -0
- data/lib/redis_token_bucket/limiter.rb +106 -0
- data/lib/redis_token_bucket/version.rb +3 -0
- data/redis_token_bucket.gemspec +26 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e1991459cf61f197e4b366ade53dd41ecbaf26bd
|
4
|
+
data.tar.gz: 4931d16f9aa7fb622bf167fdbc74d4781bbdc548
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3aa2fdf2aca0e7c374cd6845dece306ef0de74651f50b17328269d0abb5a38f9d742ae32ed9fb25119bd5922e166d00aceb42ce2574dae1da02df712d31e581a
|
7
|
+
data.tar.gz: 7f99aef70badd3708e784d240a81c1fa0f1c25a543f6d7fb66d40f7c5e7eb3d6cb4c7e64d2985aeb90aab834ea652ed8f940109fd4f39214e303d33e01de7ccc
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Kristian Hanekamp
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
# RedisTokenBucket
|
2
|
+
|
3
|
+
A [Token Bucket](https://en.wikipedia.org/wiki/Token_bucket) rate limiting implementation in Ruby using a Redis backend.
|
4
|
+
|
5
|
+
Features:
|
6
|
+
* Lightweight and efficient
|
7
|
+
* Uses a single Redis key per bucket
|
8
|
+
* Buckets are automatically created when first used
|
9
|
+
* Buckets are automatically removed when no longer used
|
10
|
+
* Fast and concurrency safe
|
11
|
+
* Each operation uses just a single network roundtrip to Redis
|
12
|
+
* Charging tokens is done with all-or-nothing semantics
|
13
|
+
* Computed continuously
|
14
|
+
* Token values (rate, size, current level, cost) use floating point numbers
|
15
|
+
* Bucket level is computed with microsecond precision
|
16
|
+
* Powerful and flexible
|
17
|
+
* Ability to charge multiple buckets with arbitrary token amounts at once
|
18
|
+
* Ability to "reserve" tokens and to create "token debt"
|
19
|
+
|
20
|
+
Redis version 3.2 or newer is needed.
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
Add this line to your application's Gemfile:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
gem 'redis_token_bucket'
|
28
|
+
```
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
Basic rate limiting:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require 'redis'
|
36
|
+
require 'redis_token_bucket'
|
37
|
+
|
38
|
+
# create connection to redis server
|
39
|
+
# details see: https://github.com/redis/redis-rb/
|
40
|
+
redis = Redis.new
|
41
|
+
|
42
|
+
# create a limiter instance which uses the redis connection
|
43
|
+
limiter = RedisTokenBucket.limiter(redis)
|
44
|
+
|
45
|
+
# define the bucket
|
46
|
+
bucket = {
|
47
|
+
key: "RedisKeyForMyBucket",
|
48
|
+
rate: 100,
|
49
|
+
size: 1000,
|
50
|
+
}
|
51
|
+
|
52
|
+
# charge 10 tokens to the bucket
|
53
|
+
success, level = limiter.charge(bucket, 10)
|
54
|
+
|
55
|
+
# check if charging was successful
|
56
|
+
if success
|
57
|
+
# rate limiter permits request
|
58
|
+
call_my_business_logic
|
59
|
+
else
|
60
|
+
# rate limiter denies request
|
61
|
+
raise "Rate Limit exceeded. Increase you calm!"
|
62
|
+
end
|
63
|
+
|
64
|
+
# print the resulting level of tokens in the bucket
|
65
|
+
puts "The current level of tokens in my bucket: #{level}"
|
66
|
+
|
67
|
+
```
|
68
|
+
|
69
|
+
Reading the current level of tokens of a bucket:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
puts "Current level of tokens: #{limiter.read_level(bucket)}"
|
73
|
+
```
|
74
|
+
|
75
|
+
Charging multiple buckets at once:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
long_bucket = {
|
79
|
+
key: "RedisKeyForLongBucket",
|
80
|
+
rate: 100,
|
81
|
+
size: 10000
|
82
|
+
}
|
83
|
+
|
84
|
+
short_bucket = {
|
85
|
+
key: "RedisKeyForShortBucket",
|
86
|
+
rate: 1000,
|
87
|
+
size: 3000
|
88
|
+
}
|
89
|
+
|
90
|
+
success, levels = limiter.batch_charge(
|
91
|
+
[long_bucket, 1],
|
92
|
+
[short_bucket, 1]
|
93
|
+
)
|
94
|
+
|
95
|
+
puts "The current level of tokens in bucket short: #{levels[short_bucket[:key]]}"
|
96
|
+
puts "The current level of tokens in bucket long: #{levels[long_bucket[:key]]}"
|
97
|
+
|
98
|
+
if success
|
99
|
+
# rate limiter permits request (all buckets were charged)
|
100
|
+
call_my_business_logic
|
101
|
+
else
|
102
|
+
# rate limiter denies request (none of the buckets was charged)
|
103
|
+
raise "Rate Limit exceeded. Increase you calm!"
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
Reading the current level of tokens from multiple buckets:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
levels = limiter.read_levels(short_bucket, long_bucket)
|
111
|
+
|
112
|
+
puts "The current level of tokens in bucket short: #{levels[short_bucket[:key]]}"
|
113
|
+
puts "The current level of tokens in bucket long: #{levels[long_bucket[:key]]}"
|
114
|
+
```
|
115
|
+
|
116
|
+
Advanced: Bucket with Reserved Tokens
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
# this reserves the last 10 tokens,
|
120
|
+
# i.e. charging will fail if it would result in less than 10 tokens
|
121
|
+
|
122
|
+
RedisTokenBucket.charge(bucket, 1, {limit: 10})
|
123
|
+
|
124
|
+
# also possible with batch_charge
|
125
|
+
RedisTokenBucket.batch_charge(
|
126
|
+
[short_bucket, 1, {limit: 10}],
|
127
|
+
[long_bucket, 2, {limit: 5}],
|
128
|
+
)
|
129
|
+
```
|
130
|
+
|
131
|
+
Advanced: Bucket with Token Debt
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
# this allows up to 10 "negative" tokens
|
135
|
+
# i.e. charging will only fail if it would result in less than -10 tokens
|
136
|
+
RedisTokenBucket.charge(bucket, 1, {limit: -10})
|
137
|
+
```
|
138
|
+
|
139
|
+
## Development
|
140
|
+
|
141
|
+
After checking out the repo, run `bundle` to install dependencies.
|
142
|
+
|
143
|
+
Use `bundle exec rspec` to run tests.
|
144
|
+
|
145
|
+
Use `bundle exec ruby demo.rb` to run a demo.
|
146
|
+
|
147
|
+
## Contributors
|
148
|
+
|
149
|
+
Original author: Kristian Hanekamp
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "redis_token_bucket"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/demo.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require "redis_token_bucket"
|
2
|
+
require "concurrent"
|
3
|
+
require "redis"
|
4
|
+
require "securerandom"
|
5
|
+
|
6
|
+
puts <<-EOS
|
7
|
+
The script attempts to continuously make requests against the rate limiter.
|
8
|
+
Each second, the number of requests accepted and denied by the rate limiter is printed.
|
9
|
+
|
10
|
+
You should see the following pattern:
|
11
|
+
* briefly, a burst of requests is accepted
|
12
|
+
* then the 'short' buckets starts limiting to 1000 requests per second
|
13
|
+
* after a few seconds, the 'long' buckets starts limiting to 100 requests per second
|
14
|
+
|
15
|
+
EOS
|
16
|
+
|
17
|
+
def random_key
|
18
|
+
"RedisTokenBucket:demo:#{SecureRandom.hex}"
|
19
|
+
end
|
20
|
+
|
21
|
+
buckets = {
|
22
|
+
long: {
|
23
|
+
key: random_key,
|
24
|
+
rate: 100,
|
25
|
+
size: 10000
|
26
|
+
},
|
27
|
+
short: {
|
28
|
+
key: random_key,
|
29
|
+
rate: 1000,
|
30
|
+
size: 3000
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
consumed = Concurrent::Atom.new(0)
|
35
|
+
|
36
|
+
rejected = {}
|
37
|
+
buckets.each { |name, _| rejected[name] = Concurrent::Atom.new(0) }
|
38
|
+
|
39
|
+
def increase(atom)
|
40
|
+
atom.swap { |before| before + 1 }
|
41
|
+
end
|
42
|
+
|
43
|
+
def reset(atom)
|
44
|
+
last = nil
|
45
|
+
|
46
|
+
atom.swap do |before|
|
47
|
+
last = before
|
48
|
+
|
49
|
+
0
|
50
|
+
end
|
51
|
+
|
52
|
+
last
|
53
|
+
end
|
54
|
+
|
55
|
+
NUM_FORKS = 1
|
56
|
+
NUM_THREADS_PER_FORK = 10
|
57
|
+
|
58
|
+
child_processes = NUM_FORKS.times.map do
|
59
|
+
Process.fork do
|
60
|
+
|
61
|
+
# each fork has an independent output thread
|
62
|
+
output = Thread.new do
|
63
|
+
last_output = 0
|
64
|
+
while true
|
65
|
+
now = Time.now.to_i
|
66
|
+
|
67
|
+
if last_output < now
|
68
|
+
denied_stats = rejected.map { |name, atom| "#{name}: #{reset(atom)}" }
|
69
|
+
puts "Accepted: #{reset(consumed)} / Denied: #{denied_stats}"
|
70
|
+
|
71
|
+
last_output = now
|
72
|
+
end
|
73
|
+
|
74
|
+
sleep 0.001
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# and a number of worker threads, charging tokens
|
79
|
+
workers = NUM_THREADS_PER_FORK.times.map do |i|
|
80
|
+
Thread.new do
|
81
|
+
begin
|
82
|
+
limiter = RedisTokenBucket.limiter(Redis.new)
|
83
|
+
|
84
|
+
while true
|
85
|
+
success, levels = limiter.batch_charge([buckets[:short], 1], [buckets[:long], 1])
|
86
|
+
|
87
|
+
if success
|
88
|
+
increase(consumed)
|
89
|
+
else
|
90
|
+
levels.map do |key, level|
|
91
|
+
name = buckets.keys.detect { |n| buckets[n][:key] == key }
|
92
|
+
increase(rejected[name]) if level < 1
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
rescue Exception => e
|
97
|
+
puts "ERROR"
|
98
|
+
puts e
|
99
|
+
puts e.backtrace
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
output.join
|
105
|
+
workers.join
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
child_processes.map { |pid| Process.waitpid(pid) }
|
@@ -0,0 +1,73 @@
|
|
1
|
+
redis.replicate_commands()
|
2
|
+
local injected_time = tonumber(ARGV[1])
|
3
|
+
local redis_time = redis.call('time')
|
4
|
+
local local_time = redis_time[1] + 0.000001 * redis_time[2]
|
5
|
+
|
6
|
+
local now = injected_time or local_time
|
7
|
+
|
8
|
+
local current_bucket_levels = {}
|
9
|
+
local new_bucket_levels = {}
|
10
|
+
local timeouts = {}
|
11
|
+
local exceeded = false
|
12
|
+
|
13
|
+
for key_index, key in ipairs(KEYS) do
|
14
|
+
local arg_index = key_index * 4 - 2
|
15
|
+
local rate = tonumber(ARGV[arg_index])
|
16
|
+
local size = tonumber(ARGV[arg_index + 1])
|
17
|
+
local amount = tonumber(ARGV[arg_index + 2])
|
18
|
+
|
19
|
+
local bucket = redis.call('hmget', key, 'time', 'level')
|
20
|
+
local last_time = tonumber(bucket[1]) or now
|
21
|
+
local before_level = tonumber(bucket[2]) or size
|
22
|
+
|
23
|
+
local elapsed = math.max(0, now - last_time)
|
24
|
+
local gained = rate * elapsed
|
25
|
+
|
26
|
+
local current_level = math.min(size, before_level + gained)
|
27
|
+
|
28
|
+
current_bucket_levels[key_index] = current_level
|
29
|
+
|
30
|
+
if amount > 0 then
|
31
|
+
local limit = tonumber(ARGV[arg_index + 3]) or 0
|
32
|
+
|
33
|
+
local new_level = current_level - amount
|
34
|
+
new_bucket_levels[key_index] = new_level
|
35
|
+
|
36
|
+
local seconds_to_full = (size - new_level) / rate
|
37
|
+
timeouts[key_index] = seconds_to_full
|
38
|
+
|
39
|
+
if new_level < limit then
|
40
|
+
exceeded = true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
local levels_to_report
|
46
|
+
local charged
|
47
|
+
|
48
|
+
if exceeded or #new_bucket_levels == 0 then
|
49
|
+
levels_to_report = current_bucket_levels
|
50
|
+
charged = 0
|
51
|
+
else
|
52
|
+
levels_to_report = new_bucket_levels
|
53
|
+
charged = 1
|
54
|
+
|
55
|
+
for key_index, key in ipairs(KEYS) do
|
56
|
+
local new_level = new_bucket_levels[key_index]
|
57
|
+
local timeout = timeouts[key_index]
|
58
|
+
|
59
|
+
redis.call('hmset', key,
|
60
|
+
'time', string.format("%.16g", now),
|
61
|
+
'level', string.format("%.16g", new_level)
|
62
|
+
)
|
63
|
+
|
64
|
+
redis.call('expire', key, math.ceil(timeout))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
local formatted_levels = {}
|
69
|
+
for index, value in ipairs(levels_to_report) do
|
70
|
+
formatted_levels[index] = string.format("%.16g", value)
|
71
|
+
end
|
72
|
+
|
73
|
+
return {charged, formatted_levels}
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module RedisTokenBucket
|
2
|
+
class Limiter
|
3
|
+
def initialize(redis, clock = nil)
|
4
|
+
@redis = redis
|
5
|
+
@clock = clock
|
6
|
+
end
|
7
|
+
|
8
|
+
# charges `amount` tokens to the specified `bucket`.
|
9
|
+
#
|
10
|
+
# charging only happens if the bucket has sufficient tokens.
|
11
|
+
# the level of "sufficient tokens" can be adjusted by passing in option[:limit]
|
12
|
+
#
|
13
|
+
# returns a tuple (= Array with two elements) containing
|
14
|
+
# `success:boolean` and `level:Numeric`
|
15
|
+
def charge(bucket, amount, options = nil)
|
16
|
+
success, levels = batch_charge([bucket, amount, options])
|
17
|
+
|
18
|
+
return success, levels[bucket[:key]]
|
19
|
+
end
|
20
|
+
|
21
|
+
# performs several bucket charge operations in batch.
|
22
|
+
#
|
23
|
+
# each operation is passed in as an Array, containing the parameters
|
24
|
+
# for `batch`.
|
25
|
+
#
|
26
|
+
# charging only happens if all buckets have sufficient tokens.
|
27
|
+
# the charges are done transactionally, so either all buckets are charged or none.
|
28
|
+
#
|
29
|
+
# returns a tuple (= Array with two elements) containing
|
30
|
+
# `success:boolean` and `levels:Hash<String, Numeric>`
|
31
|
+
# where `levels` is a hash from bucket keys to bucket levels.
|
32
|
+
def batch_charge(*charges)
|
33
|
+
charges.each do |(bucket, amount, options)|
|
34
|
+
unless amount > 0
|
35
|
+
message = "tried to charge #{amount}, needs to be Numeric and > 0"
|
36
|
+
raise ArgumentError, message
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
run_script(charges)
|
41
|
+
end
|
42
|
+
|
43
|
+
# returns the current level of tokens in the specified `bucket`.
|
44
|
+
def read_level(bucket)
|
45
|
+
read_levels(bucket)[bucket[:key]]
|
46
|
+
end
|
47
|
+
|
48
|
+
# reports the current level of tokens for each of the specified `buckets`.
|
49
|
+
# returns the levels as a Hash from bucket keys to bucket levels.
|
50
|
+
def read_levels(*buckets)
|
51
|
+
_, levels = run_script(buckets.map { |bucket| [bucket, 0] })
|
52
|
+
|
53
|
+
levels
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def run_script(charges)
|
59
|
+
props = charges.map(&method(:props_for_charge)).flatten
|
60
|
+
time = @clock.call if @clock
|
61
|
+
|
62
|
+
argv = [time] + props
|
63
|
+
keys = charges.map { |(bucket, _, _)| bucket[:key] }
|
64
|
+
|
65
|
+
success, levels = eval_script(:keys => keys, :argv => argv)
|
66
|
+
|
67
|
+
levels_as_hash = {}
|
68
|
+
levels.each_with_index do |level, index|
|
69
|
+
levels_as_hash[keys[index]] = level.to_f
|
70
|
+
end
|
71
|
+
|
72
|
+
[success > 0, levels_as_hash]
|
73
|
+
end
|
74
|
+
|
75
|
+
def props_for_charge(charge)
|
76
|
+
bucket, amount, options = charge
|
77
|
+
|
78
|
+
[bucket[:rate], bucket[:size], amount, options ? options[:limit] : nil]
|
79
|
+
end
|
80
|
+
|
81
|
+
def eval_script(options)
|
82
|
+
retries = 0
|
83
|
+
|
84
|
+
begin
|
85
|
+
@redis.evalsha(script_sha, options)
|
86
|
+
rescue Redis::CommandError => e
|
87
|
+
if retries > 0
|
88
|
+
raise
|
89
|
+
end
|
90
|
+
|
91
|
+
@@script_sha = nil
|
92
|
+
|
93
|
+
retries = 1
|
94
|
+
retry
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def script_sha
|
99
|
+
@@script_sha ||= @redis.script(:load, script_code)
|
100
|
+
end
|
101
|
+
|
102
|
+
def script_code
|
103
|
+
@@script ||= File.read(File.expand_path("../limiter.lua", __FILE__))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'redis_token_bucket/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "redis_token_bucket"
|
8
|
+
spec.version = RedisTokenBucket::VERSION
|
9
|
+
spec.authors = ["Kristian Hanekamp"]
|
10
|
+
spec.email = ["kris.hanekamp@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Token Bucket Rate Limiting using Redis}
|
13
|
+
spec.homepage = "https://github.com/krishan/redis_token_bucket"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "redis", "~> 3.0"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3.5"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redis_token_bucket
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kristian Hanekamp
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-12-02 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: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.9'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.9'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.5'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.5'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- kris.hanekamp@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".rspec"
|
78
|
+
- ".travis.yml"
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- bin/console
|
84
|
+
- bin/setup
|
85
|
+
- demo.rb
|
86
|
+
- lib/redis_token_bucket.rb
|
87
|
+
- lib/redis_token_bucket/limiter.lua
|
88
|
+
- lib/redis_token_bucket/limiter.rb
|
89
|
+
- lib/redis_token_bucket/version.rb
|
90
|
+
- redis_token_bucket.gemspec
|
91
|
+
homepage: https://github.com/krishan/redis_token_bucket
|
92
|
+
licenses:
|
93
|
+
- MIT
|
94
|
+
metadata: {}
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 2.2.2
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: Token Bucket Rate Limiting using Redis
|
115
|
+
test_files: []
|