Après plus de 16 années de présence sur le web, je continue encore aujourd'hui cette incroyable aventure qu'est la découverte du virtuel. Lampe torche, sac à dos, planche de surf et bien sûr ce calepin indispensable pour mémoire.

Les trackbacks Article

Introduction

Présentation

Les trackbacks ont été créés par Benjamin et Mena Trott en Août 2002. Suite à cela, une spécification a été écrite et est disponible sur le site de movabletype.

Ce système correspond à du peer-to-peer entre sites web puisque des informations sont échangées sous le protocole HTTP (en méthode POST). Il y aura donc deux serveurs (ou deux sites) qui entreront en communication et qui échangeront des données.

Idée des trackbacks

L'idée des trackbacks est de relier des sites web entre eux, ou plus particulièrement des pages de sites web avec d'autres pages d'un autre site web qui ont le même sujet.

On va prendre l'exemple de deux blogs : A et B qui parlent d'un sujet C (la LEN par exemple). Le blog B va envoyer des requêtes au blog A. Le blog A va enregistrer les données envoyées par le blog B et affichera dans une zone l'adresse du sujet présent sur le blog B.

L'interêt à retenir, c'est que les visiteurs du blog A auront une liste de liens à suivre si le sujet les intéresse. Il y a également le fait que c'est un système automatique qui peut dans certains cas s'avérer très utile (référant permanent ...).

Exemples

Concept datant de 2002, les trackbacks sont aujourd'hui très répandus sur la toile. Dans la plus grande majorité des cas, ce sont les blogs qui dominent cette technologie (exemple de système de gestion de blog : DotClear).

Les exemples ne manquent donc pas, mettez simplement : dotclear dans google ou alors, visitez le blog de l'auteur de DotClear et observez les zones appellées trackbacks.

Côté serveur local

Présentation

Le serveur local est le serveur qui envera les informations et recevra un listing des éventuelles erreurs (elles seront retournées en XML et proviendront du serveur distant). Ensuite, il traitera les informations pour afficher le message de réussite ou d'échec.

Principe

Les informations envoyées au serveur distant utilisent le protocole HTTP en méthode POST.

Dans notre cas, nous allons utiliser le php (testé avec apache2) pour faire fonctionner le système (donc les sources présentes dans la suite de cet article sont uniquement disponibles pour php).

L'envoi des informations par le protocole HTTP (suivant le système de socket pour établir une connexion à un serveur distant) est plus simplement appellé Requêtes HTTP. Les requêtes seront la base de la communication entre les deux serveurs.

Plusieurs données (aussi appellés paramètres) seront envoyées par l'intermédiaire des requêtes dont la plus importante est l'url.

Les différents paramètres

La liste des paramètres n'est pas extrêmement longue :

  • title : définit le titre de l'entrée (bien souvent ce qui sera entre la balise title de votre page web)
  • excerpt : un extrait de votre page web et il ne doit pas dépasser 250 caractères. Ce procédé permet de pouvoir stocker le texte dans un champs SQL de type varchar dont la longueur ne peut pas dépasser le nombre de caractères aloués.
  • url : l'adresse permanente (du ticket dans le cas d'un blog)
  • blog_name : le nom du blog ou du site qui envoi le ping (par exemple : xethorn.net)

L'ensemble des paramètres (ou arguments) mis ci-dessus, ne sont pas obligatoires sauf url qui est celle demandé par la majorité des systèmes de trackbacks.

Requêtes HTTP en méthode POST

Dans un premier temps, la requête HTTP va verifier si le serveur distant est disponible, ensuite, elle ouvrira la page appropriée en méthode POST envoyant, par la même occasion, les différents arguments/paramètres (les un après les autres).

Le format dans lequel les arguments seront envoyés correspond à application/x-www-form-urlencoded.

Il faudra donc penser à mettre en place une fonctionnalité qui se charge de convertir. Pour les personnes utilisant php, la fonctionnalité à utiliser est urlencode.

N'oubliez pas de séparer les paramètres en utilisant l'esperluette (le &).

N'encodez pas le caractère de séparation avec votre fonction, sinon, le langage que vous utilisez ne saura pas détecter la différence entre le nom de l'argument et sa valeur. De plus vos arguments peuvent être considérés comme invalides ou manquants.

Au final, votre requête aura comme aspect (d'un point de vue protocole HTTP) :

POST http://www.foo.com/mt-tb.cgi/5
Content-Type: application/x-www-form-urlencoded
title=Foo+Bar&url=http://www.bar.com/&excerpt=My+Excerpt&blog_name=Foo

Exemples

Pour illustrer l'envoi, je vous propose d'analyser un exemple de code source en php que j'ai réalisé pour l'occasion (et qui est libre de droit, vous pouvez le réutiliser comme bon vous semble).

<?php
# Données
$donnees =  'title='.urlencode('Le titre du message')
        .'&url='.urlencode('http://www.adresse.com/du/message.php')
        .'&excerpt='.urlencode('L\'extrait : sans code HTML')
        .'&blog_name='        .urlencode('Nom du blog');
 
# Ouverture du socket
// Pas de http:// dans le premier argument
// fsockopen('http://www.votresite.org',  80, $errno, $errstr) sera considéré comme invalide
$sock = fsockopen('www.votresite.org', 80, $errno, $errstr);
 
# Requetes
// On ouvre la page en méthode POST
// Dans le cas où il se trouve ailleur qu'a la racine du site
// mettez : /le_dossier/url.php
fputs($sock, "POST /url.php HTTP/1.0\r\n");
 
// On définit l'host
fputs($sock, "Host:www.votresite.org \r\n");
 
// On définit le format des données
fputs($sock, "Content-type: application/x-www-form-urlencoded\r\n");  // définition du format
 
// On définit la taille des données envoyés par l'intermediaire
// de la fonction strlen()
fputs($sock, "Content-length: " . strlen($donnees) . "\r\n"); // taille
 
// On définit les différents langages acceptés
// Ici, tout est défini de telle manière que l'on accepte tout
fputs($sock, "Accept: */*\r\n"); // données acceptés par le navigateur
 
// On envoi les données
fputs($sock, "\r\n"); // Ligne de séparation
fputs($sock, "$donnees\r\n"); // Envoi des données
fputs($sock, "\r\n"); // Ligne de séparation
 
// Fermeture du socket
fclose($sock);
?>

Le code est structuré en 3 grandes parties :

L'ouverture du socket puis de la page appropriés

Les sockets, avec php, sont ouverts grâce à la fonction fsockopen. Cette fonction ne doit en aucun cas contenir de "http://" dans son premier argument (sinon, php ne comprendra pas à quoi cela correspond).

Ensuite, on commence à envoyer des données grâce à la fonction fputs. La première donnée envoyée est l'adresse du fichier sur le serveur distant. Dans l'exemple situé au dessus, nous sommes sur la page /url.php qui est présente sur http://www.votresite.org/url.php.

Envoi d'informations pour le traitement

Puis, il faut envoyer (toujours par fputs) les informations qui permettront à Apache (ou un autre serveur distant) de comprendre les informations envoyées en URL telles que le format des données (application/x-www-form-urlencoded) mais aussi la taille de ses paramètres destinés au système de trackbacks (Content-length).

Envoi des paramètres

Les paramètres sont également envoyés par fputs. Par contre, observez bien lorsque la variable $donnees est définie. Vous verrez que la fonction urlencode est utilisée uniquement pour la partie valeur des arguments et non le nom des arguments.

Maintenant, il reste à voir la lecture des informations côté serveur distant ainsi que la réception des éventuelles erreurs sur le serveur local.

Côté serveur distant

Présentation

Le serveur distant aura pour rôle de recevoir les informations envoyées par le serveur local. Il sera chargé de répondre aux requêtes envoyées, d'analyser les données puis de retourner un fichier xml qui affichera les éventuelles erreurs.

Principe

Récéption des données

Les informations étant en POST, il faut utiliser la variable adéquate ou la fonctionnalité adéquate du langage que vous utilisez. Dans le cas du php, la variable $_POST fera parfaitement l'affaire.

Les données seront analysées pour être validées ou être déclarées comme invalide. Ensuite, une page xml sera générée (ce fichier sera parsé pour afficher dans le format souhaité les différentes erreurs).

Fichier de retour

Le fichier de retour est écrit sous le format XML et comprend deux branches principales :

  • error : dans le cas où il n'y a pas d'erreur, elle devra avoir pour valeur 0 et dans le cas inverse (une ou plusieurs erreurs) elle retournera comme valeur 1
  • message : cette partie là est à remplir uniquement si la valeur de error est 1. Elle retournera un message d'erreur (par exemple : l'adresse n'est pas mentionnée ou est invalide).

Donc, dans le cas où il n'y a pas d'erreur, votre fichier xml aura cette allure :

<?xml version="1.0" encoding="iso-8859-1"?>
<response>
<error>0</error>
</response>

Dans le cas inverse :

<?xml version="1.0" encoding="iso-8859-1"?>
<response>
<error>1</error>
<message>Le message d'erreur</message>
</response>

Exemple

Pour la récupération des informations, vérifiez bien que dans le fichier de configuration de php (php.ini) la variable register_globals est bien sur Off et non sur On. Les informations seront récupérées par l'intermédiaire de la variable $_POST.

Dans un premier temps, autant faire un test qui permette d'afficher les données envoyées par le serveur local :

<?php
echo
    'Nom du blog : '.$_POST['blog_name'].'<br />'.
    'Titre du message : '.$_POST['title'].'<br />'.
    'Adresse du message : '.$_POST['url'].'<br />'.
    'Extrait : '.$_POST['excerpt'];
?>

Voyant qu'il vous est impossible de recevoir la réponse du serveur distant, rajoutez ce petit code en bas du fichier qui envoit les données :

<?php
// Message de réponse
while (!@feof($sock))
    $body .= @fgets($sock, 4096);
echo $body;
?>

Normalement, vous devriez obtenir quelque chose de similaire à ceci :

HTTP/1.1 200 OK
Date: Sun, 06 Jun 2004 07:36:49 GMT
Server: Apache/2.0.49 (Unix) DAV/2 PHP/5.0.0RC2
X-Powered-By: PHP/5.0.0RC2
Connection: close
 
Content-Type: text/html; charset=ISO-8859-1
Nom du blog : Nom du blog
Titre du message : Le titre du message
Adresse du message : http://www.adresse.com/du/message.php
Extrait : L'extrait : sans code HTML

Si vous avez bien cela, vous allez pouvoir commencer à faire des vérifications concernant le contenu. Voici un début de code source :

<?php
$post = array(
                'blog_name'    =>    'Le nom du blog n\'est pas défini',
                'url'        =>    'L\'adresse n\'est pas définie',
                'title'        =>    'Le titre n\'est pas défini',
                'excerpt'    =>    'L\'extrait n\'est pas défini'
            );
 
$erreurs = array();
 
foreach($post as $nom => $valeur) {
    if(empty($_POST[$nom])) $erreurs[] = $valeur;
}
echo '<?xml version="1.0" encoding="iso-8859-1"?>';
 
// Comptabilisation des erreurs
// Si des erreurs ont été détéctés
if(count($erreurs) > 0) {
?>
 
<response>
   <error>1</error>
   <message><?php echo implode("\n\t\t",$erreurs) ?></message>
</response>
 
<?php
    }
// Si aucune erreur n'est détéctée
else {
?>
 
<response>
   <error>0</error>
</response>
 
<? } ?>

Ce script ne montre qu'une ébauche de ce que peut représenter la vérification des données reçues (il doit donc être amélioré : vérification du format de l'url, de la taille de l'extrait ...).

Maintenant que nous avons vu l'intégralité de la communication entre les deux serveurs (le principe de peer-to-peer) penchons-nous sur le parsing du fichier xml retourné par le serveur distant.

Réception des éventuelles erreurs

Rôle des réponses

Les erreurs permettent de signaler qu'un trackback n'a pas été enregistré, que l'adresse est déjà enregistrée dans la base de données (système permettant d'éviter les doubles posts) ...

Donc, les réponses sont là pour prévenir des éventuelles erreurs permettant ainsi au webmaster ou au bloggeur d'effectuer les modifications nécessaires afin de pouvoir remplir toutes les conditions demandées.

Réception des erreurs / transformations des erreurs

Pour la transformations des erreurs, le choix est misé sur un parseur de fichiers XML. Il suffira simplement d'utiliser des regex pour extraire les informations souhaitées et afficher les messages de réussite ou d'erreurs.

Dans un premier temps, il faut faire une différence entre le header et le contenu. Pour cela, et après analyse du premier résultat de test, on voit facilement une séparation entre le header et le contenu (par un retour à la ligne vide).

Donc, il faut rajouter dans le code php :

<?php
// Lecture des informations
// En header
while ($str = @trim(@fgets($sock, 4096)))
    $headers .= "$str\n";
?>

Avant le code :

<?php
// Message de réponse
while (!@feof($sock))
    $body .= @fgets($sock, 4096);
echo $body;
?>

Nous allons donc commencer à analyser le texte issu de $body.

Pour résumer, le fichier xml généré contient une balise <error> et une balise <message> si <error> à pour valeur 1. Une regex va donc récupérer la valeur de la balise error et la comparera à deux résultats, dans le cas où une/ou plusieurs erreurs sont détectées, il prendra le contenu provenant de la balise message.

Un code source sera plus parlant :

<?php
// Detection des éventuelles erreurs
// Utilisation de ereg
if(ereg('<error>1</error>',$body)) {
    // Affichage du message d'erreur
    echo 'L\'envoi du trackbacks a échoué pour la ou les raisons suivantes : <br />';
 
    // Prise du contenu dans la balise <message>
    // On retourne les resultats dans $text
    preg_match('#<message>(.*)</message>#is',$body,$text);
 
    // Affichage des erreurs
    // Et mise en place du retour à la ligne
    echo nl2br($text[1]);
}
else {
    // Affichage du message de réussite
    echo 'L\'envoi du trackback a réussi';
}
?>

Voilà, maintenant, vous avez un début de gestionnaire de trackbacks. Une zone envoi et une zone réception - donc, le B.A.B.A de ce système.

Codes sources

Pour ceux qui n'auraient pas suivit l'intégralité du dossier et qui n'auraient pris que les codes sources, voici ce que vous devriez avoir à la fin :

Serveur local

Voici le code source du fichier qui vous permettra d'envoyer des trackbacks vers des serveurs distants :

<?php
# Données
$donnees =  'title='.urlencode('Le titre du message')
       // .'&url='.urlencode('http://www.adresse.com/du/message.php')
        .'&excerpt='.urlencode('L\'extrait : sans code HTML')
        .'&blog_name='        .urlencode('Nom du blog');
 
# Ouverture du socket
$sock = fsockopen('localhost', 80, $errno, $errstr);
 
# Requetes
// On ouvre la page en m?thode POST
fputs($sock, "POST /test/test.php HTTP/1.0\r\n");
 
// On définit l'host
fputs($sock, "Host:localhost \r\n");
 
// On définit le format des données
fputs($sock, "Content-type: application/x-www-form-urlencoded\r\n");  // définition du format
 
// On définit la taille des données envoyées par l'intermediaire
// de la fonction strlen()
fputs($sock, "Content-length: " . strlen($donnees) . "\r\n"); // taille
 
// On définit les diférents langages acceptés
// Ici, tout est défini de telle manière que l'on accepte tout
fputs($sock, "Accept: */*\r\n"); // données acceptées par le navigateur
 
// On envoi les données
fputs($sock, "\r\n"); // Ligne de séparation
fputs($sock, "$donnees\r\n"); // Envoi des données
fputs($sock, "\r\n"); // Ligne de séparation
 
while ($str = @trim(@fgets($sock, 4096)))
    $headers .= "$str\n";
 
// Corp du document
while (!@feof($sock))
    $body .= @fgets($sock, 4096);
 
// Detection des éventuelles erreurs
// Utilisation de ereg
if(ereg('<error>1</error>',$body)) {
    // Affichage du message d'erreur
    echo 'L\'envoi du trackbacks a échoué pour la ou les raisons suivantes : <br />';
 
    // Prise du contenu dans la balise <message>
    // On retourne les resultats dans $text
    preg_match('#<message>(.*)</message>#is',$body,$text);
 
    // Affichage des erreurs
    // Et mise en place du retour à la ligne
    echo nl2br($text[1]);
}
else {
    // Affichage du message de réussite
    echo 'L\'envoi du trackback a réussi';
}
// Fermeture du socket
fclose($sock);
?>

Serveur distant

Voici le fichier qui vous servira à recevoir des trackbacks à partir de serveurs externes

<?php
$post = array(
                'blog_name'    =>    'Le nom du blog n\'est pas défini',
                'url'        =>    'L\'adresse n\'est pas définie',
                'title'        =>    'Le titre n\'est pas défini',
                'excerpt'    =>    'L\'extrait n\'est pas défini'
            );
 
$erreurs = array();
 
foreach($post as $nom => $valeur) {
    if(empty($_POST[$nom])) $erreurs[] = $valeur;
}
echo '<?xml version="1.0" encoding="iso-8859-1"?>';
 
// Comptabilisation des erreurs
// Si des erreurs ont été détéctés
if(count($erreurs) > 0) {
?>
 
<response>
   <error>1</error>
   <message><?php echo implode("\n\t\t",$erreurs) ?></message>
</response>
 
<?php
    }
// Si aucune erreur n'est détéctée
else {
?>
 
<response>
   <error>0</error>
</response>
 
<? } ?>