Rails:zip格式的输出即时流式传输?

我需要通过zip文件从我的数据库中提供一些数据,并在运行时将其流式传输:

  • 我不写临时文件到磁盘
  • 我没有在RAM中编写整个文件

我知道我可以使用ZipOutputStream将zip文件的流生成生成到filesystemk,就像这里一样 。 我也知道我可以通过将response_body设置为Proc来从rails控制器进行流输出。 我需要(我认为)是将这两件事融合在一起的一种方式。 我可以让rails提供ZipOutputStream的响应吗? 我可以获得ZipOutputStream给我增量的数据块,我可以将其提供给我的response_body Proc吗? 或者还有另一种方式吗?

我有一个类似的问题。 我不需要直接流,但只有你第一个不想写临时文件的情况。 您可以轻松修改ZipOutputStream以接受IO对象而不仅仅是文件名。

 module Zip class IOOutputStream < ZipOutputStream def initialize io super '-' @outputStream = io end def stream @outputStream end end end 

从那里开始,应该只是在你的Proc中使用新的Zip :: IOOutputStream。 在您的控制器中,您可能会执行以下操作:

 self.response_body = proc do |response, output| Zip::IOOutputStream.open(output) do |zip| my_files.each do |file| zip.put_next_entry file zip << IO.read file end end end 

精简版

https://github.com/fringd/zipline

长版

所以jo5h的答案在rails 3.1.1中对我不起作用

我找到了一个有帮助的YouTubevideo。

http://www.youtube.com/watch?v=K0XvnspdPsc

它的关键是创造一个对每个响应的对象……这就是我所做的:

  class ZipGenerator def initialize(model) @model = model end def each( &block ) output = Object.new output.define_singleton_method :tell, Proc.new { 0 } output.define_singleton_method :pos=, Proc.new { |x| 0 } output.define_singleton_method :<<, Proc.new { |x| block.call(x) } output.define_singleton_method :close, Proc.new { nil } Zip::IoZip.open(output) do |zip| @model.attachments.all.each do |attachment| zip.put_next_entry "#{attachment.name}.pdf" file = attachment.file.file.send :file file = File.open(file) if file.is_a? String while buffer = file.read(2048) zip << buffer end end end sleep 10 end end def getzip self.response_body = ZipGenerator.new(@model) #this is a hack to preven middleware from buffering headers['Last-Modified'] = Time.now.to_s end 

编辑:

上面的解决方案实际上没有工作......问题是rubyzip需要跳转文件来重写条目的标题。 特别是它需要在写入数据之前写入压缩大小。 这在真正的流媒体情况下是不可能的......所以最终这个任务可能是不可能的。 有可能一次缓冲整个文件,但这似乎不值得。 最终我只是写了一个tmp文件...在heroku上我可以写入Rails.root / tmp较少的即时反馈,并不理想,但是必要。

另一个编辑:

我最近得到了另一个想法...如果我们不压缩它们,我们可以知道文件的压缩大小。 计划是这样的:

ZipStreamOutput类的子类如下:

  • 总是使用“存储”压缩方法,换句话说就是不压缩
  • 确保我们永远不会向后寻找更改文件标题,事先做好准备
  • 重写与寻求的TOC相关的任何代码

我还没有尝试过这个,但会报告是否有任何成功。

OK ONE LAST EDIT:

在zip标准中: http : //en.wikipedia.org/wiki/Zip_(file_format)#File_headers

他们提到有一点你可以翻转来放大小,压缩大小和文件后的crc。 所以我的新计划是将zipoutput流子类化为它

  • 设置这个标志
  • 在数据之后写入大小和CRC
  • 永远不会倒退输出

此外,我需要得到所有的黑客,以便在固定的铁路输出流...

无论如何这一切都奏效了!

这是一颗gem!

https://github.com/fringd/zipline

现在可以直接执行此操作:

 class SomeController < ApplicationController def some_action compressed_filestream = Zip::ZipOutputStream.write_buffer do |zos| zos.put_next_entry "some/filename.ext" zos.print data end compressed_filestream .rewind respond_to do |format| format.zip do send_data compressed_filestream .read, filename: "some.zip" end end # or some other return of send_data end end 

这是您想要的链接:

http://info.michael-simons.eu/2008/01/21/using-rubyzip-to-create-zip-files-on-the-fly/

它使用ZipOutputStream构建并生成zipfile,然后使用send_file将其直接从控制器发送出去。

使用分块HTTP传输编码进行输出:HTTP标头“Transfer-Encoding:chunked”并根据分块编码规范重构输出,因此无需在传输开始时知道生成的ZIP文件大小。 可以在Open3.popen3和线程的帮助下在Ruby中轻松编码。