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

kdt*_*kdt 16 streaming zip ruby-on-rails

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

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

我知道我可以做流生成zip文件使用filesystemk ZipOutputStream作为这里.我也知道,我能做的设置流从轨控制器输出response_bodyProc作为这里.我需要(我认为)是一种将这两件事融合在一起的方法.我可以让铁杆响应来自ZipOutputStream吗?我可以ZipOutputStream给我增量的数据块,我可以提供给我response_body Proc吗?或者还有另一种方式吗?

fri*_*ngd 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                                                                                   
Run Code Online (Sandbox Code Playgroud)

编辑:

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

另一个编辑:

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

ZipStreamOutput类的子类如下:

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

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

好的最后编辑:

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

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

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

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

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

这是一颗宝石!

https://github.com/fringd/zipline


小智 3

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

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

    def stream
      @outputStream
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

从那里开始,只需在您的 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
Run Code Online (Sandbox Code Playgroud)

  • 这本身不起作用...zip 文件需要 size、compressed_size 和数据之前的 CRC...此代码只是在内存中构建文件,服务器仍然会等待,直到它完成才开始发送。使用我的 gem https://github.com/fringd/zipline (3认同)

归档时间:

查看次数:

10323 次

最近记录:

8 年 前