Page 1 sur 1

Téléchargement, problème durée d'exécution

Posté : 19 nov. 2009, 17:56
par lord.anonymous
Bonjour à tous;

Je viens vers vous pour un problème qui est assez prenant, et voir si quelqu'un parmi vous a une idée à me faire explorer. J'ai bien entendu utilisé la fonction "recherche", mais soit je suis manchot, soit cette fonction recherche est assez ardue à mettre en oeuvre...

Mon script est hébergé chez Free, c'est en Safe Mode, et la durée d'exécution du script ne peut dépasser 30s, limitation apparemment bien connue.
Je propose des fichiers de musique en téléchargement pour mon groupe. J'ai donc des fichiers parfois assez volumineux, et le temps que le transfert se fasse, le script arrête son exécution.

Voici la partie du script qui m'intéresse:
$p = '';
if (isset($_GET['p'])){
  $p = $_GET['p'];
}

if ($p) {
  // On n'autorise pas les chemins '..'
  if (preg_match('/\.\./', $p)) {
    Header ('Location: ./');
    exit();
  }

  $sql="SELECT * FROM `download` WHERE `id` =".$p;
  $result=mysql_query($sql) or die ('Problème avec la requête SQL');

  while($row=mysql_fetch_array($result)){
    $lien=$row['lien'];
	  $total=$row['total']+1;
  }

  // Code pour mettre a jour les stats de telechargements 
  $sql="UPDATE `download` SET `total` = '$total' WHERE `id` = '$p'";
  $result=mysql_query($sql) or die ('Problème avec la requête SQL');  

  $path = $UploadDir . $lien;

  if (!is_file($path)){
    exit();
  }		

  @ob_end_clean();
  @ini_set('zlib.output_compression', 'Off');

  header('Pragma: public');
  header('Expires: 0');
  header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
  header('Cache-Control: public');
  header('Content-Type: application/force-download');
  header('Content-Type: application/octet-stream');
  header('Content-Type: application/download');
  header('Content-Disposition: attachment; filename="' . basename($path) . '";');
  header('Content-Transfer-Encoding: binary');
  header('Content-Length: ' . filesize($path));
  
  @readfile($path) or die("Fichier introuvable.");
  
  $handle = fopen($path, 'rb');

  do {
    $data = fread($handle, 8192);    
    if (strlen($data) == 0) {
      break;
    }
    echo($data);
  } while (true);

  fclose($handle);
  exit();
} 
else {
  Header ('Location: ./');
}
Ce script fonctionne sans problème pour de petits fichiers, donc des temps d'exécution inférieurs à 30s.

Pour permettre le téléchargement d'un gros fichier, je pense à 2 principes:

Création d'un dossier/fichier temporaire, avec un nom codé et sur lequel pointe un "simple" lien. Le dossier/fichier est créé par le script de download, avec une limitation temporelle associée en BDD, et nettoyage des dossiers/fichiers temporaires à l'aide d'une crontab. Ca ferait une manipulation de plus pour l'internaute (après avoir choisi le fichier à télécharger, il doit valider en cliquant sur un lien "temporaire") et cela peut multiplier les copies sur le serveur. Ca ne me plait qu'à moitié.

Relancer le script après 29s d'exécution par exemple. Là, je ne vois pas vraiment comment m'en sortir et reprendre le téléchargement en cours. Si quelqu'un a une idée à ce sujet, elle serait la bienvenue.

Merci d'avance à ceux qui voudront bien se pencher sur le problème. :P

Re: Téléchargement, problème durée d'exécution

Posté : 19 nov. 2009, 18:07
par Dr@ke
Cela te sert a quoi ça?
  $handle = fopen($path, 'rb');

  do {
    $data = fread($handle, 8192);    
    if (strlen($data) == 0) {
      break;
    }
    echo($data);
  } while (true);

  fclose($handle);
  exit();
Sinon:
  header('Content-Type: application/force-download');
  header('Content-Type: application/octet-stream');
  header('Content-Type: application/download');
En général soit l'un ou soit l'autre et dans ton cas, c'est le plus souvent juste le premier...

Et:
  header('Pragma: public');
  header('Expires: 0');
  header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
  header('Cache-Control: public');
C'est plutôt contradictoire, en général dans cette configuration, ce n'est pas public mais private...

Re: Téléchargement, problème durée d'exécution

Posté : 19 nov. 2009, 19:06
par lord.anonymous
Cela te sert a quoi ça?
  $handle = fopen($path, 'rb');

  do {
    $data = fread($handle, 8192);    
    if (strlen($data) == 0) {
      break;
    }
    echo($data);
  } while (true);

  fclose($handle);
  exit();
Ca sert à envoyer les données par bout de 8Ko en mémoire. Je ne sais pas à vrai dire si c'est très utile, readfile($path) fonctionne tout autant.
Sinon:
  header('Content-Type: application/force-download');
  header('Content-Type: application/octet-stream');
  header('Content-Type: application/download');
En général soit l'un ou soit l'autre et dans ton cas, c'est le plus souvent juste le premier...

Et:
  header('Pragma: public');
  header('Expires: 0');
  header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
  header('Cache-Control: public');
C'est plutôt contradictoire, en général dans cette configuration, ce n'est pas public mais private...
Alors pour les headers, j'ai copié/collé divers que j'ai trouvé (avant de me rendre compte que ce n'était pas ça qui posait problème). Ceci dit, les headers ne sont pas vraiment un truc que je maitrise. Par contre il est assez clair que ce n'est pas ça le problème de fond.

Re: Téléchargement, problème durée d'exécution

Posté : 19 nov. 2009, 19:09
par Dr@ke
Enlève la partie des 8 ko...
et met les headers comme je te disais, histoire que ce soit plus propre -> quitte a optimiser ton script, autant le faire jusqu'au bout...

je pense que c'est la partie que je te demandais a quoi cela te servait qui pose problème...

Une fois que tu auras tout fais ça -> je pense que tout sera ok :wink:

[EDIT]
Si jamais cela bug encore, repost ton code modifié avec les modifications conseillées et on regardera la reste de ton code.

Re: Téléchargement, problème durée d'exécution

Posté : 19 nov. 2009, 20:07
par lord.anonymous
Voilà le script corrigé:
$p = '';
if (isset($_GET['p'])){
  $p = $_GET['p'];
}

if ($p) {
  // On n'autorise pas les chemins '..'
  if (preg_match('/\.\./', $p)) {
    Header ('Location: ./');
    exit();
  }

  $sql="SELECT * FROM `download` WHERE `id` =".$p;
  $result=mysql_query($sql) or die ('Problème avec la requête SQL');

  while($row=mysql_fetch_array($result)){
    $lien=$row['lien'];
          $total=$row['total']+1;
  }

  // Code pour mettre a jour les stats de telechargements
  $sql="UPDATE `download` SET `total` = '$total' WHERE `id` = '$p'";
  $result=mysql_query($sql) or die ('Problème avec la requête SQL');  

  $path = $UploadDir . $lien;

  if (!is_file($path)){
    exit();
  }            

  @ob_end_clean();
  @ini_set('zlib.output_compression', 'Off');

  header('Expires: 0');
  header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
  header('Cache-Control: private');
  header('Content-Type: application/force-download');  
  header('Content-Disposition: attachment; filename="' . basename($path) . '";');
  header('Content-Transfer-Encoding: binary');
  header('Content-Length: ' . filesize($path));
	
  readfile($path);
}
else {
  Header ('Location: ./');
} 
Je viens de faire plusieurs essais de téléchargements pendant tout ce temps.
Apparemment, le téléchargement fonctionne "mieux": il arrive plus souvent que le fichier arrive "presque" entier, mais pas toujours... Je m'explique:
J'ai toute une série de fichiers MP3, donc légers (quelques Mo): ne posent pas de problèmes en téléchargement.
J'ai 1 fichier Wav, léger, qui ne pose aucun problème non plus.
J'ai toute une autre série de fichiers Wav lourds (plusieurs dizaines de Mo): ils arrivent, sont lisibles sauf à la fin où ça plante pour un problème de codecs (Windows Media Player), donc la fin du fichier serait corrompue.

Pour une archive Zip (65 Mo environ), ça arrive très souvent (mais de façon aléatoire) que le fichier qui arrive ne fasse que 10 ou 18 Mo. Parfois il arrive "en entier" sauf que l'archive est corrompue.

Finalement je ne sais plus très bien si c'est le temps d'exécution du script qui pose problème ou si ce sont les headers. A noter que j'avais déjà essayé hier (avant de poster ici) toute une série de headers, et l'utilisation de readfile($path) comme actuellement, et que ça foirait fréquemment.

Je suis un peu perdu du coup.

Re: Téléchargement, problème durée d'exécution

Posté : 19 nov. 2009, 21:13
par Dr@ke
Essaye ça:
// si $_GET['p'] est définit $p = $_GET['p'] sinon $p = '' (donc vide)
$p = (isset($_GET['p'])) ? trim($_GET['p']) : '';

// si $p n'est pas vide
if (!empty($p)) {
  // On n'autorise pas les chemins '..'
  if (preg_match('/\.\./', $p)) {
    header ('Location: ./');
    exit();
  }

// protection de la requête avec mysql_real_escape_string et des simples quotes
  $sql = "SELECT * FROM `download` WHERE `id` = '" . mysql_real_escape_string($p) ."'";
  $result=mysql_query($sql) or die ('Problème avec la requête SQL');

  while($row=mysql_fetch_array($result)){
    $lien=$row['lien'];
          $total=$row['total']+1;
  }

  // Code pour mettre a jour les stats de telechargements
 // protection de la requête avec mysql_real_escape_string et des simples quotes
  $sql="UPDATE `download` SET `total` = '$total' WHERE `id` = '" . mysql_real_escape_string($p) ."'";
  $result=mysql_query($sql) or die ('Problème avec la requête SQL');  

  $path = $UploadDir . $lien;

  if (!is_file($path)){
    exit();
  }            

  @ob_end_clean();
  @ini_set('zlib.output_compression', 'Off');

  header('Expires: 0');
 // Ajout du header Pragma: no-cache 
  header("Pragma: no-cache");
  header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
  header('Cache-Control: private');
  header('Content-Type: application/force-download');  
  header("Content-Disposition: attachment; filename=\"".basename($file)."\";");
 // ajout du header (transfert en mode binaire)
  header('Content-Transfer-Encoding: binary');
  header('Content-Length: ' . filesize($path));
       
  readfile($path);
}
else {
// si $p est vide -> on redirige
  header ('Location: ./');
}

Ensuite test aussi en enlevant (cela dépend du reste):
( et en enlevant l'un ou l'autre)
  @ob_end_clean();
  @ini_set('zlib.output_compression', 'Off');

Re: Téléchargement, problème durée d'exécution

Posté : 20 nov. 2009, 17:21
par Dr@ke
En fait:

Mieux de remplacer:
@ini_set('zlib.output_compression', 'Off');
Par:
if(ini_get('zlib.output_compression')) ini_set('zlib.output_compression', 'Off'); 
Remplacer:
header('Content-Type: application/force-download'); 
Par:
header("Content-Type: application/octet-stream");
Enlever:
header('Cache-Control: private');
et ajouter juste avant readfile():
ob_clean();
flush();
Donc:
// si $_GET['p'] est définit $p = $_GET['p'] sinon $p = '' (donc vide)
$p = (isset($_GET['p'])) ? trim($_GET['p']) : '';

// si $p n'est pas vide
if (!empty($p)) {
  // On n'autorise pas les chemins '..'
  if (preg_match('/\.\./', $p)) {
    header ('Location: ./');
    exit();
  }

// protection de la requête avec mysql_real_escape_string et des simples quotes
  $sql = "SELECT * FROM `download` WHERE `id` = '" . mysql_real_escape_string($p) ."'";
  $result=mysql_query($sql) or die ('Problème avec la requête SQL');

  while($row=mysql_fetch_array($result)){
    $lien=$row['lien'];
          $total=$row['total']+1;
  }

  // Code pour mettre a jour les stats de telechargements
 // protection de la requête avec mysql_real_escape_string et des simples quotes
  $sql="UPDATE `download` SET `total` = '$total' WHERE `id` = '" . mysql_real_escape_string($p) ."'";
  $result=mysql_query($sql) or die ('Problème avec la requête SQL');  

  $path = $UploadDir . $lien;

  if (!is_file($path)){
    exit();
  }            

  // a tester si il est nécessaire ici
  @ob_end_clean();

  // a tester si nécessaire avec tes scripts
  if(ini_get('zlib.output_compression')) ini_set('zlib.output_compression', 'Off'); 

  header('Expires: 0');
 // Ajout du header Pragma: no-cache
  header("Pragma: no-cache");
  header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
  // La meilleur méthode pour forcer le téléchargement
  header("Content-Type: application/octet-stream"); 
  header("Content-Disposition: attachment; filename=\"".basename($file)."\";");
 // ajout du header (transfert en mode binaire)
  header('Content-Transfer-Encoding: binary');
  header('Content-Length: ' . filesize($path));

 //Ajout de ob_clean() et flush()
  ob_clean();
  flush();

  readfile($path);
  exit;
}
else {
// si $p est vide -> on redirige
  header ('Location: ./');
}
Au pire du pire, tu peux ajouter juste avant readfile():
( Mais pas conseillé à mon avis)
set_time_limit(0); 
Voila, je pense que l'on a fait le tour :wink:

Re: Téléchargement, problème durée d'exécution

Posté : 21 nov. 2009, 19:01
par lord.anonymous
Bonjour,
Désolé du silence, je suis assez occupé.
Merci à toi Dr@ke de te pencher sur mon problème.

J'ai effectué les modifications nécessaires comme tu le préconises.
Malheureusement, le problème reste exactement le même. J'ai fait beaucoup d'essais de téléchargement, et il apparait ceci:

Parfois, de façon aléatoire, le fichier (quelqu'il soit: zip, mp3 ou wav) arrive "presque" entier: le plus souvent dans ce cas, il manque 4ko de données. Le zip est donc illisible, le mp3 se lit apparemment sans bugger et le wav se lit sauf à la fin où un message d'erreur du player arrive.

D'autres fois, ce qui est le plus fréquent, le téléchargement fait comme si c'était terminé, et le fichier est totalement incomplet. Il en arrive entre 3Mo et 19Mo, quelque soit le type de fichier.

C'est un drôle de problème! :?

J'ajoute que je n'ai pas remis le set_time_limit(), j'avais déjà expérimenté, sans aucun effet. C'est en safe mode chez Free.

Re: Téléchargement, problème durée d'exécution

Posté : 21 nov. 2009, 19:10
par Dr@ke
Ok je ne pense pas que cela vienne des headers, car tu as quasiment la configuration donnée dans l'exemple de php.net:
<?php
$file = 'monkey.gif';

if (file_exists($file)) {
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename='.basename($file));
    header('Content-Transfer-Encoding: binary');
    header('Expires: 0');
    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    header('Pragma: public');
    header('Content-Length: ' . filesize($file));
    ob_clean();
    flush();
    readfile($file);
    exit;
}
?>
http://php.net/manual/fr/function.readfile.php

Donc il faut regarder ailleurs dans le script ou dans la configuration de ton serveur...

Sinon, peux tu tester l'alternative a readfile(), en remplaçant:
readfile($path);
Par:
header("Location:".$path);

[EDIT]
Sinon dans le script que j'avais posté juste avant faut remplacer:
header("Content-Disposition: attachment; filename=\"".basename($file)."\";");
par:
header("Content-Disposition: attachment; filename=\"".basename($path)."\";");
[EDIT2]
Et je ne sais pas si tu as testé en enlevant chaque lignes ce-dessous et ensuite les 2:
  @ob_end_clean();
  if(ini_get('zlib.output_compression')) ini_set('zlib.output_compression', 'Off'); 

Re: Téléchargement, problème durée d'exécution

Posté : 22 nov. 2009, 23:40
par lord.anonymous
Bonsoir,

Alors pour le header() au lieu de readfile(), ça marche bien, l'archive arrive entière et non corrompue, mais curieusement c'est lent. Je retente demain pour savoir si ce n'est pas simplement le débit qui est bas ce soir. Par contre un gros inconvénient, comme j'ai du MP3 et Wav à faire télécharger, eh bien ça ne télécharge pas, ça fait directement lire dans le navigateur. Comment faire?

Ensuite pour ton premier Edit, j'avais vu le bug rapidement, curieusement, ça faisait télécharger mon fichier download.php! Ouch!!! Mais heureusement, aucune info de connexion n'apparait en clair...

Pour les 2 lignes avec ob_end_flush() et ini_set('zlib.output_compression', 'Off'), je revois ça demain et je te tiens au courant.

Merci!

Re: Téléchargement, problème durée d'exécution

Posté : 23 nov. 2009, 02:05
par Dr@ke
curieusement, ça faisait télécharger mon fichier download.php! Ouch!!!
Je te rassure, ça ne faisait pas télécharger le fichier download.php.
-> cela téléchargeait le bon fichier sauf que cela lui donnait le nom de download.php...
ça fait directement lire dans le navigateur. Comment faire?
Avec header location, il faut les zipper.

Re: Téléchargement, problème durée d'exécution

Posté : 23 nov. 2009, 21:28
par FuZZyLine
Saulut,
Bonsoir,
Alors pour le header() au lieu de readfile(), ça marche bien, l'archive arrive entière et non corrompue, mais curieusement c'est lent. Je retente demain pour savoir si ce n'est pas simplement le débit qui est bas ce soir. Par contre un gros inconvénient, comme j'ai du MP3 et Wav à faire télécharger, eh bien ça ne télécharge pas, ça fait directement lire dans le navigateur. Comment faire?
Ensuite pour ton premier Edit, j'avais vu le bug rapidement, curieusement, ça faisait télécharger mon fichier download.php! Ouch!!! Mais heureusement, aucune info de connexion n'apparait en clair...
Pour les 2 lignes avec ob_end_flush() et ini_set('zlib.output_compression', 'Off'), je revois ça demain et je te tiens au courant.
Merci!
Et un 'tit http://fr2.php.net/manual/fr/function.s ... -limit.php arrangerait pas les choses?

S'entend, tu auras toujours un problème de durée je pense. Un facteur auquel tu n'as pas pensé c'est
le facteur "user". La connexion sera toujours sujette à caution. 'fin, ce que j'en dis.

...En espérant que j'ai soulevé un lièvre, @+ ;)

Re: Téléchargement, problème durée d'exécution

Posté : 23 nov. 2009, 21:31
par Dr@ke
Et un 'tit http://fr2.php.net/manual/fr/function.s ... -limit.php arrangerait pas les choses?
...En espérant que j'ai soulevé un lièvre, @+ ;)
Oui on a déjà essayé ça...
Cf:
Au pire du pire, tu peux ajouter juste avant readfile():
( Mais pas conseillé à mon avis)
set_time_limit(0); 
Voila, je pense que l'on a fait le tour :wink:
Mais bonne idée :wink:

[EDIT]
Au pire, si tu avais oublié de tester lord, test en mettant, par exemple, 50 et non 0 ( qui n'est vraiment vraiment pas conseillé)
set_time_limit(50); 
(et toujours juste avant readfile()) :!:

Sinon pour info:
Notez que set_time_limit() n'a pas d'effet lorsque PHP fonctionne en mode safe mode. Il n'y a pas d'autre solution que de changer de mode, ou de modifier la durée maximale d'exécution dans le php.ini.

Re: Téléchargement, problème durée d'exécution

Posté : 26 nov. 2009, 23:25
par lord.anonymous
Désolé du manque de réactivité, je n'ai toujours pas eu le temps de tester les dernières solutions! Mais je n'oublie pas.
Dr@ke: j'avais déjà essayé un set_time_limit(300) sans aucun succès. Safe mode chez Free, donc ça ne sert à rien.

Je repasse donner des nouvelles dès que j'aurai fait d'autres essais!