Ruby on Rails 3:通过Rails将数据流式传输到客户端

jkn*_*rkn 44 streaming ruby-on-rails cloudfiles ruby-on-rails-3

我正在开发一个与RackSpace cloudfiles通信的Ruby on Rails应用程序(类似于Amazon S3但缺少某些功能).

由于缺乏每对象访问权限和查询字符串身份验证的可用性,因此必须通过应用程序调解用户下载.

在Rails 2.3中,看起来您可以动态构建响应,如下所示:

# Streams about 180 MB of generated data to the browser.
render :text => proc { |response, output|
  10_000_000.times do |i|
    output.write("This is line #{i}\n")
  end
}
Run Code Online (Sandbox Code Playgroud)

(来自http://api.rubyonrails.org/classes/ActionController/Base.html#M000464)

而不是10_000_000.times...我可以在那里转储我的cloudfiles流生成代码.

麻烦的是,这是我在Rails 3中尝试使用这种技术时得到的输出.

#<Proc:0x000000010989a6e8@/Users/jderiksen/lt/lt-uber/site/app/controllers/prospect_uploads_controller.rb:75>
Run Code Online (Sandbox Code Playgroud)

看起来可能call没有调用proc对象的方法?还有其他想法吗?

Joh*_*ohn 69

分配给response_body响应的对象#each:

class Streamer
  def each
    10_000_000.times do |i|
      yield "This is line #{i}\n"
    end
  end
end

self.response_body = Streamer.new
Run Code Online (Sandbox Code Playgroud)

如果您使用1.9.x或Backports gem,则可以使用以下命令更紧凑地编写Enumerator.new:

self.response_body = Enumerator.new do |y|
  10_000_000.times do |i|
    y << "This is line #{i}\n"
  end
end
Run Code Online (Sandbox Code Playgroud)

请注意,何时以及是否刷新数据取决于正在使用的Rack处理程序和底层服务器.例如,我已经确认Mongrel会传输数据,但是其他用户报告说WEBrick会缓冲它直到响应关闭.没有办法强制响应冲洗.

在Rails 3.0.x中,还有一些额外的问题:

  • 在开发模式中,由于与类重新加载的错误交互,从枚举中访问模型类等操作可能会出现问题.这是Rails 3.0.x中的一个漏洞.
  • Rack和Rails之间交互中的错误导致#each每个请求被调用两次.这是另一个开放的bug.您可以使用以下猴子补丁来解决它:

    class Rack::Response
      def close
        @body.close if @body.respond_to?(:close)
      end
    end
    
    Run Code Online (Sandbox Code Playgroud)

这两个问题都在Rails 3.1中得到修复,其中HTTP流是一个选框功能.

请注意,另一个常见建议self.response_body = proc {|response, output| ...}在Rails 3.0.x中可以正常工作,但在3.1中已被弃用(并且不再实际流式传输数据).分配响应的对象#each适用于所有Rails 3版本.


pan*_*kka 23

感谢上面的所有帖子,这里有完整的代码来传输大型CSV.这段代码:

  1. 不需要任何额外的宝石.
  2. 使用Model.find_each()以便不会使所有匹配对象膨胀内存.
  3. 已经在rails 3.2.5,ruby 1.9.3和heroku上使用独角兽进行了测试,单个dyno.
  4. 每500行添加一次GC.start,以免烧掉heroku dyno允许的内存.
  5. 您可能需要根据型号的内存占用情况调整GC.start.我已成功使用它将105K型号流式传输到9.7MB的csv而没有任何问题.

控制器方法:

def csv_export
  respond_to do |format|
    format.csv {
      @filename = "responses-#{Date.today.to_s(:db)}.csv"
      self.response.headers["Content-Type"] ||= 'text/csv'
      self.response.headers["Content-Disposition"] = "attachment; filename=#{@filename}"
      self.response.headers['Last-Modified'] = Time.now.ctime.to_s

      self.response_body = Enumerator.new do |y|
        i = 0
        Model.find_each do |m|
          if i == 0
            y << Model.csv_header.to_csv
          end
          y << sr.csv_array.to_csv
          i = i+1
          GC.start if i%500==0
        end
      end
    }
  end
end
Run Code Online (Sandbox Code Playgroud)

配置/ unicorn.rb

# Set to 3 instead of 4 as per http://michaelvanrooijen.com/articles/2011/06/01-more-concurrency-on-a-single-heroku-dyno-with-the-new-celadon-cedar-stack/
worker_processes 3

# Change timeout to 120s to allow downloading of large streamed CSVs on slow networks
timeout 120

#Enable streaming
port = ENV["PORT"].to_i
listen port, :tcp_nopush => false
Run Code Online (Sandbox Code Playgroud)

Model.rb

  def self.csv_header
    ["ID", "Route", "username"]
  end

  def csv_array
    [id, route, username]
  end
Run Code Online (Sandbox Code Playgroud)


Ste*_*ton 16

看起来这在Rails 3中不可用

https://rails.lighthouseapp.com/projects/8994/tickets/2546-render-text-proc

这在我的控制器中似乎对我有用:

self.response_body =  proc{ |response, output|
  output.write "Hello world"
}
Run Code Online (Sandbox Code Playgroud)

  • 从Rails 3.1开始不起作用.见约翰的回答. (3认同)

小智 7

如果您要为response_body分配一个响应#each方法的对象,并且在响应关闭之前进行缓冲,请在操作控制器中尝试:

self.response.headers ['Last-Modified'] = Time.now.to_s

  • 这是我的解决方案!虽然,我需要像这样格式化时间:Time.now.ctime.to_s (2认同)

小智 5

仅仅为了记录,rails> = 3.1有一种简单的方法来通过将响应#each方法的对象分配给控制器的响应来传输数据.

这里解释了一切:http://blog.sparqcode.com/2012/02/04/streaming-data-with-rails-3-1-or-3-2/