Mark Reed
Mark Reed

Reputation: 95252

Write to file from stream block

Working on a web service that sometimes needs to return large files, and want it to send something to the client quickly so the client doesn't time out waiting for the start of the data. stream seemed perfect for this, but I ran into a problem.

Dumb example:

get '/path' do
  status 200
  headers 'Content-Type' => 'text/plain'
  stream do |out|
    sleep 1
    out << "Hello,\n"
    sleep 1
    out << "World!\n"
  end
end

This works fine:

$  curl http://localhost:4567/path
   Hello,
   World!

But I have a side log that the service writes to, and trying to mix File I/O with the streaming API doesn't work at all:

get '/path' do
  status 200
  headers 'Content-Type' => 'text/plain'
  File.open '/tmp/side-log', 'a' do |lf|
    stream do |out|
      lf.puts "Woo!"
      sleep 1
      out << "Hello,\n"
      sleep 1
      out << "World!\n"
    end
  end
end

Now I get this:

$ curl http://localhost:4567/path
curl: (18) transfer closed with outstanding read data remaining

Puma doesn't indicate any problems on the server side, but Thin exits entirely:

hello2.rb:13:in `write': closed stream (IOError)
        from hello2.rb:13:in `puts'
        from hello2.rb:13:in `block (3 levels) in <main>'
        from vendor/bundle/gems/sinatra-1.4.6/lib/sinatra/base.rb:437:in `block (2 levels) in stream'
        from vendor/bundle/gems/sinatra-1.4.6/lib/sinatra/base.rb:628:in `with_params'
        from vendor/bundle/gems/sinatra-1.4.6/lib/sinatra/base.rb:437:in `block in stream'
        from vendor/bundle/gems/sinatra-1.4.6/lib/sinatra/base.rb:403:in `call'
        from vendor/bundle/gems/sinatra-1.4.6/lib/sinatra/base.rb:403:in `block in each'
        from vendor/bundle/gems/eventmachine-1.0.8/lib/eventmachine.rb:1062:in `call'
        from vendor/bundle/gems/eventmachine-1.0.8/lib/eventmachine.rb:1062:in `block in spawn_threadpool'
[1]+  Exit 1                  ruby hello2.rb

So what should I do if I want to write something out to someplace other than the output stream from inside the stream block?

Upvotes: 2

Views: 631

Answers (1)

Mark Reed
Mark Reed

Reputation: 95252

Not sure if this is the best solution, but using the asynchronous em-files gem worked for me, even in Puma (which I understand is not EventMachine-based):

require 'em-files'  

get '/path' do
  status 200
  headers 'Content-Type' => 'text/plain'
  EM::File.open '/tmp/side-log', 'a' do |lf|
    stream do |out|
      lf.write "Woo!\n"
      sleep 1
      out << "Hello,\n"
      sleep 1
      out << "World!\n"
   end
  end
end

Upvotes: 2

Related Questions