Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trigger Rack middleware on specific Rails routes

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
like image 812
Cev Avatar asked Oct 01 '12 19:10

Cev


3 Answers

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
like image 149
Ian Avatar answered Nov 06 '22 09:11

Ian


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.

like image 20
Eduardo Avatar answered Nov 06 '22 11:11

Eduardo


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)

like image 1
mltsy Avatar answered Nov 06 '22 10:11

mltsy