2005-07-03

Psy Guest Book 1.1

Il y a peu, on m'a demandé conseil pour insérer un livre d'or sur un site Web. Je me suis donc attelé à la tâche d'en chercher un gratuit et potable. J'avais certaines idées en tête, je voulais notamment un truc simple à mettre en œuvre et à modifier.

Après avoir testé pleins de systèmes différents, je n'étais toujours pas satisfait. Je me suis donc dit que ça serait là un bon exercice de programmation, et pas trop long. De plus, je pourrais réutiliser une bonne partie des concepts dans le cadre d'un système de commentaires sur mon blog.

Au bout de quelques jours, la version 1.0 était là, avec les fonctionnalités dont je voulais :

  • un truc simple, trois fichiers seulement : un fichier php, un fichier css et un fichier .htaccess facultatif ;
  • une page sémantiquement pas trop dégueux, en xhtml strict, délivrée en application/xhtml+xml ;
  • de l'UTF-8 pour autoriser tous les caractères existants ;
  • pas de base de données (donc simple à installer) ;
  • reconnaissance de paragraphes ;
  • petit système anti-flood (qui vaut ce qui vaut hein) ;
  • rapide d'accès.

Il ne manquait qu'un seul problème à gérer : l'accès concurrentiel. En effet, quelles seraient les conséquences sur le fichier de stockage des messages vis-à-vis d'accès simultanés ? Je me suis dit qu'il y avait des chances que ça corrompe le fichier contenant tous les messages.

Étudier les accès simultanés à la main, c'est aussi pratique que nettoyer son balcon à la brosse à dent, c'est pourquoi j'ai mis sur pied :

  • un serveur Apache faisant tourner PsyGB ;
  • un fichier PsyGB modifié avec pleins d'informations de debug ;
  • un programme C++ simulant un postage HTTP massif en simultané de plusieurs dizaines de clients, chacun postant plusieurs dizaines de messages assez long.

Premier test, après avoir reçu 1000 messages dans la tête, PsyGB a généré un fichier de messages corrompu. Certains messages étaient entrelacés dans la masse tels un plat de capellini. Pour résoudre ce problème, j'ai décidé de me limiter à un seul appel de fonction d'écriture de fichier.

Avant :

fwrite($hFile, $strDate);
fwrite($hFile, $strAuthor);
fwrite($hFile, $strMessage);

Après :

$strData = $strDate . $strAuthor . $strMessage;
fwrite($hFile, $strData);

J'ai relancé le test C++ d'envoi massif. Cette fois-ci, terminées les données entrelacées... :)

... en revanche, j'ai subi de la perte de données :( Sur 200 messages envoyés correctement (c'est ce que me disait Apache, et mes traces de debug), une dizaine s'évaporait. Il m'a donc fallu penser à mettre un système de gestion d'accès concurrentiels. Les bases de données gèrent ça en natif, mais quand on souhaite n'utiliser que des fichiers, il faut faire ça à la main.

Après quelques recherches peu satisfaisantes (fonctions de sémaphore et flock de PHP) j'ai fini par trouver un bout de code intéressant dans les commentaires de la fonction flock sur php.net. La méthode consiste à créer un répertoire temporaire le temps de l'écriture. Si un autre processus trouve le répertoire, il attend son tour. Pourquoi un répertoire et pas un fichier ? La création de répertoire est une opération atomique, contrairement à la création de fichier.

J'ai mis en place cette méthode. Après avoir envoyé 1200 messages à partir de 60 threads (pourquoi pas 100 threads ? Une limitation indirecte de Windows), le nombre de requêtes acceptées par Apache était identique au nombre de messages contenu dans le fichier de stockage de PsyGB. Objectif atteint ! :)

Du coup je peux me permettre de publier le tout. J'ai créé une page pour l'occasion : psygb d'où vous pourrez télécharger les sources. Vous pouvez accéder à une démo en ligne aussi si vous voulez. Les messages doivent être assez courts sous peine d'être tronqués (tronquer de l'UTF-8, ça a été drôle à coder ça aussi). Les messages ne sont pas destinés à rester toute l'éternité, c'est juste une démo :)