Class: Tina4::RateLimiterMiddleware

Inherits:
Object
  • Object
show all
Defined in:
lib/tina4/middleware.rb

Overview

RateLimiterMiddleware – tracks requests per IP, returns 429 when exceeded. Config via env: TINA4_RATE_LIMIT (default 100), TINA4_RATE_WINDOW (default 60s).

Class Method Summary collapse

Class Method Details

.before_rate_limit(request, response) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/tina4/middleware.rb', line 171

def before_rate_limit(request, response)
  limit  = (ENV["TINA4_RATE_LIMIT"]  || 100).to_i
  window = (ENV["TINA4_RATE_WINDOW"] || 60).to_i
  ip = request.ip || "unknown"
  now = Time.now

  cleanup_if_needed(now, window)

  @mutex.synchronize do
    @store[ip] ||= []
    entries = @store[ip]

    # Sliding window -- drop expired timestamps
    cutoff = now - window
    entries.reject! { |t| t < cutoff }

    if entries.length >= limit
      oldest = entries.first
      retry_after = [(oldest + window - now).ceil, 1].max

      response.headers["X-RateLimit-Limit"]     = limit.to_s
      response.headers["X-RateLimit-Remaining"]  = "0"
      response.headers["X-RateLimit-Reset"]      = (oldest + window).to_i.to_s
      response.headers["Retry-After"]            = retry_after.to_s
      response.json({ error: "Too Many Requests", retry_after: retry_after }, 429)

      return [request, response]
    end

    entries << now

    response.headers["X-RateLimit-Limit"]     = limit.to_s
    response.headers["X-RateLimit-Remaining"]  = (limit - entries.length).to_s
    response.headers["X-RateLimit-Reset"]      = (now + window).to_i.to_s
  end

  [request, response]
end

.reset!Object

Allow resetting state (useful in tests)



211
212
213
# File 'lib/tina4/middleware.rb', line 211

def reset!
  @mutex.synchronize { @store.clear }
end