Search code examples
rubysinatra

How to use Rack::RewindableInput::Middleware in a Sinatra Ruby app?


This is the code that works just fine:

require 'sinatra'
post '/foo' do
  "Equals to #{params[:a]}!"
end

If I send POST request, it returns all right:

$ ruby foo.rb -p 8888
$ curl -X POST -H 'Content-Length: 5' --data 'a=444' http://localhost:8888/foo
Equals to 444!

Then, I modify the code, adding Rack::RewindableInput::Middleware because it's necessary for another case, where rewind was available in earlier versions of Rack:

require 'sinatra'
use Rack::RewindableInput::Middleware
post '/foo' do
  "Equals to #{params[:a]}!"
end

I'm getting this:

$ ruby foo.rb -p 8888
$ curl -X POST -H 'Content-Length: 5' --data 'a=444' http://localhost:8888/foo
Equals to !

What am I doing wrong?


Solution

  • This appears to be an interaction between the RewindableInput middleware and the MethodOverride middleware, which Sinatra adds by default.

    Sinatra adds any middleware you specify with use after its own default middleware, so incoming requests see MethodOverride first, and then RewindableInput.

    MethodOverride will parse the input (thus consuming the IO) if the content type indicates it is form data, looking for the _method parameter. It puts all the data into a hash in the request env object so that it can be used later.

    RewindableInput replaces the input object with a rewindable copy, but the input is now empty.

    This wouldn’t be a problem, since the form data has already been parsed into a hash, but Rack only reuses this hash if the underlying IO object is the same. Since the input is now empty, re-parsing it produces no data.

    A workaround is to swap the order of these two pieces of middleware. Use disable :method_override to prevent Sinatra adding it by itself, then add it after RewindableInput (alternatively you could just disable MethodOverride if you are not using it):

    disable :method_override
    
    use Rack::RewindableInput::Middleware
    use Rack::MethodOverride