Tester si un url existe et répond avec un timeout

ViPHP
ViPHP | 1380 Messages

19 nov. 2005, 16:58

Contexte: Tester ou accéder à une page ou un fichier distant par http avec fopen ou file_get_contents.

Problème: Imaginons qu'un script doive récupérer le contenu d'un fichier distant. Un post récent, a attiré mon attention sur un réel problème: si, un domaine (host) existe et est à l'écoute sur son port, mais que le serveur web ne sert pas la page assez rapidement, ou si la page est très lente à charger, il n'existe pas de timeout sur les fonctions fopen ou file_get_contents. Le script reste en suspens tant qu'il n'a pas reçu complètement la page.

Un timeout permettrait de sortir proprement du script avec un message d'erreur approprié au lieu d'attendre la max_execution_time et une sortie brutale.

Test: faites une page bidon (sleep.php) et mettez-y une temporisation pour simuler un long temps de réponse:
<?php
$sleep = 4;
$debut = date ('h:i:s');
sleep ($sleep);
echo "<h2>Cette page est temporisée à $sleep secondes</h2>";
echo $debut.' Début de la page.<br />';
echo date ('h:i:s').' Fini !';
?>
On est prêt à tester maintenant. Je vois 3 solutions.
  1. Première solution : méthode Rambo . Solution la plus simple mais la moins propre: changer le max_execution_time avec un set_time_limit(). La punition sera la même: une sortie en férocité avec un message peu avenant du genre: Fatal error: Maximum execution time of 10 seconds exceeded in.... De plus, je ne suis pas certain que les flux comptent dans la comptabilisation du temps d'exécution. On oublie Rambo et on passe à autre chose.
  2. Deuxième solution:
    • ouvrir un flux avec fsockopen
    • envoyer un header vers la page
    • tester son temps de réponse avec le timeout (stream_set_timeout et stream_get_meta_data)
    • si dans les limites de temps, ouvrir le document (file_get_contents).
    function monFileGetContents($adresse, $montrerContenu, $timeout = 10){
      $url = parse_url($adresse);
      $url['port'] = isset($url['port']) ? $url['port'] : '80';
      $fp = fsockopen($url['host'], $url['port'], $errno, $errstr, $timeout);
      if (!$fp) {
        echo 'Impossible de se connecter au domaine: <b>'.$url['host'].'</b>';
      } else {
        $header  = 'GET '.$url['path'].' '.$url['scheme']."/1.1\r\n";
        $header .= 'Host: '.$url['host']."\r\n";
        $header .= "Connection: Close\r\n\r\n";
        fputs($fp, $header);
        stream_set_timeout($fp, $timeout);
        fgets($fp, 128);
        $info = stream_get_meta_data($fp);
        fclose($fp);    
        if ($info['timed_out']) {
          echo "Le délai de réponse de la page <b>".$url['host'].$url['path'].
          '</b> a dépassé le timeout de <b>'.$timeout.'</b> sec.';
          return false;
        }elseif ($montrerContenu){
          return file_get_contents($adresse);
        }
        return true;
      }
    }
    
    // paramètres du test:
    // mettre $montrerContenu à false si on veut simplement tester le temps de réponse
    $url            = 'http://localhost/test/sleep.php';
    $montrerContenu = true;
    $timeout        = 1;
    
    // appel à la fonction
    echo  monFileGetContents($url, $montrerContenu , $timeout);
    
    
    Mettez $timeout à 10 secondes et essayez à nouveau.

    C’est mieux que la méthode Rambo plus haut. On peut au moins générer une erreur que l’on peut gérer à sa guise. Mais c’est lent en raison du fait qu’il faut d’abord tester le temps de réponse de la page (c’est le rôle du fgets) et ensuite la charger si nécessaire (file_get_contents plus loin). Si une page est lente, elle sera sollicitée deux fois. Mais, au moins, on ne reste plus planté en plein milieu du script. On est protégé par un timeout.
  3. Et enfin, le fin du fin. La chérie sur le gâteau! Si vous avez la chance de disposer de la superbe librairie CURL, il y a moyen de tout faire en une seule passe (tester le temps de réponse et ouverture du document) :
    function monFileGetContentsCurl($url, $montrerContenu, $timeout){
      $ch = curl_init($url);
      curl_setopt($ch, CURLOPT_HEADER, 0);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
      curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
      $resultat = curl_exec($ch);
      $CurlErr = curl_error($ch);
      curl_close($ch);
      if ($CurlErr) {
        echo $CurlErr;
        return false;
      }elseif ($montrerContenu){
        return $resultat;
      }
    }
    
    // paramètres du test:
    // mettre $montrerContenu à false si on veut simplement tester le temps de réponse
    $url            = 'http://localhost/test/sleep.php';
    $montrerContenu = true;
    $timeout        = 1;
    
    // appel à la fonction
    echo  monFileGetContentsCurl($url, $montrerContenu , $timeout);
    
Voilà, cette dernière fonction est deux fois plus rapide que celle avec fsockopen si on doit récupérer le contenu de la page cible.

S'il s'agit de simplement tester si la page existe et répond, les deux fonctions sont aussi rapides l'une que l'autre.

Dans tous les cas, un simple fopen n'est pas plus rapide et n'a pas de timeout!

Si vous n'avez pas la librairie CURL installez-la! :wink:
ripat