cappie013
cappie013

Reputation: 2444

Rails Streaming

I try to stream a huge file from my Rails server my user. My final goal is to stream huge file through my server.

I search and find that I have to use self.response_body.

So, to test it, I wrote the following code :

class HomeController < ApplicationController
    def index
        self.response_body = proc {|resp, out|
            10.times do |x|
                out.write "count = #{x}"
                sleep 1
            end
        }
    end
end

But, when I request my Webserver I got the following answer :

curl  http://localhost:3000
#<Proc:0x0000010284d048@/Users/nicolas/Documents/development/Ruby/StreamTest/app/controllers/home_controller.rb:4>%

Do you have any idea ?

I'm working with Rails 4.1.4 on a Thin server

Upvotes: 3

Views: 700

Answers (3)

Darren Hicks
Darren Hicks

Reputation: 5076

There are a couple of things that need to happen use self.response_body in this manner:

  1. self.response_body needs to be set to a (ducktyped) Enumerator (must implement #each) and the Enumerator (or your #each method) should return String(y) values
  2. the action must set the response Headers appropriately for the client browser (and the Rack Middleware) to treat the file as a streaming download

Here's an example:

class HomeController < ApplicationController
  def index
    headers["Content-Type"] = "text/plain"
    headers["Content-disposition"] = "attachment; filename=\"index.txt\""
    # Rack::Etag > 2.1.x will break streaming unless Last-Modified is set
    headers["Last-Modified"] = Time.current.httpdate   
    headers['X-Accel-Buffering'] = 'no'
    headers["Cache-Control"] ||= "no-cache"
    headers.delete("Content-Length")

    self.response_body = Enumerator.new do |yielder|
      50.times do |x|
        # Don't yield integers to the response_body unless you like getting:
        # #<NoMethodError: undefined method `bytesize' for 0:Integer
        yielder << x.to_s
        sleep 0.1
      end
    end    
  end
end

One common pitfall to this pattern is that there are similar solutions to this existing on blogs that do not include

headers["Last-Modified"] = Time.current.httpdate

Which only became necessary with the addition of this commit to Rack::ETag in the Rack 2.2.x and beyond.

Upvotes: 4

Erwin Schens
Erwin Schens

Reputation: 434

What you are doing here is writing a Proc to the response_body. So the value of your response body will be Proc#to_s. What you want to do is the following:

class HomeController < ApplicationController
   include ActionController::Live

   def index
     response.headers['Content-Type'] = 'text/event-stream'
     10.times.each do |x|
       response.stream.write "count = #{x}"
       sleep 1
     end
     response.stream.close
   end
end

If you want to stream a file you need to consider setting the right response headers.

Upvotes: 0

knotito
knotito

Reputation: 1392

I think you try to stream output there, here is the documentation

http://api.rubyonrails.org/classes/ActionController/Streaming.html

Upvotes: -1

Related Questions