rails_rate_limit 0.1.0 → 0.1.2
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 +4 -4
- data/README.md +54 -19
- data/lib/rails_rate_limit/configuration.rb +13 -12
- data/lib/rails_rate_limit/controller.rb +2 -2
- data/lib/rails_rate_limit/klass.rb +4 -3
- data/lib/rails_rate_limit/rate_limiter.rb +0 -2
- data/lib/rails_rate_limit/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf52b273c158732c58a654255744cbc096ddab47348f16684211435074dfd9b9
|
4
|
+
data.tar.gz: 239cf6c9e06d1300d057c28a2bdcd065de265799d58c369e061e9787e504879b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 17198b2b17fe32b8dea0d56d99a02157cc4e0f50e103006b6faea197d38b2206c9810243cdf06a1c0e3c95807bd63d00feea07a426f358f026d60c7c57a5e37b
|
7
|
+
data.tar.gz: b1b66ee43016fd1db38083833228c779507dd586573cd9d47a3d69bd0a5cac45e6c478308a9e399d370fe5618b9182c657c55e8b09ff0e4ca465a6dbe25a2e6e
|
data/README.md
CHANGED
@@ -3,12 +3,11 @@
|
|
3
3
|
[](https://badge.fury.io/rb/rails_rate_limit)
|
4
4
|
[](https://github.com/kasvit/rails_rate_limit/actions)
|
5
5
|
|
6
|
-
A flexible and robust rate limiting solution for Ruby on Rails applications. The gem implements a sliding window log algorithm, which means it tracks the exact timestamp of each request and calculates the count within a sliding time window. This provides more accurate rate limiting compared to fixed window approaches.
|
6
|
+
A flexible and robust rate limiting solution for Ruby on Rails applications. The gem implements a **sliding window log** algorithm, which means it tracks the exact timestamp of each request and calculates the count within a sliding time window. This provides more accurate rate limiting compared to fixed window approaches.
|
7
7
|
|
8
8
|
For example, if you set a limit of 100 requests per hour, and a user makes 100 requests at 2:30 PM, they won't be able to make another request until some of those requests "expire" after 2:30 PM the next hour. This prevents the common issue with fixed windows where users could potentially make 200 requests around the window boundary.
|
9
9
|
|
10
|
-
|
11
|
-
Inspired by https://github.com/rails/rails/blob/8-0-sec/actionpack/lib/action_controller/metal/rate_limiting.rb.
|
10
|
+
The gem supports rate limiting for both HTTP requests (in controllers) and instance method calls (in any Ruby class), with multiple storage backends (Redis, Memcached, Memory).
|
12
11
|
|
13
12
|
## Installation
|
14
13
|
|
@@ -30,7 +29,7 @@ Create an initializer `config/initializers/rails_rate_limit.rb`:
|
|
30
29
|
|
31
30
|
```ruby
|
32
31
|
RailsRateLimit.configure do |config|
|
33
|
-
#
|
32
|
+
# Optional: Choose your storage backend (default: :memory)
|
34
33
|
config.default_store = :redis # Available options: :redis, :memcached, :memory
|
35
34
|
|
36
35
|
# Optional: Configure Redis connection (required if using Redis store)
|
@@ -47,10 +46,12 @@ RailsRateLimit.configure do |config|
|
|
47
46
|
)
|
48
47
|
|
49
48
|
# Optional: Configure logging
|
49
|
+
# set `nil` to disable logging
|
50
50
|
config.logger = Rails.logger
|
51
51
|
|
52
52
|
# Optional: Configure default handler for controllers (HTTP requests)
|
53
|
-
config.
|
53
|
+
config.handle_controller_exceeded = -> {
|
54
|
+
# Default handler returns a JSON response with a 429 status code
|
54
55
|
render json: {
|
55
56
|
error: "Too many requests",
|
56
57
|
retry_after: response.headers["Retry-After"]
|
@@ -58,10 +59,9 @@ RailsRateLimit.configure do |config|
|
|
58
59
|
}
|
59
60
|
|
60
61
|
# Optional: Configure default handler for methods
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
Rails.logger.warn("Rate limit exceeded for #{self.class.name}")
|
62
|
+
# By default, it raises RailsRateLimit::RateLimitExceeded
|
63
|
+
config.handle_klass_exceeded = -> {
|
64
|
+
raise RailsRateLimit::RateLimitExceeded, "Rate limit exceeded"
|
65
65
|
}
|
66
66
|
end
|
67
67
|
```
|
@@ -99,7 +99,7 @@ end
|
|
99
99
|
|
100
100
|
### Rate Limiting Methods
|
101
101
|
|
102
|
-
You can limit any method
|
102
|
+
You can limit any instance method in your classes (class methods are not supported yet):
|
103
103
|
|
104
104
|
```ruby
|
105
105
|
class ApiClient
|
@@ -109,7 +109,7 @@ class ApiClient
|
|
109
109
|
# Your API call logic here
|
110
110
|
end
|
111
111
|
|
112
|
-
# IMPORTANT: set_rate_limit must be called AFTER method definition
|
112
|
+
# IMPORTANT: set_rate_limit must be called AFTER method definition
|
113
113
|
# Basic usage
|
114
114
|
set_rate_limit :make_request,
|
115
115
|
limit: 100,
|
@@ -122,10 +122,27 @@ class ApiClient
|
|
122
122
|
by: -> { "client:#{id}" }, # Method call identifier
|
123
123
|
store: :memcached, # Override default store
|
124
124
|
on_exceeded: -> { # Custom error handler
|
125
|
+
# You can handle the error here and return any value (including nil)
|
125
126
|
notify_admin
|
126
127
|
log_exceeded_event
|
127
|
-
|
128
|
+
nil # Method will return nil
|
128
129
|
}
|
130
|
+
|
131
|
+
# Example with default handler that raises error
|
132
|
+
set_rate_limit :risky_method,
|
133
|
+
limit: 5,
|
134
|
+
period: 1.minute
|
135
|
+
# Without on_exceeded option it will use default handler
|
136
|
+
# that raises RailsRateLimit::RateLimitExceeded
|
137
|
+
|
138
|
+
# Example with custom error handling
|
139
|
+
def safe_request
|
140
|
+
risky_method
|
141
|
+
rescue RailsRateLimit::RateLimitExceeded => e
|
142
|
+
# Handle the error
|
143
|
+
Rails.logger.warn("Rate limit exceeded: #{e.message}")
|
144
|
+
nil # or any other fallback value
|
145
|
+
end
|
129
146
|
end
|
130
147
|
```
|
131
148
|
|
@@ -135,8 +152,8 @@ For both controllers and methods:
|
|
135
152
|
- `limit`: (Required) Maximum number of requests/calls allowed
|
136
153
|
- `period`: (Required) Time period for the limit (in seconds or ActiveSupport::Duration)
|
137
154
|
- `by`: (Optional) Lambda/Proc to generate unique identifier
|
138
|
-
- Default for controllers: `request.remote_ip`
|
139
|
-
- Default for methods: `"#{
|
155
|
+
- Default for controllers: `"#{controller.class.name}:#{controller.request.remote_ip}"`
|
156
|
+
- Default for methods: `"#{self.class.name}##{method_name}:#{respond_to?(:id) ? 'id='+id.to_s : 'object_id='+object_id.to_s}"`
|
140
157
|
- `store`: (Optional) Override default storage backend (`:redis`, `:memcached`, `:memory`)
|
141
158
|
- `on_exceeded`: (Optional) Custom handler for rate limit exceeded
|
142
159
|
|
@@ -149,16 +166,34 @@ Additional options for controllers:
|
|
149
166
|
The gem provides different default behaviors for controllers and methods:
|
150
167
|
|
151
168
|
1. For controllers (HTTP requests):
|
152
|
-
- The `on_exceeded` handler (or
|
169
|
+
- The `on_exceeded` handler (or default handler) is called
|
153
170
|
- By default, returns HTTP 429 with a JSON error message
|
154
171
|
- Headers are automatically added with limit information
|
155
172
|
- The handler's return value is used (usually render/redirect)
|
156
173
|
|
157
174
|
2. For methods:
|
158
|
-
- The `on_exceeded` handler (
|
175
|
+
- The `on_exceeded` handler (if provided) is called first
|
176
|
+
- Then `RailsRateLimit::RateLimitExceeded` exception is raised
|
159
177
|
- The event is logged if a logger is configured
|
160
|
-
-
|
161
|
-
|
178
|
+
- You should catch the exception to handle the error
|
179
|
+
|
180
|
+
### Default error messages
|
181
|
+
|
182
|
+
By default, the gem logs the error message to the logger together with your custom `on_exceeded` message.
|
183
|
+
```ruby
|
184
|
+
@logger.warn(
|
185
|
+
"Rate limit exceeded for #{key}. " \
|
186
|
+
"Limit: #{limit} requests per #{period} seconds"
|
187
|
+
)
|
188
|
+
# where key for klass is `by` or default unique identifier
|
189
|
+
# Rate limit exceeded for ReportGenerator#generate:object_id=218520. Limit: 2 requests per 10 seconds
|
190
|
+
# Rate limit exceeded for Notification#deliver:id=1. Limit: 3 requests per 60 seconds
|
191
|
+
|
192
|
+
# where key for controller is `by` or default unique identifier
|
193
|
+
# Rate limit exceeded for HomeController:127.0.0.1. Limit: 100 requests per 1 minute
|
194
|
+
```
|
195
|
+
|
196
|
+
You can remove it by setting `config.logger = nil` or specify `by` options.
|
162
197
|
|
163
198
|
### HTTP Headers
|
164
199
|
|
@@ -183,7 +218,7 @@ For controller rate limiting, the following headers are automatically added:
|
|
183
218
|
- Works well in distributed environments
|
184
219
|
- Good option if you're already using Memcached
|
185
220
|
|
186
|
-
### Memory
|
221
|
+
### Memory (Default)
|
187
222
|
- No additional dependencies
|
188
223
|
- Perfect for development or single-server setups
|
189
224
|
- Data is lost on server restart
|
@@ -4,39 +4,40 @@
|
|
4
4
|
module RailsRateLimit
|
5
5
|
class Configuration
|
6
6
|
attr_accessor :default_store, :redis_connection, :memcached_connection, :logger
|
7
|
-
attr_reader :
|
7
|
+
attr_reader :handle_controller_exceeded, :handle_klass_exceeded
|
8
8
|
|
9
9
|
def initialize
|
10
|
-
@default_store = :
|
10
|
+
@default_store = :memory
|
11
11
|
@redis_connection = nil
|
12
12
|
@memcached_connection = nil
|
13
13
|
@logger = nil
|
14
14
|
set_default_handlers
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
raise ArgumentError, "
|
17
|
+
def handle_controller_exceeded=(handler)
|
18
|
+
raise ArgumentError, "handle_controller_exceeded must be a Proc" unless handler.is_a?(Proc)
|
19
19
|
|
20
|
-
@
|
20
|
+
@handle_controller_exceeded = handler
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
24
|
-
raise ArgumentError, "
|
23
|
+
def handle_klass_exceeded=(handler)
|
24
|
+
raise ArgumentError, "handle_klass_exceeded must be a Proc" unless handler.is_a?(Proc)
|
25
25
|
|
26
|
-
@
|
26
|
+
@handle_klass_exceeded = handler
|
27
27
|
end
|
28
28
|
|
29
29
|
private
|
30
30
|
|
31
31
|
def set_default_handlers
|
32
|
-
@
|
32
|
+
@handle_controller_exceeded = lambda {
|
33
33
|
render json: {
|
34
|
-
error: "Too many requests"
|
35
|
-
retry_after: response.headers["Retry-After"]
|
34
|
+
error: "Too many requests"
|
36
35
|
}, status: :too_many_requests
|
37
36
|
}
|
38
37
|
|
39
|
-
@
|
38
|
+
@handle_klass_exceeded = lambda {
|
39
|
+
raise RailsRateLimit::RateLimitExceeded, "Rate limit exceeded"
|
40
|
+
}
|
40
41
|
end
|
41
42
|
end
|
42
43
|
end
|
@@ -13,7 +13,7 @@ module RailsRateLimit
|
|
13
13
|
before_action(options) do |controller|
|
14
14
|
limiter = RateLimiter.new(
|
15
15
|
context: controller,
|
16
|
-
by: by,
|
16
|
+
by: by || "#{controller.class.name}:#{controller.request.remote_ip}",
|
17
17
|
limit: limit,
|
18
18
|
period: period.to_i,
|
19
19
|
store: store
|
@@ -22,7 +22,7 @@ module RailsRateLimit
|
|
22
22
|
begin
|
23
23
|
limiter.perform!
|
24
24
|
rescue RailsRateLimit::RateLimitExceeded
|
25
|
-
handler = on_exceeded.nil? ? RailsRateLimit.configuration.
|
25
|
+
handler = on_exceeded.nil? ? RailsRateLimit.configuration.handle_controller_exceeded : on_exceeded
|
26
26
|
controller.instance_exec(&handler)
|
27
27
|
end
|
28
28
|
end
|
@@ -13,7 +13,9 @@ module RailsRateLimit
|
|
13
13
|
define_method(method_name) do |*args, &block|
|
14
14
|
limiter = RateLimiter.new(
|
15
15
|
context: self,
|
16
|
-
by: by ||
|
16
|
+
by: by || lambda {
|
17
|
+
"#{self.class.name}##{method_name}:#{respond_to?(:id) ? "id=#{id}" : "object_id=#{object_id}"}"
|
18
|
+
},
|
17
19
|
limit: limit,
|
18
20
|
period: period.to_i,
|
19
21
|
store: store
|
@@ -23,9 +25,8 @@ module RailsRateLimit
|
|
23
25
|
limiter.perform!
|
24
26
|
original_method.bind(self).call(*args, &block)
|
25
27
|
rescue RailsRateLimit::RateLimitExceeded
|
26
|
-
handler = on_exceeded.nil? ? RailsRateLimit.configuration.
|
28
|
+
handler = on_exceeded.nil? ? RailsRateLimit.configuration.handle_klass_exceeded : on_exceeded
|
27
29
|
instance_exec(&handler)
|
28
|
-
nil
|
29
30
|
end
|
30
31
|
end
|
31
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_rate_limit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kasvit
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-01-
|
11
|
+
date: 2025-01-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dalli
|
@@ -169,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
169
169
|
- !ruby/object:Gem::Version
|
170
170
|
version: '0'
|
171
171
|
requirements: []
|
172
|
-
rubygems_version: 3.
|
172
|
+
rubygems_version: 3.4.10
|
173
173
|
signing_key:
|
174
174
|
specification_version: 4
|
175
175
|
summary: Flexible rate limiting for Rails applications
|