matadon / mizuno

Jetty-powered running shoes for JRuby/Rack.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Reading rack.input and rewinding multiple times is not possible

lucsky opened this issue · comments

Here's a contrived example demonstrating the problem:

require 'rubygems'
require 'rack'

class Test
  def call(env)
    input = env["rack.input"]
    puts "first read: '#{input.read}'"
    input.rewind
    puts "second read: '#{input.read}'"

    [200, {"Content-Type" => "text/plain"}, "Hello World"]
  end
end

run Test.new

Launch a mizuno server for this app and make a POST request like this:

curl -X POST -d "foo=bar" localhost:9292

Expected result is:

first read: 'foo=bar'
second read: 'foo=bar'
0:0:0:0:0:0:0:1%0 - - [24/Jul/2012 19:27:03] "POST / HTTP/1.1" 200 - 0.0070

But actually is:

first read: 'foo=bar'
second read: ''
0:0:0:0:0:0:0:1%0 - - [24/Jul/2012 19:27:03] "POST / HTTP/1.1" 200 - 0.0070

Here's a simple spec for this, which might be easier to use than the full example above:

it "should read data than rewind and read again when wrapped in a Ruby IO object" do
    str = "hello world"
    input = str.unpack('c*')
    io = rewindable_input_stream(input.to_java(:byte)).to_io
    io.read.should == str
    io.rewind
    io.read.should == str
end

Adding this to spec/rewindable_input_stream_spec.rb makes the specs fail and actually points fingers at the fact that the RewindableInputStream is wrapped in a Ruby IO object by the to_io call. So this is either an incorrect use of RewindableInputStream or a bug somewhere else.

And I think I'm actually going to submit this to the jruby-rack guys too to have their input on it.

(done: jruby/jruby-rack#113)

So. Calling to_io on a RewindableInputStream actually breaks its "rewindability", which basically makes Mizuno broken in its current state.

The question that I have now is why not simply using Rack::RewindableInput instead? Like this:

env['rack.input'] = Rack::RewindableInput.new(request.getInputStream.to_io.binmode)

This might be less efficient than using a straight RewindableInputStream because Rack::RewindableInput always buffers to a temp file instead of in memory below a certain threshold, but at least it's not broken :)

Thoughts?

Changing it to the way you supposed actually works pretty well for me, otherwise Grape (https://github.com/intridea/grape) can't be used properly (can't get POST body) in conjunction with Mizuno.
I don't know the best way to fix it though - could the owner of this project fix it somehow or should we do a fork? The latest maintenance of the project was 6 months ago...
For now I'll just monkey-patch the servlet_to_rack method locally myself which breaks future changes to this part of mizuno of course :-(

Unicorn 3.0 handles it in the following way:

Rewindable "rack.input" may be disabled via the
"rewindable_input false" directive in the configuration file.
This will violate Rack::Lint for Rack 1.x applications, but can
reduce I/O for applications that do not need a rewindable
input.

jruby-rack:

  • The previous non-rewindable input behavior can be re-instated by
    setting the system property 'jruby.rack.input.rewindable' to false.
    Despite being out of spec, environments such as Google AppEngine
    require this behavior because writing to a tempfile is not possible.

Perhaps it would make sense to make it an option.

I tend to get distracted by my client work, but I'll see if I can carve out some time in the near future to fix this, with Rack::RewindableInput as a configurable option -- some users, myself included, probably don't want to buffer input streams to files by default for both security and performance reasons.

Of course, a pull request with specs that doesn't break existing functionality would get merged-and-released much more quickly. :)

This bug should probably be marked as closed.

Agreed! Thanks.