Search code examples
ruby-on-railsrubymiddlewarerack

How to catch a Rack RangeError in Rails 6


I have a Rails 6 app to which users can upload CSV files. Rails/Rack imposes a limit in the number of params that can be included in a request, and I've set this to a size larger than likely submissions to my app. However, I would like to return a friendly response if a too-large file is uploaded.

It looks like I need to add some custom middleware, to catch and rescue the error, but I can't get the code to work - the basic error is still raised without my rescue block being called.

The error from the server is:

Rack app error handling request { POST /[PATH_TO]/datasets }
#<RangeError: exceeded available parameter key space>

The code in my app/middleware/catch_errors.rb file is basically taken from a previous SO answer, where someone was catching ActionDispatch::ParamsParser::ParseError in JSON, but with my own code in the rescue block (which I realise may not work properly in this context, but that's not the issue right now):

class CatchErrors
  def initialize(_app)
    @app = _app
  end

  def call(_env)
    begin
      @app.call(_env)
    rescue RangeError => _error
      _error_output = "There were too many fields in the data you submitted: #{_error}"
      if env['HTTP_ACCEPT'] =~ /application\/html/
        Rails.logger.error("Caught RangeError: #{_error}")
        flash[:error_title] = 'Too many fields in your data'
        flash[:error_detail1] = _error_output
        render 'static_pages/error', status: :bad_request
      elsif env['HTTP_ACCEPT'] =~ /application\/json/
        return [
          :bad_request, { "Content-Type" => "application/json" },
          [ { status: :bad_request, error: _error_output }.to_json ]
        ]
      else
        raise _error
      end
    end
  end
end

I'm loading it in config.application.rb like this:

require_relative '../app/middleware/catch_errors'
...
config.middleware.use CatchErrors

I'm resetting the size limit for testing in app/initializers/rack.rb like this:

if Rack::Utils.respond_to?("key_space_limit=")
  Rack::Utils.key_space_limit = 1
end

Any help gratefully received!


Solution

  • First, execute command to see all middlewares:

    bin/rails middleware
    

    config.middleware.use place your middleware at the bottom of the middleware stack. Because of that it can not catch error. Try to place it at the top:

    config.middleware.insert_before 0, CatchErrors
    

    Another point to mention, may be you will need to config.middleware.move_after or even config.middleware.delete some middleware. For instance, while tinkering I needed to place:

    config.middleware.move_after CatchErrors, Rack::MiniProfiler