2011-06-01 783 views
8

Estoy intentando servir archivos zip grandes a los usuarios. Cuando hay 2 conexiones concurrentes, el servidor se queda sin memoria (RAM). Aumenté la cantidad de memoria de 300MB a 4GB (Dreamhost VPS) y luego funcionó bien.Cómo forzar la descarga de archivos grandes sin utilizar demasiada memoria?

Necesito permitir mucho más de 2 conexiones simultáneas. Los 4GB reales permitirían algo así como 20 conexiones simultáneas (muy mal).

Bueno, el código actual que estoy usando, necesita el doble de memoria que el tamaño real del archivo. Eso es muy malo. Quiero algo como "transmitir" el archivo al usuario. Por lo tanto, no asignaría más que el fragmento que se está sirviendo a los usuarios.

El siguiente código es el que estoy usando en CodeIgniter (framework PHP):

ini_set('memory_limit', '300M'); // it was the maximum amount of memory from my server 
set_time_limit(0); // to avoid the connection being terminated by the server when serving bad connection downloads 
force_download("download.zip", file_get_contents("../downloads/big_file_80M.zip"));exit; 

La función force_download es el siguiente (CodeIgniter función auxiliar por defecto):

function force_download($filename = '', $data = '') 
{ 
    if ($filename == '' OR $data == '') 
    { 
     return FALSE; 
    } 

    // Try to determine if the filename includes a file extension. 
    // We need it in order to set the MIME type 
    if (FALSE === strpos($filename, '.')) 
    { 
     return FALSE; 
    } 

    // Grab the file extension 
    $x = explode('.', $filename); 
    $extension = end($x); 

    // Load the mime types 
    @include(APPPATH.'config/mimes'.EXT); 

    // Set a default mime if we can't find it 
    if (! isset($mimes[$extension])) 
    { 
     $mime = 'application/octet-stream'; 
    } 
    else 
    { 
     $mime = (is_array($mimes[$extension])) ? $mimes[$extension][0] : $mimes[$extension]; 
    } 

    // Generate the server headers 
    if (strpos($_SERVER['HTTP_USER_AGENT'], "MSIE") !== FALSE) 
    { 
     header('Content-Type: "'.$mime.'"'); 
     header('Content-Disposition: attachment; filename="'.$filename.'"'); 
     header('Expires: 0'); 
     header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); 
     header("Content-Transfer-Encoding: binary"); 
     header('Pragma: public'); 
     header("Content-Length: ".strlen($data)); 
    } 
    else 
    { 
     header('Content-Type: "'.$mime.'"'); 
     header('Content-Disposition: attachment; filename="'.$filename.'"'); 
     header("Content-Transfer-Encoding: binary"); 
     header('Expires: 0'); 
     header('Pragma: no-cache'); 
     header("Content-Length: ".strlen($data)); 
    } 

    exit($data); 
} 

Probé algunos códigos basados ​​en fragmentos que encontré en Google, pero el archivo siempre se entregó dañado. Probablemente debido al mal código.

¿Alguien podría ayudarme?

+1

¿Ha tratado de redirigir a un archivo usando cabecera 'Location'? – Dani

+0

Me parece que está mucho mejor si solo le da a los usuarios un enlace directo a los archivos ... – NotMe

+0

Olvidé decir que los archivos están en una carpeta a la que no se puede acceder a través de la web. Esto es por razones de seguridad. Solo sirvo el archivo si el usuario pasa un proceso de autenticación. Probaré las sugerencias a continuación y volveré a votar para obtener la mejor respuesta. –

Respuesta

3

Existen algunas ideas en this thread. No sé si el método readfile() ahorrará memoria, pero parece prometedor.

+0

sí, 'readfile' ahorra memoria, porque cada fragmento del archivo que lee se envía directamente al navegador sin guardarlo en una variable (y así sin usar memoria extra, solo lo necesario para el fragmento de archivo). –

+1

Eso funcionó bien.Acabo de agregar algunos encabezados adicionales, por lo que el iPhone no alerta un error con la descarga. 'header ('Content-Disposition: attachment; filename =" download.zip "'); encabezado ('Caduca: 0'); encabezado ('Cache-Control: must-revalidate, post-check = 0, pre-check = 0'); encabezado ("Content-Transfer-Encoding: binary"); encabezado ('Pragma: public'); header ("Content-Length:" .filesize ($ filename)); readfile ($ filename); exit; ' –

+0

¡Me alegro de que funcionó! –

0

No puede usar $ datos con todos los datos de archivos. Pruebe pasar a esta función, no al contenido del archivo, solo su ruta. Luego envíe todos los encabezados una vez y después de eso lea parte de este archivo usando fread(), repita ese fragmento, llame a flush() y repita. Si se enviará otro encabezado mientras tanto, finalmente la transferencia se dañará.

+0

Lo que usted propone es lo que 'readfile' hace –

+0

' readfile' lee todo el archivo a la vez, en mi proposición usé 'fread' (también se puede usar' fgets') porque si el archivo se leerá en i'e 1MB trozos entonces esa memoria se puede liberar cuando el siguiente trozo se asignará a la misma variable. –

+1

lo siento pero no, 'readfile' lee el archivo en 8K fragmentos. De hecho, he buscado código PHP porque me intrigó y lo confirmé. [readfile source, línea 1383] (http://svn.php.net/viewvc/php/php-src/trunk/ext/standard/file.c?revision=311543&view=markup), 'php_stream_passthru' allí está DEFINIDO a '_php_stream_passthru' [(fuente, línea 453)] (http://svn.php.net/viewvc/php/php-src/trunk/main/php_streams.h?revision=311422&view=markup) y esa función posterior es en [este archivo, línea 1314] (http://svn.php.net/viewvc/php/php-src/trunk/main/streams/streams.c?revision=311545&view=markup) –

0

Enlace simbólico del archivo grande a la raíz de su documento (suponiendo que no es un archivo único autorizado), luego deje que Apache lo maneje. (De esta forma también puede aceptar rangos de bytes)

+0

Supone que Apache tiene habilitado FollowSymLinks y que el OP no está utilizando el script de descarga de PHP para agregar una capa de seguridad, pero de lo contrario es una buena idea. –

3

¿Está enviando los contenidos ($ data) de este archivo a través de PHP?

Si es así, cada proceso de Apache que maneja esto terminará creciendo al tamaño de este archivo, ya que esos datos se almacenarán en caché.

Su ÚNICA solución es no enviar contenido de archivos/datos a través de PHP y simplemente redirigir al usuario a una URL de descarga en el sistema de archivos.

Utilice un enlace simbólico generado y único, o una ubicación oculta.

0

Haz tu ini_set antes SESSION_START();

Cuestiones relacionadas