remo recuperar que mac guardado fue comprimir como archivos archivo php bash zip pipe lamp
la extensión ZIP

php - recuperar - LAMP: Cómo crear.Zip de archivos de gran tamaño para el usuario sobre la marcha, sin agitación de disco/CPU



remo zip repair (6)

A menudo, un servicio web necesita comprimir varios archivos grandes para descargarlos por el cliente. La forma más obvia de hacerlo es crear un archivo zip temporal, y luego echo para el usuario o guardarlo en el disco y redirigirlo (eliminándolo en el futuro).

Sin embargo, hacer las cosas de esa manera tiene inconvenientes:

  • una fase inicial de CPU intensiva y agolpamiento de disco, lo que resulta en ...
  • un retraso inicial considerable para el usuario mientras se prepara el archivo
  • muy alta huella de memoria por solicitud
  • uso de espacio sustancial en el disco temporal
  • si el usuario cancela la descarga a la mitad, todos los recursos utilizados en la fase inicial (CPU, memoria, disco) se habrán desperdiciado

Las soluciones como ZipStream-PHP mejoran al descargar los datos en archivos de Apache por archivo. Sin embargo, el resultado sigue siendo un uso elevado de la memoria (los archivos se cargan por completo en la memoria) y picos grandes y truculentos en el uso del disco y la CPU.

Por el contrario, considere el siguiente fragmento bash:

ls -1 | zip -@ - | cat > file.zip # Note -@ is not supported on MacOS

Aquí, zip funciona en modo de transmisión, lo que resulta en una huella de memoria baja. Una tubería tiene una memoria intermedia integral: cuando la memoria intermedia está llena, el sistema operativo suspende el programa de escritura (programa a la izquierda de la tubería). Esto aquí asegura que zip funciona solo tan rápido como su salida puede ser escrita por cat .

La forma óptima, entonces, sería hacer lo mismo: reemplazar cat con un proceso de servidor web, transmitiendo el archivo zip al usuario con la creación creada sobre la marcha. Esto crearía poca sobrecarga en comparación con la transmisión de los archivos, y tendría un perfil de recursos no problemático y sin espinas.

¿Cómo se puede lograr esto en una pila LAMP?


Al tratar de implementar una descarga dinámica generada con muchos archivos con diferentes tamaños encontré esta solución, pero me encontré con varios errores de memoria como "Tamaño de memoria permitido de 134217728 bytes agotados en ...".

Después de agregar ob_flush(); justo antes del flush(); los errores de memoria desaparecen

Junto con el envío de los encabezados, mi solución final se ve así (solo almacenando los archivos dentro del zip sin estructura de directorios):

<?php // Sending headers header(''Content-Type: application/zip''); header(''Content-Disposition: attachment; filename="download.zip"''); header(''Content-Transfer-Encoding: binary''); ob_clean(); flush(); // On the fly zip creation $fp = popen(''zip -0 -j -q -r - file1 file2 file3'', ''r''); while (!feof($fp)) { echo fread($fp, 8192); ob_flush(); flush(); } pclose($fp);


De acuerdo con el manual de PHP , la extensión ZIP proporciona un zip: wrapper.

Nunca lo he usado y no conozco su funcionamiento interno, pero lógicamente debería poder hacer lo que está buscando, suponiendo que se puedan transmitir archivos ZIP, algo de lo que no estoy completamente seguro.

En cuanto a su pregunta sobre la "pila LAMP", no debería ser un problema, siempre que PHP no esté configurado para almacenar el buffer .

Editar: Estoy tratando de poner una prueba de concepto juntos, pero parece no-trivial. Si no tiene experiencia con las transmisiones de PHP, puede resultar demasiado complicado, incluso si es posible.

Editar (2): releyendo tu pregunta después de echar un vistazo a ZipStream, encontré cuál será tu principal problema aquí cuando dices (énfasis agregado)

el Zipping operativo debe operar en modo de transmisión, es decir, procesar archivos y proporcionar datos a la velocidad de la descarga .

Esa parte será extremadamente difícil de implementar porque no creo que PHP proporcione una forma de determinar qué tan lleno está el búfer de Apache. Entonces, la respuesta a su pregunta es no, probablemente no podrá hacer eso en PHP.



Otra solución es mi módulo mod_zip para Nginx, escrito específicamente para este propósito:

https://github.com/evanmiller/mod_zip

Es extremadamente liviano y no invoca un proceso "zip" separado o se comunica a través de tuberías. Simplemente apunta a una secuencia de comandos que enumera las ubicaciones de los archivos que se incluirán, y mod_zip hace el resto.


Parece que puedes eliminar cualquier problema relacionado con el buffer de salida usando fpassthru() . También uso -0 para ahorrar tiempo de CPU ya que mis datos ya son compactos. Utilizo este código para servir una carpeta entera, comprimida al vuelo:

chdir($folder); $fp = popen(''zip -0 -r - .'', ''r''); header(''Content-Type: application/octet-stream''); header(''Content-disposition: attachment; filename="''.basename($folder).''.zip"''); fpassthru($fp);


Puede usar popen() (docs) o proc_open() (docs) para ejecutar un comando de Unix (por ejemplo, zip o gzip), y volver a stdout como una secuencia de php. flush() (docs) hará todo lo posible para enviar los contenidos del buffer de salida de php al navegador.

Al combinar todo esto, obtendrá lo que desea (siempre que nada más se interponga en el camino, consulte especialmente las advertencias en la página de documentos para flush() ).

( Nota : no use flush() . Consulte la actualización a continuación para obtener más información).

Algo como lo siguiente puede hacer el truco:

<?php // make sure to send all headers first // Content-Type is the most important one (probably) // header(''Content-Type: application/x-gzip''); // use popen to execute a unix command pipeline // and grab the stdout as a php stream // (you can use proc_open instead if you need to // control the input of the pipeline too) // $fp = popen(''tar cf - file1 file2 file3 | gzip -c'', ''r''); // pick a bufsize that makes you happy (64k may be a bit too big). $bufsize = 65535; $buff = ''''; while( !feof($fp) ) { $buff = fread($fp, $bufsize); echo $buff; } pclose($fp);

Usted preguntó sobre "otras tecnologías": a lo que le diré, "cualquier cosa que admita E / S no bloqueante para todo el ciclo de vida de la solicitud". Podrías construir un componente como un servidor independiente en Java o C / C ++ (o cualquiera de muchos otros idiomas disponibles), si estuvieras dispuesto a entrar en el "descuido" del acceso a archivos sin bloqueo y otras cosas.

Si desea una implementación sin bloqueo, pero prefiere evitar el "down and dirty", la ruta más fácil (en mi humilde opinión) sería usar nodeJS . Existe un amplio soporte para todas las características que necesita en la versión existente de nodejs: use el módulo http (por supuesto) para el servidor http; y use el módulo child_process para generar la canalización tar / zip / whatever.

Finalmente, si (y solo si) está ejecutando un servidor multiprocesador (o multi-core), y desea obtener el máximo provecho de nodejs, puede usar Spark2 para ejecutar varias instancias en el mismo puerto. No ejecute más de una instancia de nodo por instancia de procesador.

Actualización (de los excelentes comentarios de Benji en la sección de comentarios sobre esta respuesta)

1. Los documentos para fread() indican que la función solo leerá hasta 8192 bytes de datos a la vez de cualquier cosa que no sea un archivo normal. Por lo tanto, 8192 puede ser una buena opción de tamaño de búfer.

[nota editorial] 8192 es casi seguro un valor dependiente de la plataforma: en la mayoría de las plataformas, fread() leerá los datos hasta que el búfer interno del sistema operativo esté vacío, en cuyo punto volverá, permitiendo que el sistema operativo vuelva a llenar el búfer de forma asincrónica. 8192 es el tamaño del búfer predeterminado en muchos sistemas operativos populares.

Hay otras circunstancias que pueden hacer que fread devuelva incluso menos de 8192 bytes; por ejemplo, el cliente (o proceso) "remoto" tarda en llenar el búfer; en la mayoría de los casos, fread() devolverá el contenido de la entrada búfer tal como está sin esperar a que se llene. Esto podría significar que se devuelven bytes desde 0..os_buffer_size.

La moraleja es: el valor que pasa a fread() ya que buffsize debe considerarse como un tamaño "máximo" - nunca suponga que ha recibido el número de bytes que solicitó (o cualquier otro número para ese asunto).

2. Según los comentarios de los documentos de fread, algunas advertencias: las comillas mágicas pueden interferir y deben desactivarse .

3. Establecer mb_http_output(''pass'') (docs) puede ser una buena idea. Aunque ''pass'' ya es la configuración predeterminada, es posible que deba especificarlo explícitamente si su código o configuración lo ha cambiado previamente a otra cosa.

4. Si está creando un archivo comprimido (en lugar de gzip), le gustaría usar el encabezado del tipo de contenido:

Content-type: application/zip

o ... en su lugar se puede usar ''application / octet-stream''. (es un tipo de contenido genérico utilizado para descargas binarias de todos los tipos):

Content-type: application/octet-stream

y si desea que se le solicite al usuario que descargue y guarde el archivo en el disco (en lugar de que el navegador trate de mostrar el archivo como texto), necesitará el encabezado de disposición de contenido. (donde nombre de archivo indica el nombre que debe sugerirse en el diálogo de guardar):

Content-disposition: attachment; filename="file.zip"

También se debe enviar el encabezado Content-length, pero esto es difícil con esta técnica, ya que no se conoce el tamaño exacto del zip por adelantado. ¿Hay un encabezado que se puede configurar para indicar que el contenido es "de transmisión" o es de longitud desconocida? ¿Alguien sabe?

Finalmente, aquí hay un ejemplo revisado que usa todas sugerencias de @ (y que crea un archivo ZIP en lugar de un archivo TAR.GZIP):

<?php // make sure to send all headers first // Content-Type is the most important one (probably) // header(''Content-Type: application/octet-stream''); header(''Content-disposition: attachment; filename="file.zip"''); // use popen to execute a unix command pipeline // and grab the stdout as a php stream // (you can use proc_open instead if you need to // control the input of the pipeline too) // $fp = popen(''zip -r - file1 file2 file3'', ''r''); // pick a bufsize that makes you happy (8192 has been suggested). $bufsize = 8192; $buff = ''''; while( !feof($fp) ) { $buff = fread($fp, $bufsize); echo $buff; } pclose($fp);

Actualización : (2012-11-23) Descubrí que llamar a flush() dentro del ciclo de lectura / eco puede causar problemas cuando se trabaja con archivos muy grandes y / o redes muy lentas. Al menos, esto es cierto cuando se ejecuta PHP como cgi / fastcgi detrás de Apache, y parece probable que el mismo problema ocurra cuando se ejecuta en otras configuraciones también. El problema parece ser el resultado cuando PHP envía la salida a Apache más rápido de lo que Apache realmente puede enviarlo a través del socket. Para archivos muy grandes (o conexiones lentas), esto eventualmente causa un rebasamiento del buffer de salida interno de Apache. Esto hace que Apache mate el proceso de PHP, lo que hace que la descarga se cuelgue, o termine prematuramente, con solo una transferencia parcial.

La solución es no llamar flush() en absoluto. He actualizado los ejemplos de código anteriores para reflejar esto, y coloqué una nota en el texto en la parte superior de la respuesta.