IO HTTP et performances dans PHP

Pour les besoins du module des IO sur HTTP, j’ai réalisé quelques tests afin de déterminer la meilleur méthode pour vérifier la présence d’un dossier ou d’un fichier sur un serveur web via HTTP.

La méthode la plus simple consiste à appeler l’URL de ce que l’on veut avec l’instruction file_get_contents.
C’est très simple mais très lent puisque tout le contenu est téléchargé alors que l’on ne souhaite que l’entête de la réponse du serveur. En fait, on a juste besoin de la première ligne de la réponse pour connaître le code de réponse du serveur, 200 si un fichier est disponible. 404 si le fichier n’existe pas. 403 si le fichier n’est pas accessible pour cause de restriction.
A noter que cette instruction et d’autres comme fopen nécessitent d’autoriser explicitement l’ouverture des URL avec allow_url_fopen=On et allow_url_include=On. Ces options sont nécessaires au bon fonctionnement du bootstrap, cela n’introduit donc pas de configuration supplémentaire.

La seconde méthode consiste à demander l’entête (header) via l’instruction PHP correspondante, get_headers.
Sauf que ce n’est pas mieux puisque le contenu est malgré tout téléchargé même si au final on n’extrait que l’entête.

Une autre méthode consiste à utiliser la librairie CURL dans PHP. Cette librairie est spécialisée dans les manipulations des URL, mais cela oblige à l’installer, ce qui n’est pas forcément la meilleur solution.
Par contre, en terme de performance, c’est d’un autre niveau. On peut raisonnablement savoir si un gros fichier est présent sur le serveur sans avoir de temps de latence démesuré. Au moins pour ces performances, cela peut valoir le coût d’installer cette librairie.

Enfin, une dernière solution consiste à générer soit-même la requête à transmettre au serveur et d’analyse la réponse. Cela peut être fait avec l’instruction fsockopen. Mais il faut tout faire, générer la requête, la transmettre, recevoir la réponse et la traiter.
La requête doit être du type HEAD pour n’avoir que l’entête.
Après avoir fait divers tests, cette dernière solution semble la plus rapide.

Voir les temps de réponses pour savoir si le dossier l est présent sur le serveur web local (CF code en annexe) :

 file_get_contents
 HTTP/1.1 200 OK
 2.0584919452667s

 get_headers
 HTTP/1.1 200 OK
 1.8262121677399s

 curl
 200
 0.006443977355957s

 fsockopen
 200
 0.0010509490966797s

Le dossier en question contient 24000 fichiers.
Il est clair de fsockopen est la solution la plus performante. C’est celle qui sera utilisée.

Je ne traite pas le cas des renvois type 301 puisqu’il n’est pas prévu pour l’instant dans la librairie nebule de suivre ces renvois.

Annexe :

Code des tests :

<?php
$url = "http://localhost/l/";

echo "file_get_contents<br />n";
$start_time = microtime(TRUE);
$response = file_get_contents($url);
echo $http_response_header[0]."<br>n";
$end_time = microtime(TRUE);
echo $end_time - $start_time."s<br>n<br />n";

echo "get_headers<br />n";
$start_time = microtime(TRUE);
$headers = get_headers($url);
echo $headers[0]."<br>n";
$end_time = microtime(TRUE);
echo $end_time - $start_time."s<br>n<br />n";

echo "curl<br />n";
$start_time = microtime(TRUE);
$handle = curl_init($url);
curl_setopt($handle,  CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($handle, CURLOPT_NOBODY, 1); // and *only* get the header 
/* Get the HTML or whatever is linked in $url. */
$response = curl_exec($handle);
/* Check for 404 (file not found). */
$httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
// if($httpCode == 404) {
    // /* Handle 404 here. */
// }
echo $httpCode."<br>n";
curl_close($handle);
$end_time = microtime(TRUE);
echo $end_time - $start_time."s<br>n<br />n";

echo "fsockopen<br />n";
$start_time = microtime(TRUE);
$purl=parse_url($url);
$fp = fsockopen($purl['host'], 80, $errno, $errstr, 1);
if ($fp !== false)
{
    $out  = "HEAD ".$purl['path']." HTTP/1.1rn";
    $out .= "Host: ".$purl['host']."rn";
    $out .= "Connection: Closernrn";
    $response = '';
    fwrite($fp, $out);
    while (!feof($fp))
    {
        $response .= fgets($fp,20);
        if ( sizeof($response) > 0 ) break;
    }
}
$code = substr($response, strpos($response, ' ') + 1, 3);
echo $code."<br>n";
$end_time = microtime(TRUE);
echo $end_time - $start_time."s<br>n<br />n";

Comments are closed.