Is it possible to trigger Rack middleware only on specific Rails routes?
For example, let's say I wanted to run a rate limiter middleware only on the api namespace.
namespace :api do
resources :users
end
I've had good success with Rack::Throttle for rate limiting. Subclass one of the built-in throttle classes and overload the allowed?
method. Your custom logic can check which controller is being accessed and apply the rate limit as needed.
class ApiThrottle < Rack::Throttle::Hourly
##
# Returns `false` if the rate limit has been exceeded for the given
# `request`, or `true` otherwise.
#
# Rate limits are only imposed on the "api" controller
#
# @param [Rack::Request] request
# @return [Boolean]
def allowed?(request)
path_info = (Rails.application.routes.recognize_path request.url rescue {}) || {}
# Check if this route should be rate-limited
if path_info[:controller] == "api"
super
else
# other routes are not throttled, so we allow them
true
end
end
end
Adding to Ian's answer, to setup up the ApiThrottle you have to:
# application.rb
require 'rack/throttle'
class Application < Rails::Application
...
config.require "api_throttle"
# max 100 requests per hour per ip
config.middleware.use ApiThrottle, :max => 100
...
end
# /lib/api_throttle.rb
# Ian's code here
One important thing to add is that, for me, the path_info[:controller]
came as "api/v1/cities"
and not only as "api"
. Of course, that is due to the namespace configuration. Therefore, take care when setting up the throttler.
You can also (now) use a Rails Engine to create an isolated set of routes that adds additional middleware to the stack for its mounted routes.
See https://stackoverflow.com/a/41515577/79079
(Unfortunately I found this question while looking to see if there was any simpler way to do it. Writing a custom middleware for every middleware that I wanted to add seems even more round-about than using a Rails::Engine)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With