Class: Rubino::API::Middleware::RateLimit

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/api/middleware/rate_limit.rb

Overview

Token-bucket rate limiter, applied BEFORE Auth so that the open endpoints (/v1/health, /v1/metrics) get their own per-IP ceiling and cannot be flooded by an unauthenticated client. Authenticated requests are keyed by the bearer token so a single API key cannot saturate the process by spraying connections from many IPs.

Buckets refill linearly over a 60-second window. Storage is a single in-memory hash with monotonic timestamps; safe for a single-process deployment. Multi-process / multi-host needs a shared backend (Redis, etc.) — defer until we actually scale out.

On exceed: 429 with the canonical error envelope

{ error: { code: "rate_limited", message: "...",
           details: { retry_after_seconds: N } } }

and a Retry-After header so well-behaved clients can back off without parsing the body.

Constant Summary collapse

DEFAULT_UNAUTH_PER_MINUTE =
60
DEFAULT_AUTH_PER_MINUTE =
600
WINDOW_SECONDS =
60.0

Instance Method Summary collapse

Constructor Details

#initialize(app, clock: nil) ⇒ RateLimit

Returns a new instance of RateLimit.



29
30
31
32
33
34
# File 'lib/rubino/api/middleware/rate_limit.rb', line 29

def initialize(app, clock: nil)
  @app = app
  @clock = clock || -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
  @buckets = {}
  @mutex = Mutex.new
end

Instance Method Details

#call(env) ⇒ Object



36
37
38
39
40
41
42
43
44
# File 'lib/rubino/api/middleware/rate_limit.rb', line 36

def call(env)
  return @app.call(env) unless enabled?

  key, capacity = bucket_for(env)
  allowed, retry_after = consume(key, capacity)
  return too_many(retry_after) unless allowed

  @app.call(env)
end