我需要通过zip文件从我的数据库中提供一些数据,然后即时流式传输:
我知道我可以使用ZipOutputStream
作为here将zip文件的流生成生成到filesystemk。我也知道我可以通过将response_body
设置为Proc
作为here来从rails控制器执行流输出。我需要(我认为)是一种将这两件事融合在一起的方法。我是否可以通过ZipOutputStream
来提供回复?我可以ZipOutputStream
向我提供增量数据块,我可以将这些数据提供给response_body
Proc
吗?或者还有另一种方式吗?
答案 0 :(得分: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
编辑:
上面的解决方案实际上没有工作......问题是rubyzip需要跳转文件来重写条目的标题。特别是它需要在写入数据之前写入压缩大小。这在真正的流媒体情况下是不可能的......所以最终这个任务可能是不可能的。有可能一次缓冲整个文件,但这似乎不值得。最终我只是写了一个tmp文件...在heroku上我可以写入Rails.root / tmp更少的即时反馈,并不理想,但是必要。
另一个编辑:
我最近有了另一个想法...如果我们不压缩它们,我们可以知道文件的压缩大小。计划是这样的:
将ZipStreamOutput类子类化如下:
我还没有尝试过这个,但会报告是否有任何成功。
OK ONE LAST EDIT:
在zip标准中:http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers
他们提到有一点你可以翻转来放大小,压缩大小和文件后的crc。所以我的新计划是将zipoutput流子类化,以便
此外,我需要获得所有黑客以便在固定的轨道中输出输出...
无论如何它都奏效了!
这是一个宝石!
答案 1 :(得分:3)
我有类似的问题。我不需要直接流,但只有第一个不想写临时文件的情况。您可以轻松修改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
答案 2 :(得分:1)
现在可以直接执行此操作:
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
答案 3 :(得分:0)
这是您想要的链接:
http://info.michael-simons.eu/2008/01/21/using-rubyzip-to-create-zip-files-on-the-fly/
使用ZipOutputStream构建并生成zipfile,然后使用send_file将其直接从控制器发送出去。
答案 4 :(得分:0)
对输出使用分块HTTP传输编码:HTTP标头&#34;传输编码:分块&#34;并根据分块编码规范重构输出,因此无需在传输开始时知道生成的ZIP文件大小。可以在Open3.popen3和线程的帮助下在Ruby中轻松编码。