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

时间:2011-01-25 18:19:55

标签: ruby-on-rails streaming zip

我需要通过zip文件从我的数据库中提供一些数据,然后即时流式传输:

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

我知道我可以使用ZipOutputStream作为here将zip文件的流生成生成到filesystemk。我也知道我可以通过将response_body设置为Proc作为here来从rails控制器执行流输出。我需要(我认为)是一种将这两件事融合在一起的方法。我是否可以通过ZipOutputStream来提供回复?我可以ZipOutputStream向我提供增量数据块,我可以将这些数据提供给response_body Proc吗?或者还有另一种方式吗?

5 个答案:

答案 0 :(得分:11)

短版

https://github.com/fringd/zipline

长版

所以jo5h的回答在rails 3.1.1

中对我不起作用

我找到了一个有帮助的YouTube视频。

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
  • 永不倒退输出

此外,我需要获得所有黑客以便在固定的轨道中输出输出...

无论如何它都奏效了!

这是一个宝石!

https://github.com/fringd/zipline

答案 1 :(得分:3)

我有类似的问题。我不需要直接流,但只有第一个不想写临时文件的情况。您可以轻松修改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

答案 2 :(得分:1)

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

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

答案 3 :(得分:0)

这是您想要的链接:

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

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

答案 4 :(得分:0)

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