class Vox::HTTP::Middleware::RateLimiter

Faraday middleware to handle ratelimiting based on bucket ids and the rate limit key provided by {Client#request} in the request context

Constants

LOGGER

@!visibility private

Public Class Methods

new(app, **_options) click to toggle source
Calls superclass method
# File lib/vox/http/middleware/rate_limiter.rb, line 138
def initialize(app, **_options)
  super(app)
  @limit_table = LimitTable.new
  @mutex_table = Hash.new { |hash, key| hash[key] = Mutex.new }
end

Public Instance Methods

call(env) click to toggle source

Request handler

# File lib/vox/http/middleware/rate_limiter.rb, line 145
def call(env)
  rl_key = env.request.context[:rl_key]
  req_id = env.request.context[:trace]
  mutex = @mutex_table[rl_key]

  mutex.synchronize do
    rl_wait(rl_key, req_id)
    rl_wait(:global, req_id)
    @app.call(env).on_complete do |environ|
      on_complete(environ, req_id)
    end
  end
end
on_complete(env, req_id) click to toggle source

Handler for response data

# File lib/vox/http/middleware/rate_limiter.rb, line 160
def on_complete(env, req_id)
  resp = env.response

  if resp.status == 429 && resp.headers['x-ratelimit-global']
    @limit_table.update_from_headers(:global, resp.headers, req_id)
    Thread.new { @limit_table.get_from_key(:global).lock_until_reset }
    LOGGER.error { "{#{req_id}}} Global ratelimit hit" }
  end

  update_from_headers(env)
end

Private Instance Methods

rl_wait(key, trace) click to toggle source

Lock a rate limit mutex preemptively if the next request would deplete the bucket.

# File lib/vox/http/middleware/rate_limiter.rb, line 175
def rl_wait(key, trace)
  bucket_id = @limit_table.id_from_key(key)
  bucket = if bucket_id
             @limit_table.get_from_id(bucket_id)
           else
             @limit_table.get_from_key(key)
           end
  return if bucket.nil?

  bucket.wait_until_available
  return unless bucket.will_limit?

  LOGGER.info do
    duration = bucket.reset_time - Time.now
    "{#{trace}} [RL] Locking #{key} for #{duration.truncate(3)} seconds"
  end

  bucket.lock_until_reset
end
update_from_headers(env) click to toggle source
# File lib/vox/http/middleware/rate_limiter.rb, line 195
def update_from_headers(env)
  rl_key = env.request.context[:rl_key]
  req_id = env.request.context[:trace]
  @limit_table.update_from_headers(rl_key, env.response.headers, req_id)
end