Class: Tina4::RateLimiter
- Inherits:
-
Object
- Object
- Tina4::RateLimiter
- Defined in:
- lib/tina4/rate_limiter.rb
Constant Summary collapse
- DEFAULT_LIMIT =
100- DEFAULT_WINDOW =
seconds
60
Instance Attribute Summary collapse
-
#limit ⇒ Object
readonly
Returns the value of attribute limit.
-
#window ⇒ Object
readonly
Returns the value of attribute window.
Instance Method Summary collapse
-
#apply(ip, response) ⇒ Object
Apply rate limit headers to a response object and return 429 if exceeded.
-
#before_rate_limit(request, response) ⇒ Object
Standardized middleware hook — enforces rate limiting before the route handler.
-
#check(ip) ⇒ Object
Check if the given IP is rate limited.
-
#entry_count ⇒ Object
Returns current entry count (for monitoring).
-
#initialize(limit: nil, window: nil) ⇒ RateLimiter
constructor
A new instance of RateLimiter.
-
#rate_limited?(ip) ⇒ Boolean
Convenience predicate.
-
#reset(ip = nil) ⇒ Object
Reset tracking for a specific IP (useful for testing).
Constructor Details
#initialize(limit: nil, window: nil) ⇒ RateLimiter
Returns a new instance of RateLimiter.
10 11 12 13 14 15 16 |
# File 'lib/tina4/rate_limiter.rb', line 10 def initialize(limit: nil, window: nil) @limit = (limit || ENV["TINA4_RATE_LIMIT"] || DEFAULT_LIMIT).to_i @window = (window || ENV["TINA4_RATE_WINDOW"] || DEFAULT_WINDOW).to_i @store = {} # ip => [timestamps] @mutex = Mutex.new @last_cleanup = Time.now end |
Instance Attribute Details
#limit ⇒ Object (readonly)
Returns the value of attribute limit.
8 9 10 |
# File 'lib/tina4/rate_limiter.rb', line 8 def limit @limit end |
#window ⇒ Object (readonly)
Returns the value of attribute window.
8 9 10 |
# File 'lib/tina4/rate_limiter.rb', line 8 def window @window end |
Instance Method Details
#apply(ip, response) ⇒ Object
Apply rate limit headers to a response object and return 429 if exceeded. Returns [status, headers_hash] or nil if allowed.
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/tina4/rate_limiter.rb', line 67 def apply(ip, response) result = check(ip) # Always set rate limit headers response.headers["X-RateLimit-Limit"] = result[:limit].to_s response.headers["X-RateLimit-Remaining"] = result[:remaining].to_s response.headers["X-RateLimit-Reset"] = result[:reset].to_s unless result[:allowed] response.headers["Retry-After"] = result[:retry_after].to_s response.status_code = 429 response.headers["content-type"] = "application/json; charset=utf-8" response.body = JSON.generate({ error: "Too Many Requests", retry_after: result[:retry_after] }) return false end true end |
#before_rate_limit(request, response) ⇒ Object
Standardized middleware hook — enforces rate limiting before the route handler.
90 91 92 93 94 |
# File 'lib/tina4/rate_limiter.rb', line 90 def before_rate_limit(request, response) ip = request.respond_to?(:ip) ? request.ip : "unknown" apply(ip, response) [request, response] end |
#check(ip) ⇒ Object
Check if the given IP is rate limited. Returns a hash with rate limit info:
{ allowed: true/false, limit:, remaining:, reset:, retry_after: }
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/tina4/rate_limiter.rb', line 21 def check(ip) now = Time.now cleanup_if_needed(now) @mutex.synchronize do @store[ip] ||= [] entries = @store[ip] # Remove expired entries (sliding window) cutoff = now - @window entries.reject! { |t| t < cutoff } if entries.length >= @limit # Rate limited oldest = entries.first reset_at = (oldest + @window).to_i retry_after = [(oldest + @window - now).ceil, 1].max { allowed: false, limit: @limit, remaining: 0, reset: reset_at, retry_after: retry_after } else entries << now { allowed: true, limit: @limit, remaining: @limit - entries.length, reset: (now + @window).to_i, retry_after: nil } end end end |
#entry_count ⇒ Object
Returns current entry count (for monitoring)
108 109 110 |
# File 'lib/tina4/rate_limiter.rb', line 108 def entry_count @mutex.synchronize { @store.length } end |
#rate_limited?(ip) ⇒ Boolean
Convenience predicate
61 62 63 |
# File 'lib/tina4/rate_limiter.rb', line 61 def rate_limited?(ip) !check(ip)[:allowed] end |
#reset(ip = nil) ⇒ Object
Reset tracking for a specific IP (useful for testing)
97 98 99 100 101 102 103 104 105 |
# File 'lib/tina4/rate_limiter.rb', line 97 def reset(ip = nil) @mutex.synchronize do if ip @store.delete(ip) else @store.clear end end end |