I. Préliminaires▲
I-A. Introduction▲
SOAP est un protocole de communication. Il détermine le format des trames qui vont être envoyées du client au serveur (et vice-versa) afin que chacun puisse reconstituer les données envoyées.
gSoap, contrairement à ce qui est souvent écrit, n'est pas une bibliothèque. En réalité, c'est un compilateur: il prend un fichier (.h ou .wsdl) et crée les fichiers sources (.c/.cpp et .h) qui implémentent un serveur et/ou un client.
La partie émergée de gSoap, celle dont traite ce tutoriel, se compose de deux exécutables : soapcpp2.exe et wsdl2h.exe.
Il y a donc deux façons d'utiliser gSoap :
- À partir d'un en-tête c/c++ (fichier .h) ;
- À partir d'un fichier wsdl.
L'avantage du 1. c'est que c'est plus simple: il n'est pas nécessaire de connaitre la norme wsdl, et pour un développeur c/c++ c'est plus « intuitif » d'écrire un en-tête c/c++ qu'un wsdl.
L'avantage du 2. c'est qu'il existe des outils pour vérifier la validité du wsdl, créer un xsd correspondant, etc. le wsdl étant du xml, c'est également plus facile pour un développeur qui n'aime/connais pas le c/c++.
Dans les deux cas, le résultat est le même: un ensemble de fichiers .h et .c/.cpp qui, une fois compilés, donnent un serveur et/ou un client SOAP.
La nature du serveur (stand alone ou « attaché » à un serveur existant, version, etc.) dépend des paramètres de compilation passés aux compilateurs wsdl2h et soapcpp2.
Dans ce tutoriel, je ne traiterai que du serveur de type « stand alone », et je n'y aborde pas la problématique des proxies.
I-B. Installation▲
Tout d'abord il vous faut télécharger gSoap. Ce tutoriel est écrit et testé avec la version 2.7.14. Pour le télécharger, rendez-vous sur la page de téléchargement de gSoap sur sourceforgepage de téléchargement de gSoap sur sourceforge et choisissez la version que vous souhaitez.
Dézippez-le dans le dossier de votre choix, et c'est tout.
I-C. À propos de soapcpp2.exe▲
soapcpp2 est l'exécutable qui génère les fichiers sources d'un client et/ou d'un serveur SOAP à partir d'un en-tête (fichier .h).
Exemple d'utilisation :
soapcpp2.exe mon_client.h
Liste des principaux paramètres d'exécution
- -C ne génère que le code pour le client.
- -S ne génère que le code pour le serveur.
- -L ne génère pas les fichiers xxxLib (ces fichiers servent à créer notre client/serveur sous forme de bibliothèque).
- -c génère du code C (sinon, ça génère du C++).
- -d path spécifie l'endroit où sont générés les fichiers. Si ce paramètre n'est pas spécifié, les fichiers sont générés dans le répertoire courant.
I-D. À propos de wsdl2h.exe▲
WSDL est un protocole qui permet de définir un web service (la norme wsdl est décrite ici). Écrire un fichier wsdl correspond à définir précisément ce que va faire ce web service.
Le wsdl est une forme particulière de XML.
wsdl2h est l'exécutable qui génère l'en-tête d'un client et/ou d'un serveur SOAP à partir d'un fichier wsdl.
Exemple d'utilisation :
wsdl2h.exe my_cs.wsdl
Liste des principaux paramètres d'exécution
- -c génère du code C (sinon, ça génère du C++).
- -I path va chercher le(s) fichier(s) wsdl dans path.
- -o file spécifie le nom de l'en-tête généré.
- -s ne génère pas le code nécessaire à l'utilisation de la stl.
II. Tutoriel Windows▲
Afin de comprendre comment fonctionne gSoap, nous allons créer un serveur SOAP qui résout une opération simple, ainsi que son client. Cette opération simple, que j'appellerai op1, prend deux paramètres entiers a et b et effectue l'opération suivante : r = 2a + b. r étant le résultat, c'est-à-dire la valeur qui sera renvoyée au client par le serveur.
II-A. À partir d'un en-tête▲
Nous commencerons par écrire l'en-tête, qui définit juste la signature de notre opération. Nous pouvons l'appeler mon_cs.h (mon_client serveur) :
// mon_cs.h
int
op1( int
a, int
b, int
*
r );
Attention, les fonctions gSoap ne peuvent retourner qu'un entier, qui est un code d'erreur. Le résultat doit donc être récupéré par un paramètre passé sous forme de pointeur ou de référence. Ici, le résultat est le troisième paramètre (r).
Ensuite, nous allons générer le client et le serveur. Pour ce faire, je conseille de procéder comme suit :
1. Copier l'exécutable (soapcpp2.exe) dans le même répertoire que votre en-tête ;
2. Créer un fichier batch (par exemple: mon_cs.bat), au même endroit, et d'y écrire la ligne de commande qui génère le client et/ou le serveur.
Voici le batch le plus simple que je vous propose :
soapcpp2.exe -L mon_cs.h
pause
Le -L c'est pour que gSoap ne génère pas les fichiers pour utiliser notre client/serveur comme une bibliothèque. Ce n'est pas nécessaire, mais cela évite des fichiers que nous n'utiliserons pas ici.
la ligne « pause » est importante: elle sert à ce que la fenêtre cmd ne se ferme pas immédiatement afin que nous puissions vérifier que la génération s'est bien déroulée, ou de voir, en cas de problème, les messages d'erreurs.
Si la génération s'est bien déroulée, les fichiers suivants ont été créés :
- soapC.cpp ;
- soapClient.cpp ;
- soapServer.cpp ;
- soapH.h ;
- soapStub.h.
Ces fichiers devront être compilés avec le reste de votre code (sauf soapClient.cpp pour le serveur et soapServer.cpp pour le client).
Il vous faudra également ajouter les fichiers stdsoap2.h et stdsoap2.cpp à votre projet. Ces deux fichiers se trouvent à la racine du dossier gSoap que vous avez téléchargé. Je vous conseille de copier ces deux fichiers dans le répertoire de votre projet.
II-A-1. Le serveur▲
Un serveur gSoap est un web service. Il s'agit donc d'une boucle qui attend des trames sur un port donné. Et lorsqu'une trame est reçue, il la lit et regarde ce qu'il peut en faire. Il y a donc deux parties dans un serveur :
1. La boucle principale ;
2. La définition des opérations.
La boucle principale ressemblera à ceci :
SOAP_SOCKET s; // slave socket
struct
soap soap; // structure soap
for
( ; ; )
{
s =
soap_accept( &
soap ); // reception d'une requete
std::
cout <<
"connexion au client ok: slave socket = "
<<
s <<
std::
endl;
if
( !
soap_valid_socket( s ) )
{
std::
cout <<
"erreur: problème de socket"
<<
std::
endl <<
"appuyez sur une touche pour quitter"
;
return
1
;
}
// execution de la requete. soap_serve() va appeler la fonction correspondante à la requete.
soap_serve( &
soap );
// finalisation
soap_end( &
soap );
}
Ensuite, les opérations seront de simples fonctions écrites en C++. Par exemple, notre op1() sera écrite comme suit :
int
op1( struct
soap *
soap, int
a, int
b, int
*
r )
{
std::
cout <<
"op1 appelé avec: a="
<<
a <<
", b="
<<
b <<
std::
endl;
*
r =
2
*
a +
b;
return
SOAP_OK;
}
II-A-2. Le client▲
Un client SOAP est un programme qui envoie des requêtes à un serveur dont il connait l'adresse et le port d'accès.
Dans ce tutoriel, le client est, à l'image du serveur, minimal et ne sait faire qu'une opération (celle que j'appelle op1).
Le client est donc très simple. La seule ligne de code représentative est la suivante :
soap_call_op1( &
soap, server, ""
, a, b, &
result );
soap_call_op1 est une fonction générée par gSoap à partir de notre en-tête. Elle va récupérer les paramètres, créer la ou les trames correspondantes selon le protocole SOAP, et les envoyer.
Ce client s'utilise ainsi :
- ouvrez une invite de commande ;
- allez dans le répertoire où est généré l'exécutable du client ;
- entrez la ligne de commande: client a b (ou a et b sont des entiers).
II-A-3. Les sources▲
Code source du client/serveur (avec fichiers projet et solution pour visual 2008 express ed.) :
sources
Ce dossier contient l'en-tête à partir duquel gSoap va générer le client et le serveur, ainsi que le petit script qui lance cette génération en utilisant l'exécutable soapcpp2.
Cet exemple est minimal et crée un client/serveur qui fonctionne en local.
II-B. À partir d'un fichier wsdl▲
Nous commencerons donc par écrire le fichier wsdl à partir duquel tout le reste va être généré.
La norme WSDL est assez complexe, car elle permet de définir entièrement un web service, aussi je ne vais pas m'y attarder ici et vous donner un exemple minimal. Pour approfondir, vous pouvez aller sur le site de W3C où la norme est décrite exhaustivement (ici).
II-B-1. Le fichier WSDL▲
II-B-1-a. Définitions▲
Tout d'abord, il faut préciser des définitions qui vont servir à la génération du client/server. Pour ma part j'utilise toujours la même, à savoir :
<definitions
name
=
"mon_cs"
xmlns
:
SOAP-ENV
=
"http://schemas.xmlsoap.org/soap/envelope/"
xmlns
:
SOAP-ENC
=
"http://schemas.xmlsoap.org/soap/encoding/"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns
:
xsd
=
"http://www.w3.org/2001/XMLSchema"
xmlns
:
ns
=
"urn:my_cs"
xmlns
:
SOAP
=
"http://schemas.xmlsoap.org/wsdl/soap/"
xmlns
:
MIME
=
"http://schemas.xmlsoap.org/wsdl/mime/"
xmlns
:
DIME
=
"http://schemas.xmlsoap.org/ws/2002/04/dime/wsdl/"
xmlns
:
WSDL
=
"http://schemas.xmlsoap.org/wsdl/"
xmlns
=
"http://schemas.xmlsoap.org/wsdl/"
>
Vous avez juste à remplacer « mon_cs » par le nom de votre propre client/serveur.
II-B-1-b. Types▲
Ensuite viennent les types dont vous aurez besoin. Par exemple, si vous utilisez une structure complexe pour envoyer ou recevoir des données, il faut la déclarer ici. Dans l'exemple que je donne ici, et dont vous trouverez le code source en fin de paragraphe, je n'utilise aucun type particulier, je n'ai donc rien déclaré ici. Pour plus de précision, je vous renvoie àla définition de la norme.
II-B-1-c. Messages▲
Ensuite viennent les définitions des messages. Un message se définit par une requête et une réponse. Dans la requête, il faut spécifier les paramètres (avec éventuellement des types définis dans la section précédente). Idem pour la réponse.
Par exemple, pour notre opération op1, qui prend deux entiers en entrée et qui renvoie un entier, ça donnera ceci :
<message
name
=
"op1Request"
>
<part
name
=
"a"
type
=
"xsd:int"
/>
<part
name
=
"b"
type
=
"xsd:int"
/>
</message>
<message
name
=
"op1Response"
>
<part
name
=
"r"
type
=
"xsd:int"
/>
</message>
II-B-1-d. Port types▲
Après viennent les définitions des « port types ». Il s'agit de déclaration d'opérations virtuelle. C'est, en quelque sorte, une prédéclaration des opérations. Pour notre opération op1, nous aurons ceci :
<portType
name
=
"my_csPortType"
>
<operation
name
=
"op1"
>
<documentation>
2a+b</documentation>
<input
message
=
"tns:op1Request"
/>
<output
message
=
"tns:op1Response"
/>
</operation>
</portType>
À noter que la balise <documentation> est optionnelle.
II-B-1-e. Bindings▲
Maintenant viennent les bindings. Les bindings font le lien entre les port types et les opérations.
Pour op1, nous aurons :
<binding
name
=
"my_cs"
type
=
"tns:my_csPortType"
>
<
SOAP
:
binding
style
=
"rpc"
transport
=
"http://schemas.xmlsoap.org/soap/http"
/>
<operation
name
=
"op1"
>
<
SOAP
:
operation
style
=
"rpc"
soapAction
=
""
/>
<input>
<
SOAP
:
body
use
=
"encoded"
namespace
=
"urn:my_cs"
encodingStyle
=
"http://schemas.xmlsoap.org/soap/encoding/"
/>
</input>
<output>
<
SOAP
:
body
use
=
"encoded"
namespace
=
"urn:my_cs"
encodingStyle
=
"http://schemas.xmlsoap.org/soap/encoding/"
/>
</output>
</operation>
</binding>
II-B-1-f. Services▲
Et enfin, on définit le service, dans lequel nous spécifions notamment l'adresse du serveur.
<service
name
=
"my_cs"
>
<documentation>
test of gSoap</documentation>
<port
name
=
"my_cs"
binding
=
"tns:my_cs"
>
<
SOAP
:
address
location
=
"http://127.0.0.1"
/>
</port>
</service>
II-B-2. Le serveur▲
Un serveur gSoap est un web service. Il s'agit donc d'une boucle qui attend des trames sur un port donné. Et lorsqu'une trame est reçue, il la lit et regarde ce qu'il peut en faire. Il y a donc 2 parties dans un serveur :
1. La boucle principale ;
2. La définition des opérations.
La boucle principale ressemblera à ceci :
SOAP_SOCKET s; // slave socket
struct
soap soap; // structure soap
for
( ; ; )
{
s =
soap_accept( &
soap ); // reception d'une requete
std::
cout <<
"connection au client ok: slave socket = "
<<
s <<
std::
endl;
if
( !
soap_valid_socket( s ) )
{
std::
cout <<
"erreur: problème de socket"
<<
std::
endl <<
"appuyez sur une touche pour quitter"
;
return
1
;
}
// execution de la requete. soap_serve() va appeler la fonction correspondante à la requete.
soap_serve( &
soap );
// finalisation
soap_end( &
soap );
}
Ensuite, les opérations seront de simples fonctions écrites en C++. Par exemple, notre op1() sera écrite comme suit :
int
ns1__op1( struct
soap *
soap, int
a, int
b, int
&
r )
{
std::
cout <<
"op1 appelé avec: a="
<<
a <<
", b="
<<
b <<
std::
endl;
r =
2
*
a +
b;
return
SOAP_OK;
}
II-B-3. Le client▲
Un client SOAP est un programme qui envoie des requêtes à un serveur dont il connait l'adresse et le port d'accès.
Dans ce tutoriel, le client est, à l'image du serveur, minimal et ne sait faire qu'une opération (celle que j'appelle op1).
Le client est donc très simple. La seule ligne de code représentative est la suivante :
soap_call_op1( &
soap, server, ""
, a, b, result );
soap_call_op1 est une fonction générée par gSoap à partir de notre en-tête. Elle va récupérer les paramètres, créer la ou les trames correspondantes selon le protocole SOAP, et les envoyer.
Ce client s'utilise ainsi :
- ouvrez une invite de commande ;
- allez dans le répertoire où est généré l'exécutable du client ;
- entrez la ligne de commande: client a b (ou a et b sont des entiers).
II-B-4. Les sources▲
Code source du client/serveur (avec fichiers projet et solution pour visual 2008 express ed.):
sources.
Ce dossier contient les fichiers sources du client et du serveur, ainsi que le script qui lance la génération du code. Cette génération se fait en deux phases :
1. Génération de l'en-tête à partir du fichier wsdl ;
2. Génération du client et du serveur à partir de ces en-têtes.
Cet exemple est minimal et crée un client/serveur qui fonctionne en local.
II-C. Un mot sur les tests▲
Pour tester votre client et/ou votre serveur, je conseille de passer par un logiciel externe. En effet, si l'on se contente de tester notre propre client avec notre propre serveur, et vice-versa, on n'est nullement assuré que notre serveur fonctionnera avec d'autres clients SOAP et respectivement, que notre client fonctionnera avec d'autres serveurs SOAP.
Pour ma part, j'utilise soapUI, qui est gratuit et que vous pourrez trouver ici.
III. Notes pour Linux▲
gSoap fonctionne exactement de la même façon sous Windows et Linux.
Vous pouvez donc lire le tuto Windows et récupérer ses sources, ce sera pareil, sauf les petits batches de génération qu'il faudra traduire dans votre shell préféré.