En effet, il est tout à fait possible d'envoyer des requêtes à un serveur PHP utilisant le protocole FastCGI indépendamment d'un serveur HTTP.

Il suffit pour cela d'ouvrir une socket sur l'adresse IP et le port correspondant au serveur PHP, et d'y envoyer les données nécessaires en respectant le protocole FastCGI.

Cela peut sembler bien compliquer, surtout par rapport à un appel HTTP classique, mais nous allons voir que le jeu en vaut la chandelle car le gain de performance peut être très significatif.

En effet, le protocole FastCGI supporte le mutiplexage, ce qui signifie qu'il est possible d'envoyer sur une même socket plusieurs requêtes sans devoir l'ouvrir et la fermer pour chacune d'elle.

Il est donc possible d'envoyer à un serveur respectant ce protocole une ou plusieurs requêtes parallèlement et non plus de manière séquentielle, tout en ayant la possiblité d'effectuer d'autres tâches en attendant les réponses correspondantes.

Et si le client FastCGI est écrit en PHP, il devient possible d'y faire appel dans le cadre d'un script PHP et donc de distribuer efficacement sur un ou plusieurs serveurs l'exécution de une ou plusieurs tâches identiques ou différentes.

De plus, depuis sa version 5.4 et s'il a été compilé à l'aide de l'option --enable-fpm, le langage dispose d'un serveur FastCGI portant le nom de php-fpm qui se lance très simplement grâce à la commande suivante :

# php-fpm

Le serveur FastCGI écoute alors par défaut sur l'adresse IP 127.0.0.1 sur le port 9000 et il n'y a plus qu'à lui envoyer des requêtes pour qu'il les exécute.

Grâce à cela, il devient donc vraiment très simple de déléguer à un serveur FastCGI l'exécution de certaines tâches de manière asynchrone via un client écrit en PHP, par exemple de la manière suivante :

<?php

require_once '/path/to/atoum/classes/autoloader.php';

use mageekguy\atoum;

$stream = new atoum\fcgi\stream('tcp://127.0.0.1:9000', 30, true);

$request = new atoum\fcgi\requests\post();
$request->SCRIPT_FILENAME = '/path/to/task.php';
$responses = $requests = 0;

const numberOfRequests = 10000;

while ($responses < 10000)
{
   if ($requests < numberOfRequests)
   {
      // on envoit la requête
      $request['request'] = ++$requests;
      $stream($request);
   }
   
   // il est possible de faire d'autres choses ici en attendant…

   // on collecte le nombre de réponses
   $responses += sizeof($stream());
}

?>

Le script précédent envoie parallèlement et de manière asynchrone 10000 requêtes au serveur FastCGI via $stream($request), chacune d'elles demandant l'exécution du script /path/to/task.php en utilisant la methode POST.

Les réponses sont quand à elles collectées via $stream(), et dans l'intervalle entre l'envoie des requêtes et la collecte des réponses, il est tout à fait possible d'exécuter le code de son choix.

Pour information, le code ci-dessus est fonctionnellement équivalent à celui-ci :

<?php
$options = array('http' =>
   array(
      'method'  => 'POST',
      'header'  => 'Content-type: application/x-www-form-urlencoded',
      'content' => array('query' => uniqid())
   )
);

$context  = stream_context_create($options);

for ($i = 0; $i < 10000; $i++)
{
   file_get_contents('http://my.web.server/task.php', false, $context);
}

?>

Ce dernier nécessite cependant l'installation et la configuration d'un serveur HTTP, ce qui est un peu plus complexe que de faire appel à php-fpm, et surtout, il est beaucoup moins performant, car il exécute les requêtes nécessaires non plus parallèlement mais de manière séquentielle.

Ainsi, dans le meilleur des cas, sur mon MacBook Air 2011 avec PHP 5.4.5, il traite les 10000 requêtes en environs 10 secondes, alors que le client FastCGI permet de les exécuter en approximativement 3 secondes, et cela sans aucune optimisation au niveau des configurations respectives de php-fpm ou de apache.

Pour être clair, le client FastCGI est donc out of the box trois fois plus efficace que le client HTTP.

Pour celui qui a besoin de faire d'exécuter des tâches de manières distribuées et asynchrone, comme par exemple calculer un devis ou bien exécuter des tests unitaires, l'utilisation d'un client FastCGI capable de faire du multiplexage et de l'envoie asynchrone de requêtes semble donc être une solution pertinente, puisqu'en plus d'être performante, elle est facile à installer et à utiliser car elle ne fait pas appel à des technologies tierces.

Certes, il existe des solutions alternatives, comme par exemple Gearman, mais ce dernier ne permet pas de récupérer facilement le résultat d'une tâche exécutée de manière asynchrone, et de plus, il nécessite l'installation d'un serveur dédié et d'une extension PHP, tout comme des outils tel que øMQ.

Pour ceux qui voudraient s'amuser, le code de mon client FastCGI (encore très expérimental et donc loin d'être utilisable en production) est disponible dans une branche spécifique de atoum, mon framework de tests unitaires pour PHP ≥ 5.3 simple, moderne et intuitif, l'idée (soufflé par Ivan Enderlin, le papa de Hoa) étant est en effet de l'utiliser pour permettre l'exécution de tests unitaires de manière distribuée sur une ferme de machines (et il nécessite que PHP ait été compilé avec l'option --enable-sockets).