Ruby流式传输tar/gz

5

我想从内存中流式传输数据到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

就是这样!您可以将GzipWriter中的文件替换为任何IO以保持流式传输。感谢dw11wtq提供的帮助!


我还应该指出,这确实需要大量内存——在进入gzip流之前,它将使用整个tar填充StringIO。对于大文件的更好解决方案是在流之间创建缓冲区。当我开始实现时,我会添加代码来解决这个问题... - Tony R
1
请注意,gz.close函数将关闭输出IO(在本例中为文件)。如果要保持其打开状态,请使用gz.finish函数。 - Tony R
2个回答

7
请查看rubygems中的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流中读取输出?我不想触碰硬盘,所以不允许使用文件! - Tony R
此外,它需要是平台无关的,并且我不想依赖系统调用。我使用的工具需要是我自己可以打包的库,例如gems或rb文件。这就是为什么我远离libarchive-ruby的原因。 - Tony R
再看一遍,这可能会起作用。我相信zlib的Zlib :: GzipWriter可以同时使用输入和输出流,而TarWriter也可以使用StringIO,就像你提到的那样。我会尝试一下,如果它起作用,我会给你饼干的。 - Tony R
没问题,只需要使用TarWriter填充一个StringIO,然后再使用GzipWriter对该StringIO进行gzip。StringIO是您的好朋友 :) - d11wtq
我想知道StringIO的性能如何?如果我有大量的数据要流式传输,会怎么样?(我确实有...) - Tony R
显示剩余7条评论

0

基于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

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