para - Descargando archivos de gran tamaño de manera confiable en PHP
php web oficial (13)
Chunking de archivos es el método más rápido / simple en PHP, si no puedes o no quieres usar algo un poco más profesional como cURL , mod-xsendfile
en Apache o algún script dedicado .
$filename = $filePath.$filename;
$chunksize = 5 * (1024 * 1024); //5 MB (= 5 242 880 bytes) per one chunk of file.
if(file_exists($filename))
{
set_time_limit(300);
$size = intval(sprintf("%u", filesize($filename)));
header(''Content-Type: application/octet-stream'');
header(''Content-Transfer-Encoding: binary'');
header(''Content-Length: ''.$size);
header(''Content-Disposition: attachment;filename="''.basename($filename).''"'');
if($size > $chunksize)
{
$handle = fopen($filename, ''rb'');
while (!feof($handle))
{
print(@fread($handle, $chunksize));
ob_flush();
flush();
}
fclose($handle);
}
else readfile($path);
exit;
}
else echo ''File "''.$filename.''" does not exist!'';
richnetapps.com desde richnetapps.com / NeedBee . Probado en archivos de 200 MB, en los que readfile()
murió, incluso con el límite de memoria máximo permitido establecido en 1G
, es cinco veces más que el tamaño del archivo descargado.
Por cierto: Probé esto también en archivos >2GB
, pero PHP solo logró escribir los primeros 2GB
de archivo y luego rompió la conexión. Las funciones relacionadas con archivos (fopen, fread, fseek) usan INT, por lo que finalmente alcanzas el límite de 2GB
. Las soluciones mencionadas anteriormente (es decir, mod-xsendfile
) parecen ser la única opción en este caso.
EDIT : hazte al 100% de que tu archivo esté guardado en utf-8
. Si omite eso, los archivos descargados se dañarán. Esto es, porque esta solución utiliza la print
para enviar un fragmento de un archivo a un navegador.
Tengo un script php en un servidor para enviar archivos a recipents: obtienen un enlace único y luego pueden descargar archivos de gran tamaño. A veces hay un problema con la transferencia y el archivo está dañado o nunca termina. Me pregunto si hay una mejor manera de enviar archivos grandes
Código:
$f = fopen(DOWNLOAD_DIR.$database[$_REQUEST[''fid'']][''filePath''], ''r'');
while(!feof($f)){
print fgets($f, 1024);
}
fclose($f);
He visto funciones como
http_send_file
http_send_data
Pero no estoy seguro de si funcionarán.
¿Cuál es la mejor manera de resolver este problema?
Saludos
erwing
Cree un enlace simbólico al archivo real y haga que el enlace de descarga apunte al enlace simbólico. Luego, cuando el usuario haga clic en el enlace DL, obtendrá una descarga de archivo del archivo real, pero se llamará desde el enlace simbólico. Se necesitan milisegundos para crear el enlace simbólico y es mejor que tratar de copiar el archivo a un nuevo nombre y descargar desde allí.
Por ejemplo:
<?php
// validation code here
$realFile = "Hidden_Zip_File.zip";
$id = "UserID1234";
if ($_COOKIE[''authvalid''] == "true") {
$newFile = sprintf("myzipfile_%s.zip", $id); //creates: myzipfile_UserID1234.zip
system(sprintf(''ln -s %s %s'', $realFile, $newFile), $retval);
if ($retval != 0) {
die("Error getting download file.");
}
$dlLink = "/downloads/hiddenfiles/".$newFile;
}
// rest of code
?>
<a href="<?php echo $dlLink; ?>Download File</a>
Eso es lo que hice porque Go Daddy elimina la ejecución de la secuencia de comandos después de 2 minutos y 30 segundos más o menos ... esto evita ese problema y oculta el archivo real.
A continuación, puede configurar un trabajo CRON para eliminar los enlaces simbólicos a intervalos regulares ....
Todo este proceso enviará el archivo al navegador y no importa cuánto tiempo se ejecute, ya que no es un script.
Cuando hice esto en el pasado lo he usado:
set_time_limit(0); //Set the execution time to infinite.
header(''Content-Type: application/exe''); //This was for a LARGE exe (680MB) so the content type was application/exe
readfile($fileName); //readfile will stream the file.
Estas 3 líneas de código harán todo el trabajo del readfile() descarga readfile() transmitirá el archivo completo especificado al cliente, y asegúrese de establecer un límite de tiempo infinito, de lo contrario, puede que se esté quedando sin tiempo antes de que el archivo termine la transmisión.
Esto se prueba en archivos de un tamaño de más de 200 MB en un servidor que tiene un límite de memoria de 256 MB.
header(''Content-Type: application/zip'');
header("Content-Disposition: attachment; filename=/"$file_name/"");
set_time_limit(0);
$file = @fopen($filePath, "rb");
while(!feof($file)) {
print(@fread($file, 1024*8));
ob_flush();
flush();
}
He tenido el mismo problema, mi problema fue resuelto agregando esto antes de comenzar la sesión session_cache_limiter (''none'');
He utilizado el siguiente fragmento encontrado en los comentarios de la entrada manual de php para readfile:
function _readfileChunked($filename, $retbytes=true) {
$chunksize = 1*(1024*1024); // how many bytes per chunk
$buffer = '''';
$cnt =0;
// $handle = fopen($filename, ''rb'');
$handle = fopen($filename, ''rb'');
if ($handle === false) {
return false;
}
while (!feof($handle)) {
$buffer = fread($handle, $chunksize);
echo $buffer;
ob_flush();
flush();
if ($retbytes) {
$cnt += strlen($buffer);
}
}
$status = fclose($handle);
if ($retbytes && $status) {
return $cnt; // return num. bytes delivered like readfile() does.
}
return $status;
}
Hemos estado usando esto en un par de proyectos y hasta ahora funciona bastante bien:
/**
* Copy a file''s content to php://output.
*
* @param string $filename
* @return void
*/
protected function _output($filename)
{
$filesize = filesize($filename);
$chunksize = 4096;
if($filesize > $chunksize)
{
$srcStream = fopen($filename, ''rb'');
$dstStream = fopen(''php://output'', ''wb'');
$offset = 0;
while(!feof($srcStream)) {
$offset += stream_copy_to_stream($srcStream, $dstStream, $chunksize, $offset);
}
fclose($dstStream);
fclose($srcStream);
}
else
{
// stream_copy_to_stream behaves() strange when filesize > chunksize.
// Seems to never hit the EOF.
// On the other handside file_get_contents() is not scalable.
// Therefore we only use file_get_contents() on small files.
echo file_get_contents($filename);
}
}
La mejor solución sería confiar en light o apache, pero si estuviera en PHP, usaría HTTP_Download de PEAR (no es necesario reinventar la rueda, etc.), tiene algunas características agradables, como:
- Mecanismo de aceleración básico
- Rangos (descargas parciales y reanudación)
No estoy seguro de que esta sea una buena idea para archivos grandes. Si el hilo de su script de descarga se ejecuta hasta que el usuario haya finalizado la descarga y esté ejecutando algo parecido a Apache, solo 50 o más descargas simultáneas podrían bloquear su servidor, porque Apache no está diseñado para ejecutar grandes cantidades de ejecuciones prolongadas. hilos al mismo tiempo. Por supuesto que podría estar equivocado, si el hilo apache de alguna manera termina y la descarga se encuentra en un buffer en algún lugar mientras la descarga progresa.
Para descargar archivos, lo más fácil que se me ocurre es colocar el archivo en una ubicación temporal y darles una URL única que puedan descargar a través de HTTP normal.
Como parte de la generación de estos enlaces, también podría eliminar archivos que tenían más de X horas de antigüedad.
Si está enviando archivos realmente grandes y está preocupado por el impacto que esto tendrá, puede usar el encabezado x-sendfile.
Del SOQ using-xsendfile-with-apache-php , un howto blog.adaniels.nl: how-i-php-x-sendfile /
Si está utilizando lighttpd como un servidor web, una alternativa para descargas seguras sería usar ModSecDownload . Necesita la configuración del servidor pero permitirá que el servidor web maneje la descarga en lugar del script PHP.
La generación de la URL de descarga se vería así (tomada de la documentación) y, por supuesto, solo podría generarse para usuarios autorizados:
<?php
$secret = "verysecret";
$uri_prefix = "/dl/";
# filename
# please note file name starts with "/"
$f = "/secret-file.txt";
# current timestamp
$t = time();
$t_hex = sprintf("%08x", $t);
$m = md5($secret.$f.$t_hex);
# generate link
printf(''<a href="%s%s/%s%s">%s</a>'',
$uri_prefix, $m, $t_hex, $f, $f);
?>
Por supuesto, dependiendo del tamaño de los archivos, usar readfile()
como el propuesto por Unkwntech es excelente. Y usar xsendfile según lo propuesto por garrow es otra buena idea también soportada por Apache.
header("Content-length:".filesize($filename));
header(''Content-Type: application/zip''); // ZIP file
header(''Content-Type: application/octet-stream'');
header(''Content-Disposition: attachment; filename="downloadpackage.zip"'');
header(''Content-Transfer-Encoding: binary'');
ob_end_clean();
readfile($filename);
exit();