Auteur : Bruno Bord
Date : 03-Dec-2002
Version : 1.0
Contact : brunobord@yahoo.frJe tiens à avertir le lecteur non averti que je vais te tutoyer. Merci de ne pas s'en formaliser, c'est juste une sale manie. Sur le web, tout le monde se tutoie. Alors, pourquoi pas dans une doc ? D'autant plus que quelqu'un qui s'intéresse à REBOL et à XML-RPC ne peut pas être foncièrement mauvais. Donc, on se dit "tu".
Lors de ce tutorial, nous verrons comment programmer un forum simple, le transformer en service web en utilisant le protocole XML-RPC.
| Attention ! |
On va beaucoup causer PHP et assez peu REBOL dans cette partie. Mais c'est à mon avis une étape indispensable dans ce tutorial. Je ne cherche pas à te convertir au PHP. Ce qui est le plus important, c'est de voir que d'autres langages que REBOL sont capables de "parler" XML-RPC. C'est l'aspect communicant de cette application qui est réllement mis en valeur. Le langage servant à l'implémentation du service reste ensuite à ta discrétion. |
Nous verrons dans un prochain article comment utiliser les résultats de cette implémentation pour fabriquer une petite (et modeste) application cliente graphique en REBOL.
Le service web décrit ci-après utilise le duo célèbre PHP/MySQL. Mais n'y vois rien d'obligatoire. J'ai choisi ce "couple" parce qu'il est très largement répandu dans le web (parmi les hébergeurs, gratuits ou payants), et que je trouve personnellement PHP très puissant et très complet. De plus, il est très facile de mettre en place un serveur Apache interprétant du PHP et un serveur MySQL, sous quelque plateforme que ce soit. Personnellement, j'utilise le package EasyPHP pour tous mes tests de site en PHP (voir les liens à la fin de l'article).
PHP est facile à apprendre pour qui sait à quoi peut servir un langage de script serveur. Pour conclure, la fabrication d'un forum est quand même plus simple en PHP qu'en REBOL (et même en utilisant MAGIC!, n'est-ce pas Olivier ?).
| Sans ambiguïté |
Pour ce tutorial, je pars du principe que tous les thèmes abordés dans la première partie ont été compris et assimilés. Ceci couvre les webservices, la norme XML-RPC en particulier, et une utilisation sommaire de rebXR. De même, je vais utiliser pour la partie serveur la syntaxe PHP, et pour la base de données, SQL. Il faudrait que tu connaisses un peu ces deux langages pour piger le reste. Je ne voudrais surtout pas que tu copies-colles sans comprendre, ce n'est pas du tout le but du tutorial. L'essentiel est d'assimiler les principes de base de la construction d'un webservice simple. Rien ne t'empêche d'ailleurs de programmer directement en ASP / ACCESS un webservice de résultats sportifs tout en suivant cet article. Mon objectif serait même complètement atteint. |
Qu'est-ce qu'un forum ? C'est une seule et unique table. Je sais bien que certains vont me regarder d'un drôle d'air, mais il faut dire franchement les choses. La table des messages est la seule table réellement utile dans un forum.
Si on décortique un package du style PHPbb ou phorum, pour citer les plus célèbres, on va trouver une bonne dizaine de tables, une messagerie interne, des compteurs de pages / de clics / de visiteurs, des groupes de travail, que sais-je...
Mais au bout du compte, l'info qui compte le plus, c'est le message. Titre + Corps. Eventuellement la Date et l'Auteur, encore que... L'auteur... Il y a tellement d'usurpation possible dans l'utilisation des pseudonymes que je me demande si l'auteur est vraiment indispensable. Ou alors, en effet il va falloir mettre en place un système de groupe + membres + identification, etc... La barbe !
Nous aurons donc une table des messages, dotée d'un identifiant unique (numérique), avec les champs auteur, date, titre et texte.
Le champ de date sera renseigné automatiquement au moment de la création de chaque message. Les autres champs seront de type chaîne. Le champ texte sera illimité.
Là encore, voyageons léger. On doit pouvoir lire les messages existants et écrire des messages (ou "poster"). Pour simplifier ce tutorial, je ne mettrais pas en place de système d'arborescence (en engliche, on dirait "thread"). Inutile de compliquer. Les messages seront les uns au-dessous des autres.
Pour simplifier la navigation, nous doterons le forum en webservice de 3 fonctionnalités :
Nous pouvons d'ores et déjà nommer ces trois fonctions, et leur coller les paramètres dont elles auront besoin. De même, sachons par avance ce que ces fonctions vont renvoyer.
CREATE TABLE forum (
id int(11) NOT NULL auto_increment,
auteur varchar(50) NOT NULL default '',
date date NOT NULL default '0000-00-00',
titre varchar(255) NOT NULL default '',
texte text NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY id (id),
KEY id_2 (id)
) TYPE=MyISAM;Clair net et précis. L'identifiant, id, est auto_increment, c'est à dire qu'il s'incrémentera automatiquement dans la table. C'est la clé, l'index primaire. Pour des systèmes plus élaborés (avec par exemple, un compteur "nombre de message sur un jour donné" ou "nombre de messages postés par une personne", on pourrait rajouter des indexes sur les champs date ou auteur. Ceci accélèrerait considérablement les requêtes. Mais je vais pas t'apprendre à gérer une base de données.
Pour les besoins de l'exercice, je vais créer la base forum sur mon serveur mysql, et la table forum dans cette base.
Je te conseille vivement de remplir la table du forum avec des messages bidons avant de tester les fonctions, ça permet de valider leur utilisation. Lorsque le webservice sera mis en route, tu pourras virer les éléments de tests, ça fera plus propre.
Pour le coup, on pourrait d'abord créer tout un module de forum en PHP "pur", c'est à dire qu'il générerait des pages HTML dynamiques qui listeraient les messages et qui pourraient afficher leur contenu, ainsi qu'une page de formulaire pour envoyer les posts sur le forum.
Mais je crains de manquer de temps, alors on va aller directement au but.
Cette classe est téléchargeable sur le site de sourceforge.net :
Elle est l'oeuvre de Edd Dumbill, qui est relativement impliqué dans le formalisme XML-RPC et ses implémentations.
Dans ce package, on trouve de nombreuses pages PHP permettant d'implémenter des clients ainsi que la partie serveur (qui nous intéresse plus). Comme en ce qui concerne rebXR, cet ensemble de scripts se veut simple d'utilisation. A aucun moment on ne manipule réellement la moindre balise XML, tant pour parser le flux d'entrée que pour écrire le flux XML de sortie. On utilise les capacités de programmation orientée objet de PHP pour fabriquer un objet "response" qui va être traduit en XML par la classe et envoyé via HTTP au client.
Ainsi, envoyer un simple entier vers le client s'écrira :
return new xmlrpcresp(new xmlrpcval($mavariable,"int"));Les deux seuls fichiers indispensables pour construire un serveur sont :
Je te propose de les copier coller quelque part dans l'arborescence de ton serveur, dans un répertoire nommé par exemple "/xmlrpc". Le service web que l'on va écrire aura donc pour adresse http://monserveur/xmlrpc/.
Théoriquement, tout serveur XML-RPC doit connaître au moins trois fonctions :
Hélas, j'ai bien dit : théoriquement. Parce qu'il arrive souvent que ces trois fonctions ne soient pas implémentées sur le serveur, pour des raisons de sécurité sans doute. Bon, on a du bol, Edd Dumbill a pensé à nous et ces fonctions sont implémentées par défaut dans la classe XML-RPC pour PHP.
Construisons d'abord le squelette de notre webservice. Soit le fichier index.php dans la même arborescence que les classes XML-RPC pour PHP :
<?php
include("xmlrpc.inc");
include("xmlrpcs.inc");
$s = new xmlrpc_server( array(
"forum.getListeMessages" =>
array("function" => "i_getListeMessages",
"signature" => $i_getListeMessages_sig,
"docstring" => $i_getListeMessages_doc),
"forum.getContenuMessage" =>
array("function" => "i_getContenuMessage",
"signature" => $i_getContenuMessage_sig,
"docstring" => $i_getContenuMessage_doc),
"forum.putMessage" =>
array("function" => "i_putMessage",
"signature" => $i_putMessage_sig,
"docstring" => $i_putMessage_doc)
));
?>En faisant cela, nous avons déclaré un serveur XML-RPC donnant accès à trois fonctions (en plus des trois premières, bien sûr).
Mais, tu me diras : "aucune de ces trois fonctions n'est implémentée, et si on s'amusait à interroger le serveur, il serait bien incapable de nous renvoyer la moindre information."
Et tu aurais raison. C'est parfaitement exact.
Par contre, à titre d'exercice, on va tester le package. D'abord, tu démarres le serveur web. Ensuite, tu ouvres une session REBOL et tu charges les fonctions de la classe rebXR. Et après...
>>remote: make xmlrpc-client []
>>remote/set-server http://localhost/xmlrpc/index.php
>> print remote/exec [system.listMethods]
forum.getListeMessages forum.getContenuMessage forum.putMessage system.l
istMethods system.methodHelp system.methodSignatureLes six fonctions ont bien été déclarées sur le serveur. Par contre :
>> print remote/exec [forum.getListeMessages 10]
** Script Error: unmarshal-resp expected xml-response argument of type:
string
** Where: exec
** Near: xmlrpc-marshaler/unmarshal-resp resÇa buggue. C'est normal. Y'a personne au bout du fil.
Reste donc à coder les trois fonctionnalités de notre forum en PHP.
Chaque fonction du webservice doit être composée de trois choses :
function i_getListeMessages($m) {
global $xmlrpcArray;
global $xmlrpcStruct;
$outAr = array();
$v = new xmlrpcval();
include("include/db_connect.php");
if (!empty($m)) {
$nb_o = $m->getParam(0);
$nb = $nb_o->scalarval();
} else {
$nb = 5;
}
if ($nb == 0) {
$sql = "SELECT id, titre FROM forum ORDER BY id DESC";
} else {
$sql = "SELECT id, titre FROM forum ORDER BY id DESC LIMIT 0, $nb";
}
$mysql_result = mysql_query($sql);
while ($row = mysql_fetch_row($mysql_result)) {
$outAr[] = new xmlrpcval(array(
new xmlrpcval($row[0],"int"),
new xmlrpcval($row[1],"string")), "array");
}
$v->addArray($outAr);
return new xmlrpcresp($v);
}Dans le détail :
| getListeMessages($m) | un seul argument est déclaré pour la méthode : $m, même si la méthode en requiert zéro ou plusieurs. Cette variable est en fait un objet qu'on peut parser. On voit d'ailleurs dans la suite que l'on demande $m->getParam(0), soit le premier élément des arguments. Tu vois déjà comment faire pour récupérer le deuxième et ainsi de suite, n'est-ce pas ?... |
| global $xmlrpcArray; | la variable globale $xmlrpcArray correspond au type tableau décrit dans la classe XML-RPC pour PHP. |
| $v = new xmlrpcval(); | c'est la variable de retour. On la déclare en tant que xmlrpcval, soit un objet qui deviendra un flux XML-RPC. Chaque élément de la réponse qui devra être renvoyé est ajouté par la méthode addArray(); |
| include("include/db_connect.php"); | je te conseille vivement de créer un fichier db_connect.php qui contiendra tout le code nécessaire à la connexion à ta base de données. Je le dis souvent : il faut être feignant dans ce métier, alors écrire une fois ces quelques lignes de connexion pour l'appeler systématiquement 10000 fois, c'est pas du luxe. |
| if (!empty($m)) ... | petite sécurité pour éviter un bug éventuel à l'exécution. Au cas où la fonction n'aurait pas reçu l'argument _nb, je lui assigne une valeur par défaut (5). |
| $nb_o = $m->getParam(0); | je récupère le premier objet de la liste des paramètres. |
| $nb = $nb_o->scalarval(); | j'utilise la méthode scalarval pour extraire la valeur de ce paramètre. Le type de la variable est accessible par la méthode scalartyp. |
$sql = "SELECT id, titre FROM forum ORDER BY id DESC LIMIT 0, $nb";
C'est du SQL. Je sélectionne les messages dans l'ordre décroissant, pour avoir les derniers messages entré en tête de liste. A noter que si le paramètre $nb est égal à zéro, je renvoie la liste de tous les messages, sans limite. A utiliser avec parcimonie, cette liste pouvant devenir très longue si le forum a quelque succès. (1500 enregistrements à transférer en XML, puis à parser dans l'application, ça risque de charger un peu...)
| while ($row = ... | parcours classique d'un enregistrement mysql. Pas de quoi révolutionner le monde de l'internet ou du script serveur. |
| Là, ça rigole plus : |
new xmlrpcval(array(
new xmlrpcval($row[0],"int"),
new xmlrpcval($row[1],"string")),
"array");Par cette commande, je crée une valeur xmlrpcval, qui contient un tableau de deux xmlrpcval (un entier et une chaîne. Je donne le type de la valeur "principale" à la fin : c'est un tableau ("array"). J'ai donc un tableau constitué d'un entier et d'une chaîne. Avec les indentations, c'est assez clair (j'espère).Le tableau de sortie reçoit ainsi les "n" éléments de la liste des messages avec id + titre. |
| $v->addArray($outAr); | le tableau de sortie est enfin affecté à la variable de retour sous forme d'un tableau. Nous avons donc en fait un tableau de tableaux. |
| return new xmlrpcresp($v); | on retourne cette valeur. |
$i_getListeMessages_sig = array(array($xmlrpcArray, $xmlrpcInt));
$i_getListeMessages_doc = "Retourne les 'n' messages du forum";La "signature" est on ne peut plus claire. On retourne un tableau (xmlrpcArray) alors qu'en entrée on y a fourni un entier. Mais bon, j'avoue que le array(array(... peut dérouter au début.
Notre forum fournit à n'importe quel client XML-RPC la liste des "n" derniers messages.
Je détaillerais moins pour cette fonction, elle a quasiment le même fonctionnement que la première. On lui fournit un entier (l'id du message) et elle renvoie un tableau de chaînes contenant les infos relatives à ce message.
function i_getContenuMessage($m) {
global $xmlrpcArray;
$rv = new xmlrpcval(array(), $xmlrpcArray);
include("include/db_connect.php");
$id_o = $m->getParam(0);
$id = $id_o->scalarval();
$sql = "SELECT id, auteur, date, titre, texte from forum WHERE ID = $id";
$mysql_result = mysql_query($sql);
if ($row = mysql_fetch_row($mysql_result)) {
$rv->addScalar($row[0], "int");
$rv->addScalar($row[1], "string");
$rv->addScalar($row[2], "string");
$rv->addScalar($row[3], "string");
$rv->addScalar($row[4], "string");
}
return new xmlrpcresp($rv);
}On voit bien l'illustration de l'adage "There are many ways make it work". Au lieu de déclarer un tableau et de l'affecter dans mon if, je déclare une simple variable xmlrpcval et j'utilise la méthode addScalar(). C'est la méthode qui doit être utilisée pour renvoyer un élément de type string ou integer. En fait, c'est que je rajoute au tableau de sortie (response) un élément simple (scalaire). Lorsque j'ajoutais un tableau (dans ~getListeMessages~), je le rajoutais par addArray.
Puis on retourne simplement la valeur $rv, qui a été initialisée en tant que tableau (xmlrpcArray).
$i_getContenuMessage_sig = array(array($xmlrpcArray, $xmlrpcInt));
$i_getContenuMessage_doc = "Retourne le contenu du message 'n'";Des questions ? non ? Ben, moi j'en ai une.
Le contenu du message $id est donc transmis sous la forme d'un tableau. On aurait très bien pu le transmettre en tant que struct, par exemple, avec chaque valeur correctement identifiée, c'eût été plus sûr (mais plus compliqué). Tiens, voilà le XML correspondant.
<?xml version="1.0"?>
<methodResponse>
<params>
<param>
<value><struct>
<member><name>id</name>
<value><int>2</int></value>
</member>
<member><name>titre</name>
<value><string>pb. newbie</string></value>
</member>
<member><name>auteur</name>
<value><string>2002-11-01</string></value>
</member>
<member><name>texte</name>
<value><string>Salut, je suis newbie sur le forum et
j'aimerais bien savoir comment faire pour poster dessus via un client
REBOL</string></value>
</member>
</struct></value>
</param>
</params>
</methodResponse>Un bon exercice serait de construire la fonction getContenuMessageStruct, qui renverrait l'équivalent de cet objet xmlrpcresponse.
J'ai la soluce. J'te la donne contre un gâteau au chocolat. :o)
Cette fonction est d'un type différent. Elle reçoit un set de données (auteur, titre, texte) et les intègre dans la base de données. Pour simplifier le traitement, les arguments seront passés dans un simple tableau. Dans l'ordre : auteur, titre, texte.
Et parce qu'il faut bien une valeur de retour, on renverra "OK" si la fonction s'est bien déroulée et le message d'erreur PHP/MySQL en cas de bug. Je sais d'expérience que ce genre de "flag" peut être très utile.
function i_putMessage($m) {
include("include/db_connect.php");
$auteur_o = $m->getParam(0);
$titre_o = $m->getParam(1);
$texte_o = $m->getParam(2);
$auteur = addslashes($auteur_o->scalarval());
$titre = addslashes($titre_o->scalarval());
$texte = addslashes($texte_o->scalarval());
$sql = "INSERT INTO forum (auteur, titre, texte, date) values ('$auteur', '$titre', '$texte', '" . date("Y-m-d") . "')";
mysql_query($sql);
if (mysql_error()) {
$rt = "Erreur ! " . mysql_error() . "### " . $sql;
} else {
$rt = "OK";
}
return new xmlrpcresp(new xmlrpcval($rt));
}Les appels à la fonction addslashes évitent les bugs lors de l'import des chaines de caractères. Par exemple, si le titre d'un post contient un caractère comme " (guillemet) ou ' (apostrophe), il va mettre le bronx dans le parsing du document XML. Je te dis ça, parce que quand je signe No', ça fait souvent bugguer les programmes de forum :o). La chaîne "il fait "beau" aujourd'hui" devient "il fait \"beau\" aujourd'hui".
La valeur retournée est une chaîne. Typiquement, dans les traitements, on renvoie un entier (1 si OK, sinon, un code d'erreur). A voir, en fonction de la confiance relative qu'on a envers ses propres scripts. Dans le cas d'un webservice "professionnel", c'est plutôt recommandé de pouvoir monitorer les scripts du serveur proprement.
$i_putMessage_sig = array(array($xmlrpcString,$xmlrpcString,$xmlrpcString,$xmlrpcString));
$i_putMessage_doc = "Insère le message en prenant les paramètres. Valeur de retour sans objet";Je dois avouer que je suis pas très fier de la signature de cette fonction. Mais honnêtement, c'est pas très clair dans la doc, alors, comme celle-ci fonctionne, je vais pas me plaindre. Je l'ai un peu trouvée par tâtonnement, et j'ai toujours pas pigé comment ça fonctionne. D'ailleurs si quelqu'un voulait bien m'aider, je prends.
hé beh on a tout.
Ça marche - tu peux vérifier. Vide ta base et envoie un message.
>> remote/exec [forum.putMessage "Modérateur" "Bienvenue !" {et ben voil
à. Le forum XML-RPC fonctionne !}]
== "OK"
>> remote/exec [forum.getListeMessages 20]
== [[1 "Bienvenue !"]]
>> remote/exec [forum.getContenuMessage 1]
== [1 "Modérateur" "2002-11-04" "Bienvenue !" "et ben voilà. Le forum XM
L-RPC fonctionne !"]Que demande le peuple ?
J'en parlais tout à l'heure, mais en fait, ça m'a donné des idées. Comment "améliorer" ce webservice ? Quelles fonctionnalités pourrait-on rajouter pour le faire évoluer ?
Dans cette partie du tutorial, nous avons donc beaucoup codé en PHP, un peu manipulé REBOL (pour tester notre code).
Mais on va pas s'arrêter là. C'est un tutorial made in REBOLFRANCE, alors... La prochaine étape sera donc de fabriquer un client en REBOL capable d'afficher les informations renvoyées par les deux méthodes "GET" et d'envoyer un formulaire en utilisant la méthode "PUT".
Remarque, je dis REBOL, mais j'aurais pu dire JAVA, Python, PHP, ASP, Visual Basic, ou même... FLASH !!! (et oui, il existe une extension XML-RPC pour FLASH. D'ailleurs tous ceux qui se sont penchés sur la conception d'animations FLASH dynamiques savent que la tactique qui gagne s'appelle XML).
| Motherland | Nathalie Merchant. |
| Songs to no one | Jeff Buckley & Gary Lucas. |
| Audioslave | Audioslave |
Programmation en PHP4 - Leon Atkinson, Campus Presse.
un livre très complet sur le PHP. Tout y est, de la description des structures de données jusqu'à la liste exhaustive des fonctions. Je m'y réfère toujours en cas de doute.
Un package hyper simple d'utilisation et parfaitement stable, un forum sur lequel je traîne souvent mes guêtres, une équipe de développement très sympa.
L'éditeur de texte sur lequel a été tapée cette documentation. Y'a tout dedans...
http://brunobord.free.fr/ressources.php
J'ai récemment ouvert un hébergement sur free. Il est relativement vide pour le moment, mais tu pourras y trouver les sources PHP du module XML-RPC. Prochainement, on y verra aussi les sources du forum en REBOL. Plus tard, j'y collerais un rebsite, mais restons calmes, et faisons les choses dans l'ordre.
Un profond remerciement est adressé à Andreas Bolka pour avoir conçu et réalisé l'extension rebXR. Je tiens aussi à le remercier pour sa disponibilité.
Je remercie également Jason Cunliffe qui m'a un peu guidé dans mes recherches sur XML-RPC.
Je remercie Robert. M. Münch, pour son soutien sur son superbe outil : make-doc-pro.
Je remercie Chris Langreiter pour son soutien.
Je remercie encore Olivier Auverlot de m'avoir ouvert ses pages sur le site rebolfrance.net.
Un grand merci à Sébastien Rénie qui m'a fait découvrir le langage REBOL.
J'adresse un profond remerciement à Thierry Murail pour son infinie patience (hum !) et tout ce qu'il fait pour la communauté PHP.
Document formatter copyright Robert M. Münch. Tous Droits Réservés.
XHTML 1.0 Transitional formaté par Make-Doc-Pro Version : 1.0.5 le 3-Dec-2002 à 8:23:57