hanikamu-rate-limit 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/.DS_Store +0 -0
- data/CHANGELOG.md +5 -0
- data/Dockerfile +14 -0
- data/LICENSE +21 -0
- data/Makefile +25 -0
- data/README.md +141 -0
- data/Rakefile +12 -0
- data/docker-compose.yml +22 -0
- data/lib/hanikamu/rate_limit/errors.rb +7 -0
- data/lib/hanikamu/rate_limit/mixin.rb +51 -0
- data/lib/hanikamu/rate_limit/rate_queue.rb +126 -0
- data/lib/hanikamu/rate_limit/version.rb +7 -0
- data/lib/hanikamu/rate_limit.rb +63 -0
- data/lib/hanikamu-rate-limit.rb +3 -0
- metadata +102 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: af12b9492738472c4e8bd8431a20b053bde92ec6af556dcd6db9af75c9529e70
|
|
4
|
+
data.tar.gz: '0187f0d7f9d9edde19f5b095368b19feb90c73a51d25301e356161930392ccaf'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b77fb01c48f98d938bdbd4954672be5a3f2fbbed522d4c662349d4b3e6c98327893d7849bc8b8c4936cf33e13af7e5487805f4da6b7451ff4274471d0e42363b
|
|
7
|
+
data.tar.gz: 2bbe7515db8c173aab88c2ee198d0b9a4fb1015091d74706bb0045a31c37166cb1073a29806937db4057e279183c51fe5fd0e7e804a258d4463e29d2fca0ba04
|
data/.DS_Store
ADDED
|
Binary file
|
data/CHANGELOG.md
ADDED
data/Dockerfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hanikamu
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
data/Makefile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.PHONY: shell
|
|
2
|
+
shell: ## access to the system console
|
|
3
|
+
docker-compose run --rm app bash
|
|
4
|
+
|
|
5
|
+
.PHONY: build
|
|
6
|
+
build: ## build the image
|
|
7
|
+
docker-compose build
|
|
8
|
+
|
|
9
|
+
.PHONY: bundle
|
|
10
|
+
bundle: ## install gems and rebuild image
|
|
11
|
+
- docker-compose run --rm app bundle install
|
|
12
|
+
- ${MAKE} build
|
|
13
|
+
|
|
14
|
+
.PHONY: console
|
|
15
|
+
console: ## build the image
|
|
16
|
+
docker-compose run --rm app bash -c "bin/console"
|
|
17
|
+
|
|
18
|
+
.PHONY: rspec
|
|
19
|
+
rspec: ## build the image
|
|
20
|
+
docker-compose run --rm app bash -c "bundle exec rspec"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
.PHONY: cops
|
|
24
|
+
cops: ## build the image
|
|
25
|
+
docker-compose run --rm app sh -c "bundle exec rubocop -A"
|
data/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Hanikamu::RateLimit
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Hanikamu/hanikamu-rate-limit/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
Distributed, Redis-backed rate limiting with a sliding window algorithm. Works across processes and threads by coordinating through Redis.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [Why Hanikamu::RateLimit?](#why-hanikamurate-limit)
|
|
10
|
+
2. [Quick Start](#quick-start)
|
|
11
|
+
3. [Configuration](#configuration)
|
|
12
|
+
4. [Usage](#usage)
|
|
13
|
+
5. [Error Handling](#error-handling)
|
|
14
|
+
6. [Testing](#testing)
|
|
15
|
+
7. [Development](#development)
|
|
16
|
+
8. [License](#license)
|
|
17
|
+
|
|
18
|
+
## Why Hanikamu::RateLimit?
|
|
19
|
+
|
|
20
|
+
- **Use case**: You run 40 Sidekiq workers that all hit the same external marketing API capped at 20 requests per second. Without coordination, they’ll burst and trigger throttling. With a shared limit, every worker routes through the same Redis-backed window so aggregate throughput stays at 20 req/s across the whole fleet.
|
|
21
|
+
- **Distributed by design**: Limits are enforced through Redis so multiple app instances share a single limit.
|
|
22
|
+
- **Sliding window**: Limits are based on the most recent interval window, not fixed buckets.
|
|
23
|
+
- **Backoff with polling**: When a limit is hit, the limiter sleeps in short intervals until a slot opens.
|
|
24
|
+
- **Bounded waiting**: Callers can set a max wait time to avoid waiting indefinitely.
|
|
25
|
+
- **Minimal surface area**: A single mixin and a compact queue implementation.
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
**1. Install the gem**
|
|
30
|
+
|
|
31
|
+
Requires Ruby 4.0 or later.
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
# Gemfile
|
|
35
|
+
gem "hanikamu-rate-limit", "~> 0.1.0"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bundle install
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**2. Configure Redis**
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
Hanikamu::RateLimit.configure do |config|
|
|
46
|
+
config.redis_url = ENV.fetch("REDIS_URL")
|
|
47
|
+
config.check_interval = 0.25
|
|
48
|
+
config.max_wait_time = 1.5
|
|
49
|
+
|
|
50
|
+
config.register_limit(:external_api, rate: 5, interval: 0.5, check_interval: 0.5, max_wait_time: 5)
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**3. Limit a method**
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
class MyService
|
|
58
|
+
extend Hanikamu::RateLimit::Mixin
|
|
59
|
+
|
|
60
|
+
limit_method :execute, rate: 5, interval: 1.0
|
|
61
|
+
|
|
62
|
+
def execute
|
|
63
|
+
# work
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**4. Call it**
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
MyService.new.execute
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Configuration
|
|
75
|
+
|
|
76
|
+
Available settings:
|
|
77
|
+
|
|
78
|
+
- `redis_url`: Redis connection URL (required).
|
|
79
|
+
- `check_interval`: default sleep interval between retries (default: 0.5 seconds).
|
|
80
|
+
- `max_wait_time`: max time to wait before raising (default: 2.0 seconds).
|
|
81
|
+
- `register_limit`: define a named limit shared across classes.
|
|
82
|
+
|
|
83
|
+
Registered limit options:
|
|
84
|
+
|
|
85
|
+
- `rate` and `interval` (required).
|
|
86
|
+
- `check_interval`, `max_wait_time` (optional).
|
|
87
|
+
- `key_prefix` (optional) to force a shared Redis key; defaults to a registry-based prefix.
|
|
88
|
+
|
|
89
|
+
## Usage
|
|
90
|
+
|
|
91
|
+
Optional per-method overrides:
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
limit_method :execute, rate: 5, interval: 1.0, check_interval: 0.1, max_wait_time: 3.0
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Use a registered limit shared across classes:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
class ExternalApiClient
|
|
101
|
+
extend Hanikamu::RateLimit::Mixin
|
|
102
|
+
|
|
103
|
+
limit_with :execute, registry: :external_api
|
|
104
|
+
|
|
105
|
+
def execute
|
|
106
|
+
# work
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Registry precedence (highest to lowest):
|
|
112
|
+
|
|
113
|
+
1. Per-method overrides passed to `limit_with`.
|
|
114
|
+
2. Registered limit options.
|
|
115
|
+
3. Global defaults from `Hanikamu::RateLimit.configure`.
|
|
116
|
+
|
|
117
|
+
Reset method is generated automatically:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
MyService.reset_execute_limit!
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Error Handling
|
|
124
|
+
|
|
125
|
+
If Redis is unavailable, `RateQueue#shift` logs a warning and returns `nil`.
|
|
126
|
+
|
|
127
|
+
## Testing
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
bundle exec rspec
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Development
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
bundle exec rake
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT
|
data/Rakefile
ADDED
data/docker-compose.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
version: "3"
|
|
2
|
+
networks:
|
|
3
|
+
docker-compose-example-tier:
|
|
4
|
+
driver: bridge
|
|
5
|
+
services:
|
|
6
|
+
redis:
|
|
7
|
+
image: redis:7-alpine
|
|
8
|
+
networks:
|
|
9
|
+
- docker-compose-example-tier
|
|
10
|
+
app:
|
|
11
|
+
build:
|
|
12
|
+
context: .
|
|
13
|
+
dockerfile: Dockerfile
|
|
14
|
+
environment:
|
|
15
|
+
HISTFILE: /app/tmp/ash_history
|
|
16
|
+
REDIS_URL: redis://redis:6379/15
|
|
17
|
+
volumes:
|
|
18
|
+
- .:/app
|
|
19
|
+
networks:
|
|
20
|
+
- docker-compose-example-tier
|
|
21
|
+
depends_on:
|
|
22
|
+
- redis
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hanikamu
|
|
4
|
+
module RateLimit
|
|
5
|
+
module Mixin
|
|
6
|
+
def limit_method(method, rate:, interval: 60, **options, &)
|
|
7
|
+
queue = build_queue(rate, interval, method, options, &)
|
|
8
|
+
install_rate_limited_method(method, queue)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def limit_with(method, registry:, **overrides, &)
|
|
12
|
+
registry_config = Hanikamu::RateLimit.fetch_limit(registry)
|
|
13
|
+
merged = registry_config.merge(overrides.compact)
|
|
14
|
+
rate = merged.fetch(:rate)
|
|
15
|
+
interval = merged.fetch(:interval)
|
|
16
|
+
options = merged.slice(:check_interval, :max_wait_time, :key_prefix)
|
|
17
|
+
queue = build_queue(rate, interval, method, options, &)
|
|
18
|
+
install_rate_limited_method(method, queue)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def build_queue(rate, interval, method, options, &)
|
|
24
|
+
Hanikamu::RateLimit::RateQueue.new(
|
|
25
|
+
rate,
|
|
26
|
+
interval: interval,
|
|
27
|
+
klass_name: name,
|
|
28
|
+
method: method,
|
|
29
|
+
key_prefix: options[:key_prefix],
|
|
30
|
+
check_interval: options.fetch(:check_interval, Hanikamu::RateLimit.config.check_interval),
|
|
31
|
+
max_wait_time: options.fetch(:max_wait_time, Hanikamu::RateLimit.config.max_wait_time),
|
|
32
|
+
&
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def install_rate_limited_method(method, queue)
|
|
37
|
+
mixin = Module.new do
|
|
38
|
+
rate_queue = queue
|
|
39
|
+
|
|
40
|
+
define_method(method) do |*args, **options, &blk|
|
|
41
|
+
rate_queue.shift
|
|
42
|
+
options.empty? ? super(*args, &blk) : super(*args, **options, &blk)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
define_singleton_method("reset_#{method}_limit!") { queue.reset }
|
|
47
|
+
prepend(mixin)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "redis"
|
|
5
|
+
|
|
6
|
+
module Hanikamu
|
|
7
|
+
module RateLimit
|
|
8
|
+
class RateQueue
|
|
9
|
+
KEY_PREFIX = "hanikamu:rate_limit:rate_queue"
|
|
10
|
+
LUA_SCRIPT = <<~LUA
|
|
11
|
+
local key = KEYS[1]
|
|
12
|
+
local now = tonumber(ARGV[1])
|
|
13
|
+
local interval = tonumber(ARGV[2])
|
|
14
|
+
local rate = tonumber(ARGV[3])
|
|
15
|
+
local member = ARGV[4]
|
|
16
|
+
|
|
17
|
+
redis.call("ZREMRANGEBYSCORE", key, 0, now - interval)
|
|
18
|
+
local count = redis.call("ZCARD", key)
|
|
19
|
+
|
|
20
|
+
if count < rate then
|
|
21
|
+
redis.call("ZADD", key, now, member)
|
|
22
|
+
redis.call("EXPIRE", key, math.ceil(interval) + 1)
|
|
23
|
+
return {1, 0}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
local oldest = redis.call("ZRANGE", key, 0, 0, "WITHSCORES")
|
|
27
|
+
if oldest and oldest[2] then
|
|
28
|
+
local sleep_for = tonumber(oldest[2]) + interval - now
|
|
29
|
+
if sleep_for < 0 then sleep_for = 0 end
|
|
30
|
+
return {0, sleep_for}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
return {0, interval}
|
|
34
|
+
LUA
|
|
35
|
+
|
|
36
|
+
def initialize(rate, klass_name:, method:, interval: 60, **options, &block)
|
|
37
|
+
@rate = rate
|
|
38
|
+
@interval = interval.to_f
|
|
39
|
+
@klass_name = klass_name
|
|
40
|
+
@method = method
|
|
41
|
+
@key_prefix = options[:key_prefix]
|
|
42
|
+
@check_interval = options.fetch(:check_interval, Hanikamu::RateLimit.config.check_interval)
|
|
43
|
+
@max_wait_time = options.fetch(:max_wait_time, Hanikamu::RateLimit.config.max_wait_time)
|
|
44
|
+
@block = block
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def shift
|
|
48
|
+
start_time = current_time
|
|
49
|
+
|
|
50
|
+
loop do
|
|
51
|
+
allowed, sleep_time = attempt_shift(start_time)
|
|
52
|
+
|
|
53
|
+
return if allowed == 1
|
|
54
|
+
|
|
55
|
+
handle_sleep(sleep_time)
|
|
56
|
+
end
|
|
57
|
+
rescue Redis::BaseError => e
|
|
58
|
+
warn "[Hanikamu::RateLimit] Redis error: #{e.class} - #{e.message}"
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def reset
|
|
63
|
+
redis.del(redis_key)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def redis_key
|
|
69
|
+
@redis_key ||= begin
|
|
70
|
+
prefix = @key_prefix || "#{KEY_PREFIX}:#{@klass_name}:#{@method}"
|
|
71
|
+
"#{prefix}:#{@rate}:#{@interval}"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def attempt_shift(start_time)
|
|
76
|
+
now = current_time
|
|
77
|
+
elapsed = now - start_time
|
|
78
|
+
if @max_wait_time && elapsed > @max_wait_time
|
|
79
|
+
raise Hanikamu::RateLimit::RateLimitError, "Max wait time exceeded"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
member = "#{now}-#{SecureRandom.uuid}"
|
|
83
|
+
eval_script(now, member)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def eval_script(now, member)
|
|
87
|
+
redis.evalsha(
|
|
88
|
+
lua_sha,
|
|
89
|
+
keys: [redis_key],
|
|
90
|
+
argv: [now, @interval, @rate, member]
|
|
91
|
+
)
|
|
92
|
+
rescue Redis::CommandError => e
|
|
93
|
+
return reload_script_and_retry(now, member) if e.message.include?("NOSCRIPT")
|
|
94
|
+
|
|
95
|
+
raise
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def reload_script_and_retry(now, member)
|
|
99
|
+
@lua_sha = redis.script(:load, LUA_SCRIPT)
|
|
100
|
+
redis.evalsha(
|
|
101
|
+
lua_sha,
|
|
102
|
+
keys: [redis_key],
|
|
103
|
+
argv: [now, @interval, @rate, member]
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def handle_sleep(sleep_time)
|
|
108
|
+
@block&.call(sleep_time)
|
|
109
|
+
actual_sleep = @check_interval ? [@check_interval, sleep_time].min : sleep_time
|
|
110
|
+
sleep(actual_sleep) if actual_sleep.to_f.positive?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def redis
|
|
114
|
+
@redis ||= Redis.new(url: Hanikamu::RateLimit.config.redis_url)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def lua_sha
|
|
118
|
+
@lua_sha ||= redis.script(:load, LUA_SCRIPT)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def current_time
|
|
122
|
+
Time.now.to_f
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/configurable"
|
|
4
|
+
require "dry/container"
|
|
5
|
+
require "hanikamu/rate_limit/errors"
|
|
6
|
+
require "hanikamu/rate_limit/mixin"
|
|
7
|
+
require "hanikamu/rate_limit/rate_queue"
|
|
8
|
+
require "hanikamu/rate_limit/version"
|
|
9
|
+
|
|
10
|
+
module Hanikamu
|
|
11
|
+
module RateLimit
|
|
12
|
+
extend Dry::Configurable
|
|
13
|
+
|
|
14
|
+
setting :redis_url
|
|
15
|
+
setting :max_wait_time, default: 2.0
|
|
16
|
+
setting :check_interval, default: 0.5
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
def configure(&block)
|
|
20
|
+
super do |config|
|
|
21
|
+
config.define_singleton_method(:register_limit) do |name, **options|
|
|
22
|
+
Hanikamu::RateLimit.register_limit(name, **options)
|
|
23
|
+
end
|
|
24
|
+
block&.call(config)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def register_limit(name, **options)
|
|
29
|
+
registry.register(normalize_name(name), normalize_registry_options(name, options))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def fetch_limit(name)
|
|
33
|
+
registry.resolve(normalize_name(name))
|
|
34
|
+
rescue Dry::Container::Error
|
|
35
|
+
raise ArgumentError, "Unknown registered limit: #{name}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def reset_registry!
|
|
39
|
+
@registry = Dry::Container.new
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def registry
|
|
43
|
+
@registry ||= Dry::Container.new
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def normalize_name(name)
|
|
49
|
+
name.to_sym
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def normalize_registry_options(name, options)
|
|
53
|
+
rate = options.fetch(:rate)
|
|
54
|
+
interval = options.fetch(:interval)
|
|
55
|
+
key_prefix = options[:key_prefix] || "#{RateQueue::KEY_PREFIX}:registry:#{name}"
|
|
56
|
+
registry_options = { rate: rate, interval: interval, key_prefix: key_prefix }
|
|
57
|
+
registry_options[:check_interval] = options[:check_interval] unless options[:check_interval].nil?
|
|
58
|
+
registry_options[:max_wait_time] = options[:max_wait_time] unless options[:max_wait_time].nil?
|
|
59
|
+
registry_options
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: hanikamu-rate-limit
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Nicolai Seerup
|
|
8
|
+
- Alejandro Jimenez
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: dry-configurable
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: dry-container
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.11'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.11'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: redis
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '5.0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '5.0'
|
|
55
|
+
description: |
|
|
56
|
+
Ruby gem for distributed rate limiting backed by Redis. Provides a sliding-window limiter
|
|
57
|
+
with configurable polling and maximum wait time, suitable for multi-process and multi-thread
|
|
58
|
+
workloads.
|
|
59
|
+
executables: []
|
|
60
|
+
extensions: []
|
|
61
|
+
extra_rdoc_files: []
|
|
62
|
+
files:
|
|
63
|
+
- ".DS_Store"
|
|
64
|
+
- CHANGELOG.md
|
|
65
|
+
- Dockerfile
|
|
66
|
+
- LICENSE
|
|
67
|
+
- Makefile
|
|
68
|
+
- README.md
|
|
69
|
+
- Rakefile
|
|
70
|
+
- docker-compose.yml
|
|
71
|
+
- lib/hanikamu-rate-limit.rb
|
|
72
|
+
- lib/hanikamu/rate_limit.rb
|
|
73
|
+
- lib/hanikamu/rate_limit/errors.rb
|
|
74
|
+
- lib/hanikamu/rate_limit/mixin.rb
|
|
75
|
+
- lib/hanikamu/rate_limit/rate_queue.rb
|
|
76
|
+
- lib/hanikamu/rate_limit/version.rb
|
|
77
|
+
homepage: https://github.com/Hanikamu/hanikamu-rate-limit
|
|
78
|
+
licenses:
|
|
79
|
+
- MIT
|
|
80
|
+
metadata:
|
|
81
|
+
homepage_uri: https://github.com/Hanikamu/hanikamu-rate-limit
|
|
82
|
+
source_code_uri: https://github.com/Hanikamu/hanikamu-rate-limit
|
|
83
|
+
changelog_uri: https://github.com/Hanikamu/hanikamu-rate-limit/blob/main/CHANGELOG.md
|
|
84
|
+
rubygems_mfa_required: 'true'
|
|
85
|
+
rdoc_options: []
|
|
86
|
+
require_paths:
|
|
87
|
+
- lib
|
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
89
|
+
requirements:
|
|
90
|
+
- - ">="
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '4.0'
|
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - ">="
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: '0'
|
|
98
|
+
requirements: []
|
|
99
|
+
rubygems_version: 4.0.3
|
|
100
|
+
specification_version: 4
|
|
101
|
+
summary: Distributed Redis-backed rate limiting
|
|
102
|
+
test_files: []
|