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

我正在开发与RackSpace云文件(类似于Amazon S3,但缺less某些function)的Ruby on Rails应用程序。

由于缺乏每个对象访问权限和查询stringauthentication的可用性,下载给用户必须通过应用程序进行调解。

在Rails 2.3中,它看起来像你可以dynamic构build一个响应,如下所示:

# 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 } 

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

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

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

 #<Proc:0x000000010989a6e8@/Users/jderiksen/lt/lt-uber/site/app/controllers/prospect_uploads_controller.rb:75> 

看起来也许proc对象的call方法不被调用? 任何其他的想法?

看起来这在Rails 3中不可用

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

这似乎在我的控制器中为我工作:

 self.response_body = proc{ |response, output| output.write "Hello world" } 

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 

如果您正在使用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 

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

在Rails 3.0.x中,还有几个额外的陷阱:

  • 在开发模式中,由于与类重新加载的交互不良,在枚举中执行诸如访问模型类等事情可能会产生问题。 这是Rails 3.0.x中的一个开放的错误 。
  • Rack和Rails之间交互中的一个错误导致每个请求都调用两次#each 。 这是另一个开放的错误 。 你可以用下面的猴子补丁来解决它:

     class Rack::Response def close @body.close if @body.respond_to?(:close) end end 

这两个问题在Rails 3.1中都得到解决,在那里HTTPstream是一个选取框function。

注意其他常见的build议, self.response_body = proc {|response, output| ...} self.response_body = proc {|response, output| ...}在Rails 3.0.x中可以工作,但是在3.1中已经被弃用(并且不再实际的stream式传输数据)。 指定响应#each的对象在所有Rails 3版本中都#each

感谢上面的所有post,这里是完整的工作代码stream大CSV。 此代码:

  1. 不需要任何额外的gem。
  2. 使用Model.find_each()以便不会使所有匹配对象的内存膨胀。
  3. 已经在轨道3.2.5,ruby1.9.3和heroku使用独angular兽,与单一的测功机testing。
  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 

configuration/ 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 

Model.rb

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

如果你正在给response_body分配一个响应#each方法的对象,并且这个对象正在缓冲,直到响应被closures,请在action controller中尝试:

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

为了logging,rails> = 3.1通过将响应#each方法的对象分配给控制器的响应,可以轻松地传输数据。

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

是的,response_body是目前这样做的Rails 3方法: https : //rails.lighthouseapp.com/projects/8994/tickets/4554-render-text-procregression

这也解决了我的问题 – 我有gzip的CSV文件,想要发送给用户解压缩的CSV,所以我一次读一行,使用GzipReader。

如果您尝试下载大文件,这些行也很有帮助:

self.response.headers["Content-Type"] = "application/octet-stream" self.response.headers["Content-Disposition"] = "attachment; filename=#{filename}"

另外,你必须自己设置“Content-Length”标题。

否则,Rack将不得不等待(将主体数据caching到内存中)以确定长度。 这将会毁掉你的努力,使用上述的方法。

就我而言,我可以确定长度。 在不能的情况下,您需要让Rack开始发送没有“Content-Length”标题的主体。 尝试在“运行”之前的“require”之后添加到config.ru“use Rack :: Chunked”中。 (谢谢arkadiy)

我在灯塔的评论中,只是想说self.response_body = proc的方法为我工作,虽然我需要使用Mongrel而不是WEBrick成功。

马丁

将John的解决scheme和Exequiel的build议一起使用,

该声明

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

将响应标记为在机架中不可caching。

进一步调查后,我觉得还可以用这个:

 headers['Cache-Control'] = 'no-cache' 

这对我来说,只是稍微直观些。 它将信息传达给可能正在阅读我的代码的其他人。 此外,如果未来版本的机架停止检查Last-Modified,很多代码可能会中断,人们可能需要一段时间才能找出原因。

Interesting Posts