如何仅在内存中修改zip文件?

6
我有一个Ruby应用程序,需要修改现有的zip文件。
我想在内存中构建zip文件,并将字节流返回,而不必将文件写入文件系统。如果我最终在Heroku上托管它,我认为我无法写入文件系统。有人知道如何做到这一点吗?
我查看了Zip::ZipFile,但它似乎总是想要写入文件系统。我认为基于Java实现,我应该能够获取压缩文件的字节,在Java中可以这样做,但我没有找到方法。

编辑:

我的要求基本上与此相同,但是针对的是Ruby而不是Python: 创建内存中zip文件并作为http响应返回的函数

4个回答

4

我遇到了同样的问题,通过关闭文件、读取数据并将其作为send_data流式传输,使其正常工作。

然后我发现另一个库可以在Heroku上很好地处理内存缓冲区:它是zipruby(不是rubyzip)。

buffer = ''
Zip::Archive.open_buffer(buffer, Zip::CREATE) do |archive|
  files.each do |wood, report|
    title = wood.abbreviation+".txt"
    archive.add_buffer(title, report);
  end
end
file_name = "dimter_#{@offer.customerName}_#{Time.now.strftime("%m%d%Y_%H%M")}.zip"
send_data buffer, :type => 'application/zip', :disposition => 'attachment', :filename => file_name

3
这里有一篇与此问题有关的博客文章。它使用Tempfile,对我来说似乎是一个很好的解决方案(尽管请阅读评论以获取一些有用的额外讨论)。
以下是该文章中的一个示例:
def download_zip(image_list)
  if !image_list.blank?
    file_name = "pictures.zip"
    t = Tempfile.new("my-temp-filename-#{Time.now}")
    Zip::ZipOutputStream.open(t.path) do |z|
      image_list.each do |img|
        title = img.title
        title += ".jpg" unless title.end_with?(".jpg")
        z.put_next_entry(title)
        z.print IO.read(img.path)
      end
    end
    send_file t.path, :type => 'application/zip',
                      :disposition => 'attachment',
                      :filename => file_name
    t.close
  end
end

这个解决方案与Heroku兼容良好

Tempfile不会创建文件吗? - Mark Thomas
1
是的,马克(除非您的临时目录位于内存中),但rally25rs没有说明他为什么不想创建文件。我做出了一个假设,得出了一个解决方案,a)在Heroku上可以很好地工作,b)创建一个文件,但rally25rs将永远不必再考虑它,并且将被操作系统清理。如果这不能解决他/她的核心问题,我想知道。 - Jordan Running
我猜我的最初意图是为了解决Heroku的只读文件系统限制:http://docs.heroku.com/constraints#read-only-filesystem,但如果需要的话,我可以将其写入到临时文件中的/tmp目录。虽然我对Ruby还不太熟悉,但在Java中实现这个非常简单,只需将整个文件保留在内存缓冲区中,所以我认为在Ruby中也应该是如此。谢谢帮助! - CodingWithSpike

1
你可以通过修改Zip::ZipFile的newopen方法,允许使用StringIO句柄,然后直接在内存中进行I/O操作。

1

我打算在这里提出自己的答案,我认为更适合我想做的事情。这种方法真的不会生成文件(没有临时文件)。

由于ZipFile扩展了ZipCentralDirectory,并且实际上只是一堆围绕ZipCentralDirectory的便利方法,因此您可以直接使用ZipCentralDirectory而不是ZipFile进行操作。这将允许您使用IO流来创建和写入zip文件。再加上StringIO的使用,您可以从字符串中完成它:

  # load a zip file from a URL into a string
  resp = Net::HTTP.new("www.somewhere.com", 80).get("/some.zip")
  zip_as_string = response.body

  # open as a zip
  zip = Zip::ZipCentralDirectory.read_from_stream(StringIO.new(zip_as_string))

  # work with the zip file.
  # i just output the names of each entry to show that it was read correctly
  zip.each { |zf| puts zf.name }

  # write zip back to an output stream
  out = StringIO.new
  zip.write_to_stream(out)

  # use 'out' or 'out.string' to do whatever with the resulting zip file.
  out.string

更新:

实际上这根本不起作用。它将写入一个可读的zip文件,但仅包含zip文件的“目录”。所有内部文件的长度都为0。进一步挖掘Zip实现,看起来它只在内存中保存zip条目的“元数据”,并返回到底层文件以读取其他所有内容。基于此,似乎不可能在不写入文件系统的情况下使用Zip实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接