1

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!

JohnP
  • 1,229
  • 6
  • 24
  • you should be able to upload a file of arbitrary size. Yes there is a limit to the length of query params, but a file upload should not be inside the query params. Suggest you rethink your file upload scheme. – Les Nightingill Jul 20 '21 at 12:52
  • The issue is that I'm uploading the CSV after it's parsed, as a (potentially large) JSON object. And Rails has limit on how large this can be. Replacing this with uploading a single binary blob would not (IMO) be an improvement. – JohnP Jul 20 '21 at 14:34

1 Answers1

1

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
zswqa
  • 826
  • 5
  • 15
  • That was it, thanks - needed to insert the handler earlier in the middleware sequence. Now to work out how to actually handle the error properly in this context. (Can't just use my usual Rails tricks because none of that's available!) – JohnP Jul 20 '21 at 14:48
  • For future reference, the rescue clause ends up just needing to be: `rescue RangeError => _error \n return [400, { "Content-Type" => "application/json" }, [ "There were too many fields in the data you submitted: #{_error}" ] ] \n end` – JohnP Jul 20 '21 at 15:27