2011-01-25 13 views
14

necesito para servir algunos datos de mi base de datos en un archivo zip, streaming sobre la marcha de tal manera que:Rieles: transmisión de salida en tiempo real en formato zip.

  • que no escriben en un archivo temporal en el disco
  • Yo no compongo el resto del documento RAM

sé que puedo hacer streaming de generación de archivos zip a la filesystemk usando ZipOutputStream como here. También sé que puedo hacer la salida de transmisión de un controlador de rieles configurando response_body en Proc como here. Lo que necesito (creo) es una forma de conectar esas dos cosas juntas. ¿Puedo hacer que los rieles respondan desde ZipOutputStream? ¿Puedo obtener ZipOutputStream darme fragmentos incrementales de datos que puedo cargar en mi response_bodyProc? ¿O hay otra manera?

+0

ZipOutputStream no puede hacer eso porque busca hacia adelante y hacia atrás a través de la secuencia mientras escribe los datos comprimidos (consulte 'ZipOutputStream # update_local_headers', llamado desde' ZipOutputStream # close'). Por lo tanto, es imposible servir trozos de datos con ZipOutputStream antes de que la operación se complete. –

Respuesta

3

Tuve un problema similar. No necesité transmitir directamente, pero solo tuve tu primer caso de no querer escribir un archivo temporal. Puede modificar fácilmente ZipOutputStream para aceptar un objeto IO en lugar de solo un nombre de archivo.

module Zip 
    class IOOutputStream < ZipOutputStream 
    def initialize io 
     super '-' 
     @outputStream = io 
    end 

    def stream 
     @outputStream 
    end 
    end 
end 

A partir de ahí, sólo debería ser una cuestión de usar la nueva postal :: IOOutputStream en su Proc. En su controlador, lo que probablemente hacer algo como:

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

esto no funciona solo ... los archivos zip esperan tamaño, tamaño_comprimido y un CRC antes de los datos ... este código simplemente crea el archivo en la memoria, y el servidor aún espera hasta que termine para comenzar a enviar. usa mi joya https://github.com/fringd/zipline – fringd

0

Este es el enlace que desea:

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

Se construye y genera el archivo zip utilizando ZipOutputStream y luego utiliza send_file enviarlo directamente desde el controlador.

+0

No. La pregunta especifica "tal que ... no escribo un archivo temporal en el disco". Ese ejemplo crea un archivo temporal. También es más o menos idéntico al primer enlace en la pregunta. – kdt

+0

La pregunta especifica que el archivo temporal no está escrito en el disco. La suposición razonable es que no desea que los archivos temporales se acumulen en un directorio aleatorio, sino que debe destruirse. La solución dada destruye el archivo temporal inmediatamente después de ser utilizado. Si hay una suposición alternativa, háganoslo saber o sus preguntas no están completas. –

+0

Tal como está, sus dos requisitos son casi mutuamente exclusivos. O está en el disco, o está en la RAM ... ¿qué es lo que realmente quieres y por qué? –

10

versión corta

https://github.com/fringd/zipline

Versión larga

así la respuesta de jo5h no funcionó para mí en los carriles 3.1.1

Encontré un video de youtube que ayudó, sin embargo.

http://www.youtube.com/watch?v=K0XvnspdPsc

el quid de la cuestión es la creación de un objeto que responde a cada ... esto es lo que hice:

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                     

EDIT:

la solución anterior en realidad no trabajo ... el problema es que rubyzip necesita saltar el archivo para volver a escribir los encabezados para las entradas a medida que avanza. particularmente necesita escribir el tamaño comprimido ANTES de escribir los datos. esto simplemente no es posible en una situación de transmisión real ... por lo que, en última instancia, esta tarea puede ser imposible. existe la posibilidad de que sea posible almacenar en búfer un archivo completo a la vez, pero esto parecía menos valioso. en última instancia, acabo de escribir en un archivo tmp ... en heroku puedo escribir en Rails.root/tmp menos comentarios instantáneos, y no es ideal, pero sí es necesario.

otra edición:

tengo otra idea recientemente ... podríamos saber el tamaño comprimido de los archivos si no comprimirlos. el plan es algo como esto:

subclase de la clase ZipStreamOutput de la siguiente manera:

  • siempre utilice el método de compresión "almacenado", en otras palabras, no comprima
  • garantizar que nunca buscar hacia atrás para cambiar el archivo cabeceras, lo entiendo bien por adelantado
  • reescribir cualquier código relacionado con TOC que busca

no he tratado de implementar esto todavía, pero informaré si hay es cualquier éxito.

OK UNO Última edición:

En el estándar postal: http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers

mencionan que hay un poco se puede dar la vuelta a poner el tamaño, el tamaño comprimido y CRC vez que un archivo. por lo que mi nuevo plan era subclase zipoutput corriente de modo que

  • pone esta marca indicadora
  • escribe tamaños y CRC después de los datos
  • nunca se rebobina salida

, además, que necesitaba para obtener toda la hacks para transmitir la salida en rieles fijos ...

de todos modos funcionó!

aquí hay una joya!

https://github.com/fringd/zipline

1

Ahora es posible hacer esto directamente:

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 
0

Uso chunked transferencia HTTP codificación para la salida: HTTP cabecera "Transfer-Encoding: fragmentada" y reestructurar la salida de acuerdo con la fragmentada Especificación de codificación, por lo que no es necesario conocer el tamaño del archivo ZIP resultante al inicio de la transferencia. Se puede codificar fácilmente en Ruby con la ayuda de Open3.popen3 y subprocesos.

Cuestiones relacionadas