【问题标题】:Rails: on-the-fly streaming of output in zip format?Rails:以zip格式即时输出流?
【发布时间】:2011-06-15 09:38:34
【问题描述】:

我需要以 zip 文件的形式从我的数据库中提供一些数据,并将其动态流式传输,以便:

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

我知道我可以使用ZipOutputStream 作为here 将zip 文件流式生成到文件系统k。我也知道我可以通过将response_body 设置为Prochere 来从rails 控制器进行流式输出。我需要(我认为)是一种将这两件事结合在一起的方法。我可以让 Rails 响应来自 ZipOutputStream 的响应吗?我可以得到ZipOutputStream 给我增量数据块,我可以将它们输入我的response_bodyProc 吗?还是有别的办法?

【问题讨论】:

  • ZipOutputStream 无法做到这一点,因为它在写入压缩数据时在流中来回寻找(请参阅ZipOutputStream#update_local_headers,从ZipOutputStream#close 调用)。因此,在操作完成之前不可能使用 ZipOutputStream 提供数据块。

标签: ruby-on-rails streaming zip


【解决方案1】:

短版

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                                                                                   

编辑:

上述解决方案实际上不起作用...问题是 ruby​​zip 需要在文件中跳转以重写条目的标题。特别是它需要在写入数据之前写入压缩大小。这在真正的流媒体情况下是不可能的……所以最终这项任务可能是不可能的。有可能一次缓冲整个文件,但这似乎不太值得。最终我只是写了一个 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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 这本身不起作用... zip 文件在数据之前需要大小、compressed_size 和 CRC... 此代码只是在内存中构建文件,服务器仍在等待,直到它完成开始发送。使用我的宝石github.com/fringd/zipline
    【解决方案3】:

    现在可以直接这样做了:

    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
    

    【讨论】:

      【解决方案4】:

      这是你想要的链接:

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

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

      【讨论】:

      • 不。该问题指定“这样......我不会将临时文件写入磁盘”。该示例创建了一个临时文件。它也或多或少与问题中的第一个链接相同。
      • 问题指定临时文件未写入光盘。合理的假设是您不希望临时文件堆积在某个随机目录中 - 必须被销毁。给出的解决方案在使用后立即销毁临时文件。如果有其他假设,请告诉我们 - 否则您的问题不完整。
      • 事实上 - 您的两个要求几乎是相互排斥的。它要么在磁盘上,要么在 RAM 中……那么你真正想要的是什么,为什么?
      • @TarynEast 您可以使用只有 100Mb RAM + 100 MB 硬盘驱动器的服务器压缩/发送整张 DVD。这意味着立即发送压缩内容而不是流式传输。所以kdt的要求并不是相互排斥的。也许 kdt 想要使用不太昂贵的服务器有效地发送大量数据。另一个优点是压缩和下载时间是并行的,而不是相加的。干杯!
      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-07-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-02-18
        • 2018-02-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多