file_get_contents pour fichiers distants avec timeout

ViPHP
ViPHP | 1380 Messages

20 nov. 2005, 16:32

Suite à mon post plutôt généraliste sur le manque de timeout sur les fonctions de flux fopen(), readfile() et file_get_contents(), j'ai, sur mon élan, fait une petite fonction qui permet de capturer le contenu d'un fichier distant avec un timeout. J'ai aussi rajouté la possiblité de n'en capturer qu'une partie avec un offset et longueur paramétrables comme dans la version PHP 5 de file_get_contents.

La fonction
/*

UrlGetContentsCurl ( string url [, int timeout [, bool content [, int offset [, int maxlen]]]] )

Arguments:
  string url:   url avec son protocole. Par ex.
                    http://www.phpfrance.com/forums/index.php
                    http://www.rfc-editor.org/rfc/rfc2606.txt
                    ftp://ftp.rfc-editor.org/in-notes/rfc2606.txt
  int timeout:  la limite de temps.
                Optionnel. Défaut: valeur de max_execution_time.
  bool content: true si on veut récupérer le contenu de la page. False, la fonction renvoie
                le temps de réponse de la page.
                Optionnel. Défaut: true.
  int offset:   début de la capture de contenu, en octets.
                Optionnel. Défaut: 0 (début de la page)
  int maxlen:   nombre d'octets à capturer à partir de offset.
                Optionnel. Défaut: null (tout à partir de la position de l'offset)

Retourne:
  False  en cas d'échec de la connexion ou de l'ouverture de la page/fichier distant
  String le contenu du fichier/page si l'argument [content] est à true
  Float  le temps de réponse du host et du fichier/page (si [content] est à false)

*/


function UrlGetContentsCurl(){
  // traitement des arguments optionnels et des valeurs par défaut.
  $arg_names    = array('url', 'timeout', 'getContent', 'offset', 'maxLen');
  $arg_passed   = func_get_args();
  $arg_nb       = count($arg_passed);
  if (!$arg_nb){
    echo 'Il faut au moins un argument pour cette fonction';
    return false;
  }
  $arg = array (
    'url'       => null,
    'timeout'   => ini_get('max_execution_time'),
    'getContent'=> true,
    'offset'    => 0,
    'maxLen'    => null
  );
  foreach ($arg_passed as $k=>$v){
    $arg[$arg_names[$k]] = $v;
  }
  
  // connexion CURL et retour du résultat
  $ch = curl_init($arg['url']);
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($ch, CURLOPT_RESUME_FROM, $arg['offset']);
  curl_setopt($ch, CURLOPT_TIMEOUT, $arg['timeout']);
  $resultat = curl_exec($ch);
  $elapsed  = curl_getinfo ($ch, CURLINFO_TOTAL_TIME);  
  $CurlErr  = curl_error($ch);
  curl_close($ch);
  if ($CurlErr) {
    echo $CurlErr;
    return false;
  }elseif ($arg['getContent']){
    if ($arg['maxLen']){
      return substr($resultat, 0, $arg['maxLen']);
    }else{
      return $resultat;
    }
  }
  return $elapsed;
}

Exemples d'utilisation
$url        = 'http://www.rfc-editor.org/rfc/rfc2606.txt';
$timeout    = 2;
$getContent = true;                               
$offset     = 0;
$maxLen     = 50;

echo UrlGetContentsCurl($url, $timeout, $getContent,  $offset, $maxLen);
Retournera les 50 premiers caractères:

Code : Tout sélectionner

Network Working Group
$url        = 'http://www.rfc-editor.org/rfc/rfc2606.txt';
$timeout    = 2;
$getContent = true;                               
$offset     = 50;
$maxLen     = 50;

echo UrlGetContentsCurl($url, $timeout, $getContent,  $offset, $maxLen);
Retourne, bien entendu, les 50 caractères suivants:

Code : Tout sélectionner

D. Eastlake Request for Comments: 26
$url        = 'http://www.rfc-editor.org/rfc/rfc2606.txt';
$timeout    = 2;
$getContent = true;

echo UrlGetContentsCurl($url, $timeout, $getContent);
//ou
echo UrlGetContentsCurl($url, $timeout);
// ou 
echo UrlGetContentsCurl($url);
Retournont toute la page.
$url        = 'http://www.rfc-editor.org/rfc/rfc2606.txt';
$getContent = false;

echo UrlGetContentsCurl($url, $timeout,$getContent);
Retourne le temps de réponse du serveur et d'ouverture de la page (float):

Code : Tout sélectionner

0.521368026733
Toutes les erreurs produites tant par CURL que par la fonction sont envoyées en echo vers le navigateur mais une simple adaptation du code vous permettra de faire une gestion d'erreur plus appropriée à votre application.

Références:

http://php.belnet.be/manual/fr/ref.curl.php
http://php.belnet.be/manual/fr/ref.stream.php
http://curl.haxx.se/
ripat

ViPHP
ViPHP | 1380 Messages

26 déc. 2005, 15:54

Voici une version plus courte avec également une alternative à CURL pour ceux qui ne disposeraient pas de cette librairie. C'est une version simplifiée avec seulement le timeout comme argument.

Fonctions
Version CURL avec timeout comme seul argument.
function CurlFileGetContents($adresse, $timeout = 10){
  $ch = curl_init($adresse);
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($ch, CURLOPT_USERAGENT, "PHP script");
  curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
  $page    = curl_exec($ch);
  $CurlErr = curl_error($ch);
  curl_close($ch);
  if ($CurlErr){
    echo $CurlErr;
    return false;
  }else{
    return $page;
    return true;
  }
}
Version socket avec timeout.
function monFileGetContents($adresse, $timeout = 10){
  $url = parse_url($adresse);
  $url['port']   = isset($url['port'])   ? $url['port']               : '80';
  $url['scheme'] = isset($url['scheme']) ? strtoupper($url['scheme']) : 'HTTP';
  $fp = fsockopen($url['host'], $url['port'], $errno, $errstr, $timeout);
  if (!$fp) {
    echo "Erreur de socket: $errno - $errstr<br />\n";
    return;
  } else {
    $header  = 'GET '.$url['path'].' '.$url['scheme']."/1.1\r\n";
    $header .= 'Host: '.$url['host']."\r\n";
    $header .= "User-Agent: PHP Script\r\n";
    $header .= "Connection: Close\r\n\r\n";
    fputs($fp, $header);
    stream_set_timeout($fp, $timeout);
    while (!feof($fp)) {
      $page .= fread($fp, 262144);
    }
    $stream  = stream_get_meta_data($fp);
    fclose($fp);    
    if ($stream['timed_out']) {
      echo "Le délai de réponse de la page <b>".$adresse.
      '</b> a dépassé le timeout de <b>'.$timeout.'</b> sec.';
      return false;
    }else{
      // séparation du header
      $page = substr($page, strpos($page, "\r\n\r\n"));
      $page = trim($page);
      return $page;
    }
  }
}
Utilisation
$url = "http://www.rfc-editor.org/rfc/rfc2606.txt";

// CURL
$page = CurlFileGetContents($url, 5);

// par sockets
$page = monFileGetContents($url, 5);
Benchmark
Comparaison de CurlFileGetContents, monFileGetContents et des file_get_contents() et fopen(), fonctions standards de PHP.

Difficile de tester l'accès à un fichier distant dans des conditions réelles car la qualité des connexions entre client-serveur ne sont pas constantes. Toutes ces fonctions se retrouvent dans un mouchoir de poche avec un léger avantage pour CURL

Si on veut éliminer le côté aléatoire du réseau on peut travailler en loopback (localhost), voici un classement indicatif sur 100 boucles:

Code : Tout sélectionner

0.08609 sec. CurlFileGetContents (CURL) 0.12852 sec. monFileGetContents (sockets) 0.08988 sec. file_get_contents 0.11584 sec. fopen
ripat

ViPHP
fab
ViPHP | 2657 Messages

26 déc. 2005, 16:03

c'est assez proche quand le file_get_contents est ta fonction avec curl donc bon pour si peu je pense pas qu'il y est beaucoup de personnes qui changent pour utiliser curl
Seul l'intelligent a le pouvoir de se trouver con
try { work(); } catch(FlemmeExeption $e) { sleep(84600); }

ViPHP
ViPHP | 1380 Messages

26 déc. 2005, 17:21

Tu as raison pour des fichiers qui ne posent pas problème. Le but de cette gymnastique était de mettre un timeout que les flux de fichier.

Ce timeout n'existe pas sur les flux fopen() et file_get_contents(). De plus, set_time_limit est inopérant sur les flux! Eh oui, c'est comme ça!

Imagine qu'un de tes scripts utilise file_get_contents ou fopen pour récupérer, par exemple, des données 'on line' sur un fichier distant: cours de bourse, taux de change etc... Si une de ces pages n'est pas disponible ou est lente à charger, ton script se plantera et ta page qui incorpore ces données pédalera dans le vide.

Un timeout te permettra une porte de sortie du genre 'donnée indisponible' ou recherche de la même donnée sur un site alternatif et ta page s'affichera sans se planter.
ripat

ViPHP
fab
ViPHP | 2657 Messages

26 déc. 2005, 18:01

hum au passage tu aurais plus d'informations sur ta synthaxe pour passer les arguments dans ta premiere fonction cela m'interesse beaucoup éventuellement pour php-cli et faire des scripts qui fonctionnent sur php en mode web ert console
Seul l'intelligent a le pouvoir de se trouver con
try { work(); } catch(FlemmeExeption $e) { sleep(84600); }

ViPHP
ViPHP | 1380 Messages

26 déc. 2005, 20:09

La fonction func_get_args() te permet de récupérer tous les arguments passés.

Le reste permet d'affecter ces arguments aux indices d'un tableau d'arguments et de définir des valeurs par défaut.

Il y a peut-être plus simple. Mais c'est ce qui m'est venu à l'esprit en premier. Comme dans toutes les fonctions PHP, il y a une hiérarchie des arguments. Ainsi, si dans une fonction: maFonction(arg1, [arg2], [arg3]) où arg2 et arg3 sont optionnels, on ne peut pas invoquer cette fonction en "sautant" arg2. On ne peut pas faire maFonction(arg1, , [arg3]).

Voici le code, commenté cette fois:
// traitement des arguments optionnels et des valeurs par défaut.

  // tableau des noms que l'on veut donner aux arguments récupérés
  $arg_names    = array('url', 'timeout', 'getContent', 'offset', 'maxLen');
  
  // récupération proprement dite des arguments passés et comptage de ceux-ci
  $arg_passed   = func_get_args();     // $argv en CLI
  $arg_nb       = count($arg_passed);  // $argc en CLI

  
  // msg d'erreur si pas d'arguments passés et sortie de la fonction
  if (!$arg_nb){
    echo 'Il faut au moins un argument pour cette fonction';
    return false;
  }
  
  // affectation des valeurs par défaut de chacuns des arguments
  $arg = array (
    'url'       => null,
    'timeout'   => ini_get('max_execution_time'),
    'getContent'=> true,
    'offset'    => 0,
    'maxLen'    => null
  );
  
  // ré-affectation mais uniquement pour les arguments passés et récupérés
  // les autres (ceux qui n'ont pas été passés) conservent leur valeur par défaut.
  foreach ($arg_passed as $k=>$v){
    $arg[$arg_names[$k]] = $v;
  }

// voilà, tous les arguments se trouvent maintenant dans le tableau $arg.
En CLI-PHP tu joues sur les $argv et $argc mais le principe d'affectation des arguments devrait marcher.
ripat

ViPHP
fab
ViPHP | 2657 Messages

28 déc. 2005, 22:32

merci :)
Seul l'intelligent a le pouvoir de se trouver con
try { work(); } catch(FlemmeExeption $e) { sleep(84600); }