【问题标题】:Ruby streaming tar/gzRuby 流式传输 tar/gz
【发布时间】:2025-11-26 18:05:02
【问题描述】:

基本上,我想将内存中的数据流式传输到 tar/gz 格式(可能将多个文件放入 tar,但它永远不会触及硬盘,只能流式传输!),然后将它们流式传输到其他地方(我的 HTTP 请求正文案例)。

有人知道可以做到这一点的现有图书馆吗? Rails 中有什么东西吗?

libarchive-ruby 只是一个 C 包装器,并且似乎非常依赖于平台(文档希望您作为安装步骤进行编译?!)。

解决方案:

require 'zlib'
require 'rubygems/package'

tar = StringIO.new

Gem::Package::TarWriter.new(tar) { |writer|
  writer.add_file("a_file.txt", 0644) { |f| 
    (1..1000).each { |i| 
      f.write("some text\n")
    }
  }
  writer.add_file("another_file.txt", 0644) { |f| 
    f.write("some more text\n")
  }
}
tar.seek(0)

gz = Zlib::GzipWriter.new(File.new('this_is_a_tar_gz.tar.gz', 'wb'))  # Make sure you use 'wb' for binary write!
gz.write(tar.read)
tar.close
gz.close

就是这样!您可以使用任何 IO 替换 GzipWriter 中的文件以保持其流式传输。 dw11wtq 的 Cookie!

【问题讨论】:

  • 我还应该指出,这确实是内存密集型的——它会在进入 gzip 流之前用整个 tar 填充 StringIO。对于大文件,更好的解决方案是在流之间创建一个缓冲区。当我开始实现它时,我会为此添加代码......
  • 还要注意 gz.close 也会关闭输出 IO(本例中是文件)。要保持打开状态,请使用 gz.finish

标签: ruby stream tar gzip


【解决方案1】:

看看 ruby​​gems 中的 TarWriter 类:http://rubygems.rubyforge.org/rubygems-update/Gem/Package/TarWriter.html 它只是在一个 IO 流上操作,可能是 StringIO。

tar = StringIO.new

Gem::Package::TarWriter.new(tar) do |writer|
  writer.add_file("hello_world.txt", 0644) { |f| f.write("Hello world!\n") }
end

tar.seek(0)

p tar.read #=> mostly padding, but a tar nonetheless

如果您需要 tarball 中的目录布局,它还提供添加目录的方法。

作为参考,您可以使用IO.popen 实现gzipping,只需将数据输入/输出系统进程:

http://www.ruby-doc.org/core-1.9.2/IO.html#method-c-popen

gzipping 本身看起来像这样:

gzippped_data = IO.popen("gzip", "w+") do |gzip|
  gzip.puts "Hello world!"
  gzip.close_write
  gzip.read
end
# => "\u001F\x8B\b\u0000\xFD\u001D\xA2N\u0000\u0003\xF3H\xCD\xC9\xC9W(\xCF/\xCAIQ\xE4\u0002\u0000A䩲\r\u0000\u0000\u0000"

【讨论】:

  • 是否可以同时写入 tar/gz 函数并从 IO 流中读取输出?我不想碰硬盘,所以不允许文件!
  • 另外,它需要独立于平台,我宁愿不依赖系统调用。我使用的工具需要是我可以自己打包的库,比如 gems 或 rb 文件。这就是我离开 libarchive-ruby 的原因。
  • 再看一遍,这可能行得通。我相信 zlib'z Zlib::GzipWriter 可以将流用于输入和输出,而 TarWriter 也可以使用 StringIO,正如您所提到的。如果可行,我会尝试并给你 cookie。
  • 是的,只需使用 TarWriter 用 tar 数据填充 StringIO,然后使用 GzipWriter 压缩该 StringIO。 StringIO 是你的朋友 :)
  • 我想知道StringIO的性能如何?如果我要流式传输大量数据怎么办? (我确实...)
【解决方案2】:

根据 OP 写的解决方案,我编写了完整的内存 tgz 归档函数,我想用它来 POST 到 Web 服务器。

  # Create tar gz archive file from files, on the memory.
  # Parameters:
  #   files: Array of hash with key "filename" and "body"
  #     Ex: [{"filename": "foo.txt", "body": "This is foo.txt"},...]
  #
  # Return:: tar_gz archived image as string
  def create_tgz_archive_from_files(files)
    tar = StringIO.new
    Gem::Package::TarWriter.new(tar){ |tar_writer|
      files.each{|file|
        tar_writer.add_file(file['filename'], 0644){|f|
          f.write(file['body'])
        }
      }
    }
    tar.rewind

    gz = StringIO.new('', 'r+b')
    gz.set_encoding("BINARY")
    gz_writer = Zlib::GzipWriter.new(gz)
    gz_writer.write(tar.read)
    tar.close
    gz_writer.finish
    gz.rewind
    tar_gz_buf = gz.read
    return tar_gz_buf
  end

【讨论】: