web-dev-qa-db-fra.com

Télécharger des fichiers volumineux de manière fiable dans PHP

J'ai un script php sur un serveur pour envoyer des fichiers à des destinataires: ils obtiennent un lien unique et peuvent ensuite télécharger des fichiers volumineux. Parfois, il y a un problème avec le transfert et le fichier est corrompu ou ne se termine jamais. Je me demande s'il existe un meilleur moyen d'envoyer des fichiers volumineux

Code:

$f = fopen(DOWNLOAD_DIR.$database[$_REQUEST['fid']]['filePath'], 'r');
while(!feof($f)){
    print fgets($f, 1024);
}
fclose($f);

J'ai vu des fonctions telles que

http_send_file
http_send_data

Mais je ne suis pas sûr qu'ils fonctionneront.

Quel est le meilleur moyen de résoudre ce problème?

Cordialement
erwing

24
Erwing

Si vous envoyez des fichiers vraiment volumineux et que vous vous inquiétez de l'impact que cela aura, vous pouvez utiliser l'en-tête x-sendfile.

Depuis le SOQ using-xsendfile-with-Apache-php , un howto blog.adaniels.nl: how-i-php-x-sendfile/

11
garrow

La meilleure solution serait de faire appel à lighty ou Apache, mais si en PHP, je voudrais utiliser HTTP_Download de PEAR (inutile de réinventer la roue, etc.), présente quelques fonctionnalités intéressantes, telles que:

  • Mécanisme de régulation de base
  • Gammes (téléchargements partiels et reprise)

Voir intro/docs d'utilisation

8
PrettyCoder

Le découpage de fichiers est la méthode la plus simple et la plus rapide en PHP, si vous ne pouvez pas ou ne voulez pas utiliser quelque chose d'un peu plus professionnel comme cURL , mod-xsendfile on Apache ou d'autres script dédié .

$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!';

Porté à partir de richnetapps.com / NeedBee . Testé sur des fichiers de 200 Mo sur lesquels readfile() est mort, même si la limite de mémoire maximale autorisée est définie sur 1G, soit cinq fois plus que la taille du fichier téléchargé.

BTW: J'ai aussi testé cela sur les fichiers >2GB, mais PHP n'a réussi à écrire que le premier 2GB du fichier, puis à rompre la connexion. Les fonctions liées aux fichiers (fopen, fread, fseek) utilisent INT, vous atteignez donc la limite de 2GB. Les solutions susmentionnées (à savoir mod-xsendfile) semblent être la seule option dans ce cas.

EDIT: Assurez-vous 100% que votre fichier soit enregistré dans utf-8. Si vous omettez cela, les fichiers téléchargés seront corrompus. Ceci, car cette solution utilise print Transférer la partie d’un fichier dans un navigateur. _

7
trejder

Créez un lien symbolique vers le fichier réel et faites pointer le lien de téléchargement vers le lien symbolique. Ensuite, lorsque l'utilisateur clique sur le lien DL, il se télécharge à partir du fichier réel mais est nommé à partir du lien symbolique. Il faut plusieurs millisecondes pour créer le lien symbolique et il vaut mieux essayer de copier le fichier sous un nouveau nom et de le télécharger à partir de là.

Par exemple:

<?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>

C’est ce que j’ai fait parce que Go Daddy empêche le script de s’exécuter après 2 minutes 30 secondes environ…, ce qui évite ce problème et masque le fichier.

Vous pouvez ensuite configurer un travail CRON pour supprimer les liens symboliques à intervalles réguliers ....

L'ensemble de ce processus enverra ensuite le fichier au navigateur, peu importe la durée de son exécution car il ne s'agit pas d'un script.

3
Hummdis

Pour télécharger des fichiers, le moyen le plus simple auquel je puisse penser est de le placer dans un emplacement temporaire et de leur attribuer une URL unique qu’ils peuvent télécharger via HTTP.

Dans le cadre de la création de ces liens, vous pouvez également supprimer les fichiers de plus de X heures.

3
Andrew Grant

Nous l'utilisons dans quelques projets et cela fonctionne assez bien jusqu'à présent:

/**
 * 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);
    }
}
3
Andreas Baumgart

Si vous utilisez lighttpd en tant que serveur Web, une alternative aux téléchargements sécurisés serait d'utiliser ModSecDownload . La configuration du serveur est nécessaire, mais vous laisserez le serveur Web gérer le téléchargement lui-même au lieu du script PHP.

Générer l'URL de téléchargement ressemblerait à cela (tiré de la documentation) et pourrait bien sûr être généré uniquement pour les utilisateurs autorisés:

<?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);
?>

Bien entendu, en fonction de la taille des fichiers, l’utilisation de readfile() telle que proposée par Unkwntech est excellente. Et utiliser xsendfile tel que proposé par garrow est une autre bonne idée également supportée par Apache.

1
lpfavreau
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();
1
ahmed

Quand j'ai fait cela dans le passé, j'ai utilisé ceci:

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.

Ces 3 lignes de code feront tout le travail de téléchargement readfile () diffusera tout le fichier spécifié sur le client, et assurez-vous de définir une limite de temps infinie, sinon vous risquez de manquer de temps avant le fichier. est terminé le streaming.

1
UnkwnTech

Je ne suis pas sûr que ce soit une bonne idée pour les gros fichiers. Si le fil de votre script de téléchargement s'exécute jusqu'à ce que l'utilisateur ait terminé le téléchargement et si vous utilisez Apache, au moins 50 téléchargements simultanés risquent de bloquer votre serveur, car Apache n'est pas conçu pour exécuter un grand nombre de tâches de longue durée. fils en même temps. Bien sûr, je peux me tromper si le thread Apache se termine d'une manière ou d'une autre et que le téléchargement est stocké dans une mémoire tampon quelque part pendant le téléchargement.

0
user257124

Ceci est testé sur des fichiers d’une taille supérieure à 200 Mo sur un serveur disposant d’une limite de mémoire de 256 Mo.

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();
}
0
Alex

J'ai eu le même problème, Mon problème a été résolu en ajoutant ceci avant de démarrer la session Session_cache_limiter ('none');

0
Nawras

J'ai utilisé l'extrait suivant trouvé dans les commentaires de l'entrée du manuel php pour 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;
}
0
periklis