|
Services Web Delphi - John COLIBRI.
|
- résumé : Principe et fonctionnement des services Web. construction d'un
Serveur et de Clients de services Web.
- mots clé : Service Web - Web Services - CGI, ISAPI, Web App Debugger -
iInvokable
- logiciel utilisé : Windows XP personnel, Delphi 2006
- matériel utilisé : Pentium 2.800 Mhz, 512 Meg de mémoire, 250 Giga disque
dur
- champ d'application : Delphi 6, Delphi 7, Delphi 2006, Turbo Delphi,
Delphi 2007 Win32 et .Net
- niveau : développeur Delphi
- plan :
1 - Principe des Web Services
Le but fondamental d'un Service Web est de récupérer sur un poste le résultat
d'un calcul effectué sur un autre poste.
Voici quelques exemples simples:
- consultation de la météo
- consultation et réservation de trains / avions / hôtels
- récupération de cotations boursières et calculs sur ces valeurs
Pour des documents plus lourds:
- traduction de textes (BabelFish)
- téléchargement de carte en fournissant des coordonnées (longitude,
lattitude) ou une adresse postale (Mappoint)
- consultation de produits Amazon
Outre le dialogue en utilisant ou non un explorateur internet, les services web
permettent aussi d'effectuer des traitement distants entre deux application
("Business to Business, ou encore B2B). Par exemple:
- un libraire (un point de vente quelconque, une usine) vend (consomme) des
produits. Lorsque le stock attend un certain seuil, il faut
réapprovisionner. La façon traditionnelle est de téléphoner ou d'envoyer un
fax. Un peu mieux, utiliser Internet. Et, naturellement, sans aucune
intervention humaine, un Service Web. Par exemple le logiciel de
déstockage contient les seuils de réapprovisionnement, et dès que celui ci
est franchi, une commande est envoyée automatiquement
- tout système de surveillance (un laminoir)
2 - Construction d'un Service Web
2.1 - Fonctionnement des Web Services
Nous allons illustrer ceci sur un exemple très simple de conversion monétaire:
conversion de Dollars en Euros (et réciproquement éventuellement).
Pour effectuer ce calcul:
- l'utilisateur lance une application de conversion qui contient l'adresse du
Service Web de conversion, tape une valeur en Dollars et clique sur un
bouton
- le Service Web reçoit la demande, calcule le résultat, et renvoit la
réponse
- l'utilisateur voit le résulat de la conversion
Ainsi présenté, ce type de traitement est clairement du type Client /
Serveur. Voici le déroulement du dialogue
|
le Serveur est lancé. Il contient
- un exécutable (.EXE ou .DLL) contenant les FUNCTIONs et des
PROCEDUREs que des Clients peuvent invoquer. Dans notre cas une
fonction qui convertit des Dollars en Euros:
|
Function f_euro(p_dollar: Integer): Integer
|
- un Serveur TCP/IP capable de recevoir une demande Client, lancer
l'exécutable, et renvoyer la réponse
Voici notre Serveur à l'écoute des Clients:
|
|
un client lance un .EXE qui contient un Service Web Client. Il demande
la conversion d'un montant en Dollar:
|
|
le Serveur TCP/IP reçoit la requête, et charge l'exécutable qui fait la
conversion :
|
|
the TCP/IP Server renvoie la réponse:
|
Vous aurez remarqué que ce type d'échange est très similaire aux
échanges CGI ou ISAPI, à la différence que le Client
n'a pas besoin d'être un exlorateur Web, et la requête n'est pas provoquée par
un clic sur un bouton <INPUT situé dans une balise <FORM d'une page .HMTL.
Le Client est contenu dans une application Windows normale (un .EXE).
2.2 - Les Composants Nécesaires
Un service Web a besoin pour fonctionner
- d'un réseau TCP/IP
- d'un serveur HTTP capable de comprendre le protocole des Services Web
- d'une extension du serveur, CGI ou ISAPI, qui contient le traitement
effectué par le service
- de clients qui vont interroger le Service pour obtenir des résultats
2.3 - Le Réseau TCP/IP
Celui-ci est disponible par défaut sur la plupart des OS, dont Windows XP et
similaires
2.4 - Le protocole SOAP
2.4.1 - SOAP= HTTP + XML
L'exécution depuis un poste d'un traitement sur un autre fait partie des
techniques RPC (Remote Procedure Call). Parmi les moyens pour mettre en
oeuvre des RPC, citons
- CORBA (Common Object Request Broker Architecture, qui est très
complet, mais très vaste et très complexe
- DCOM (Distributed Component Object Model) qui est l'extension
"distribuée" de COM. Solution purement Microsoft, et peu à peu tombé en
désuétude à cause de sa difficulté de mise en oeuvre
- et finalement les Services Web utilisant le protocole SOAP (Simple
Object Access Protocol
SOAP spécifie simplement comment se déroulera le dialogue entre le Serveur et
les Clients. Devant le refus de DCOM, Microsoft a sous-traité à Don BOX la
création d'un nouveau protocole, et le résultat a été SOAP.
SOAP se veut une simplification par rapport à DCOM. Aussi, Don BOX a choisi:
- d'utiliser le réseau TCP/IP
- le protocole HTTP
- un contenu au format .XML
Donc:
- TCP/IP pour son ubiquité
- HTTP car c'est le protocole utilisé par le Web (serveurs et explorateurs
Web). Donc universellement accepté, et utilisant sur les PC le port 80, qui
est pratiquement toujours laissé libre par les pare feu (sinon vous n'avez
pas accès au Web, ce qui de nos jours est terrible)
- pour poser la question, une simple requête au format CGI aurait en général
suffi. Mais la réponse peut être complex (une Table complète, par exemple),
il fallait trouver un format adéquat. XML (eXtended Markup Language) a
été retenu.
2.4.2 - Le Format SOAP
Rassurez-vous, vous n'aurez ni à construire les requêtes au format HTTP, ni a
gérer ce protocole, ni à écrire ou lire du .XML. Les couches Delphi se
chargeront de tout.
Néanmoins présentons rapidement le contenu des données échangées:
|
lorsque le Client envoie sa requête, le Client envois au Serveur un
texte composé d'une en-tête HTTP et d'un contenu avec la fonction appelée
et ses paramètres au format .XML:
POST /p_server_euro.co_euro/soap/i_server_euro HTTP/1.1
SOAPAction: "urn:u_i_server_euro-i_server_euro#f_euro"
Content-Type: text/xml
User-Agent: Borland SOAP 1.2
Host: localhost
Content-Length: 525
Connection: Keep-Alive
Cache-Control: no-cache
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
// -- ...ooo... Ed: truncated >
<SOAP-ENV:Body SOAP-ENV:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/">
<NS1:compute_euro xmlns:NS1="urn:u_i_server_euro-i_server_euro">
<p_euro xsi:type="xsd:int">30</p_euro>
</NS1:compute_euro>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
|
où:
- p_server_euro est le nom de notre exécutable (p_server_euro.exe)
- i_server_euro est le nom de l'INTERFACE Delphi qui définit les
méthodes disponibles
- f_euro est la fonction appelée
- 30 est le montant en Dollars
|
|
le Serveur retourne la réponse avec le message suivant:
HTTP/1.1 200 OK
Content-Type: text/xml
Content-Length: 465
Content:
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
// -- ...ooo... Ed: truncated >
<SOAP-ENV:Body SOAP-ENV:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/">
<NS1:f_euroResponse xmlns:NS1="urn:u_i_server_euro-i_server_euro">
<return xsi:type="xsd:int">15</return>
</NS1:f_euroResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
|
où 15 est le résultat en Euros
|
Les messages comportent donc
- l'en-tête HTTP classique
- une "enveloppe" au format .XML qui contient soit la requête soit la réponse
2.5 - WSDL
Nous allons écrire un Serveur de Services Web et un Client de Services
Web.
Si nous souhaitions consulter les livres Amazon ou réserver un train, le
Serveur de Service Web est déjà écrit (par la SNCF). Il nous suffit de créer
le Client Web Service.
Il faut toutefois connaître :
- le nom des méthodes offertes par le service SNCF
- le nombre et le type des paramètres de chaque méthode
Ces informations sont disponibles dans un document .WSDL (Web Service
Definition Language, prononcé "ouicedelle"). Voici le .WSDL de notre
requête
<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
// -- ...ooo... Ed: truncated
name="i_server_euroservice"
// -- ...ooo... Ed: truncated >
<message name="f_euro0Request">
<part name="p_euro" type="xs:int"/>
</message>
<message name="f_euro0Response">
<part name="return" type="xs:int"/>
</message>
<message name="compute_euro1Request">
<part name="p_euro" type="xs:int"/>
</message>
<message name="compute_euro1Response"/>
// -- ...ooo... Ed: truncated
|
Vous distinguerez dans ce document (tronqué: il fait environ 50 lignes) les
noms du service, de notre fonction f_euro et le paramètre p_euro et son type
Int. Comme nous n'aurons pas à écrire nous-même ce type de données, nous ne
nous attarderons pas sur son contenu.
Le point important est que, pour écrire un Client Web Service, nous pouvons
interroger le Serveur Web Service pour lui demander quels service il offre.
Une fois ce document récupéré, nous pouvons créer le Client.
Delphi va même plus loin en offrant un Wizard qui à partir du document .WSDL
va générer un UNITé qui importer l'INTERFACE permettant de dialoguer avec
le Serveur. Nous utiliserons ce Wizard d'importation .WSDL ci-dessous.
2.6 - Le Serveur HTTP
Il suffit que le poste serveur dispose d'un Serveur HTTP. Parmi les
possibilités, citons
- sur Windows 95 et Windows 98, PWS (Personal Web Server)
- sur XP Home, aucun serveur n'est fourni par Microsoft. Nous pouvons toujours
acheter er installer IIS (Internet Information Server) ou Appache.
Nous pouvons utiliser des solutions purement Delphi:
- depuis Delphi 6, le WAD (Web Application Debugger)
- divers serveurs construits à partir des composants TCP/IP Indy
- nous avons nous-mêmes utilisé un serveur HTTP
CGI construit à partir de nos sockets, et adapté pour les Services
Web
- pour XP Pro, nous disposons d'une version IIS light
Dans cet article, nous utiliserons les Serveur HTTP de débugging intégré à
Delphi, WAD.
2.7 - Type d'exécutable Service Web
L'exécutable qui contiendra nos méthodes et qui sera lancé par le Serveur
TCP/IP peut avoir trois formats:
- une .DLL ISAPI (Internet Server API) ou NSAPI (NetScape API
- une extension serveur CGI (Common Gateway Interface)
- une application incluant le Serveur HTTP et une extension CGI
Les deux premières solutions nécessitent l'existence d'un Serveur HTTP (IIS,
Appache ou autre), alors que la troisième emploie le Serveur Indy de mise au
point intégré à Delphi, WAD.
Nous utiliserons WAD, qui est fourni avec Delphi
3 - Le Serveur Web Service
3.1 - Construction de l'extension Serveur
3.1.1 - Mise en place en utilisant le Wizard
Voici les étapes pour créer notre service de conversion:
|
démarrez Delphi 2006 Windows
|
|
sélectionnez le Wizzard qui crée une application de Service Web en
sélectionnant "File | New | Other | Delphi Project | Web Services | Soap
Server"
|
|
le Wizard nous propose 3 solutions:
Ces possibilités correspondent:
- une .DLL ISAPI (Internet Server API) ou NSAPI (NetScape API
- une extension serveur CGI (Common Gateway Interface)
- une application incluant le Serveur HTTP et une extension CGI
Comme indiqué ci-dessus, nous utiliserons la troisième solution qui
fonctionne avec le Serveur HTTP Indy fourni avec Delphi et qui en plus
permet de débugger notre Serveur
|
|
sélectionnez "Web App Debugger" (alias WAD)
|
|
la boite d'édition devient accessible
|
|
tapez le nom d'une co-classe. Nous suggérons co_euro_dollar, puis tapez
"ok"
|
|
Delphi nous demande si nous souhaitons créer automatiquement l'INTERFACE
et l'IMPLEMENTATION de noter Service:
|
|
tapez "Yes"
|
|
Delphi nous demande le nom de notre Service
|
|
nous suggérons d'appeler l'INTERFACE, et par conséquent vous pouvez taper
- dans "Service Name, "_server_euro_dollar" (le I sera ajouté
automatiquement)
- dans "Unit Identifier" "u_i_server_euro_dollar"
Supprimez aussi "Generate Comments" pour alléger le texte
Puis cliquez "Ok"
|
|
Delphi génère les fichiers nécessaires au Serveur de Services Web
|
Ces fichiers comprennent:
- le projet (.DPR)
- l'UNITé qui contient l'INTERFACE, appelée actuellement
U_I_SERVER_EURO_DOLLARINTF
- l'UNITé qui contient la CLASSe qui implémentera not INTERFACE, appelée
U_I_SERVER_EURO_DOLLARIMPL
- un tWebModule permettant de réceptionner les requêtes du Client Web
Service, appelé actuellement UNIT2
- une tForm, qui actuellement ne contient aucun composant, mais qui sera
affichée chaque fois que le Serveur est activé
Nous suggérons de renommer les différentes parties de la façon suivante:
|
sélectionnez "Project Manager | PROJECT1.EXE | click droit | Rename" et
tapez P_SERVER_EURO_DOLLAR_WAD
|
|
renommez U_I_SERVER_EURO_DOLLARINTF U_I_SERVER_EURO_DOLLAR
|
|
renommez U_I_SERVER_EURO_DOLLARIMPL U_C_SERVER_EURO_DOLLAR
|
|
renommez UNIT2 U_WM_SERVER_EURO_DOLLAR
|
|
renommez UNIT1 U_F_SERVER_EURO_DOLLAR
|
Crée un répertoire SERVER_WAD, et sélectionnez "File | Save All"
3.1.2 - Définition des méthodes du Service Web
Il faut à présent placer dans l'INTERFACE les méthodes que nous souhaitons
utiliser. Nous allons utiliser une FUNCTION pour passer du dollar à l'euro,
et une PROCEDURE pour la conversion inverse.
Par conséquent:
|
sélectionnez "Project Manager | u_i_server_euro_dollar".
|
|
le code généré par le wizzard contient les importations, une INTERFACE
actuellement vide, et une initialisation que enregistre notre INTERFACE
|
|
dans le code, ajoutez la définition de nos deux méthodes:
unit u_i_server_euro_dollar;
interface
uses InvokeRegistry, Types, XSBuiltIns;
type i_server_euro_dollar=
interface(IInvokable)
['{54C806EE-35DD-4F1C-8530-4B31F1DBFE3A}']
function f_dollar_to_euro(p_dollar: Double): Double; StdCall;
procedure euro_to_dollar(p_euro: Double;
var pv_dollar: Double); StdCall;
end; // i_server_euro_dollar
implementation
initialization
InvRegistry.RegisterInterface(TypeInfo(i_server_euro_dollar));
end.
|
|
Notez que
- notre INTERFACE i_server_euro_dollar descend de iInvokable. C'est ce qui
permettra de provoquer un appel depuis le Client Web Service
- les méthodes DOIVENT avoir l'attribut STDCALL pour pouvoir être invoquées
- pour pouvoir retrouver cette INTERFACE parmi d'autres qui seraient
éventuellement disponibles sur le même Serveur, Delphi a placé
automatiquement un GUID (Globally Unique Identifier). Nous pouvons
changer ce numéro tant que nous le souhaitons en tapant Shift-Ctrl-G. La
valeur de cet identifiant unique a pour seul but de pouvoir identifier notre
INTERFACE. Ce GUID ne sera pas directement utilisé par notre code dans la
suite (il ne sera pas invoqué par notre code, ou par le Client). De plus ce
GUID ne correspond pas à un objet COM. C'est uniquement un moyen
d'identification unique.
- InvRegistry.RegisterInterface permet de stocker les informations qui
permettront d'appeler la CLASSe qui implémente notre INTERFACE. Notez
que cet enregistrement est purement temporaire. Rien ne sera stocké à ce
niveau dans la base de registres (Registry). La table de correspondance
entre le nom "i_server_euro_dollar" et l'INTERFACE i_server_euro_dollar
est créée lorsque notre CGI Serveur est chargé
3.1.3 - L'implémentation de notre INTERFACE
Il nous faut à présent implémenter les méthodes que nous avons proposé dans
notre INTERFACE.
Par conséquent:
|
copiez les deux méthodes que nous avons placées dans l'INTERFACE dans le
presse-papier
|
|
sélectionnez "Project Manager | u_c_server_euro_dollar".
|
|
le code généré par le wizzard contient les importations, une CLASS
actuellement vide et une intialization que enregistre notre CLASSe
|
|
modifiez le nom de l'UNITé qui contient notre INTERFACE en
U_I_SERVER_EURO_DOLLAR
|
|
collez les deux définitions de méthode dans la CLASSe
|
|
tapez Shift-Ctrl-C pour générer les implémentations de nos deux méthodes
|
|
dans ces méthodes, tapez les calculs de conversion
Le résultat est:
unit u_c_server_euro_dollar;
interface
uses InvokeRegistry, Types, XSBuiltIns
, u_i_server_euro_dollar;
type c_server_euro_dollar=
class(TInvokableClass, i_server_euro_dollar)
public
function f_dollar_to_euro(p_dollar: Double): Double; StdCall;
procedure euro_to_dollar(p_euro: Double;
var pv_dollar: Double); StdCall;
end; // c_server_euro_dollar
implementation
// -- c_server_euro_dollar
function c_server_euro_dollar.f_dollar_to_euro(p_dollar: Double): Double;
begin
Result:= p_dollar / 1.51;
end; // f_dollar_to_euro
procedure c_server_euro_dollar.euro_to_dollar(p_euro: Double;
var pv_dollar: Double);
begin
pv_dollar:= p_euro* 1.51;
end; // euro_to_dollar
initialization
InvRegistry.RegisterInvokableClass(c_server_euro_dollar);
end.
|
|
Notez que:
- notre CLASSe doit descendre de tInvokableClass, et de notre INTERFACE
i_server_euro_dollar
- l'initialisation de notre UNITé appelle
InvRegistry.RegisterInvokableClass qui stocke dans notre base de
correspondance notre CLASSe
3.1.4 - Le WebModule
Nous n'avons pas besoin de modifier ce module, mais nous allons le présenter
rapidement.
Le code est le suivant
unit u_wm_server_euro_dollar;
interface
uses SysUtils, Classes, HTTPApp, InvokeRegistry, WSDLIntf, TypInfo,
WebServExp, WSDLBind, XMLSchema, WSDLPub, SOAPPasInv, SOAPHTTPPasInv,
SOAPHTTPDisp, WebBrokerSOAP;
type TWebModule1=
class(TWebModule)
HTTPSoapDispatcher1: THTTPSoapDispatcher;
HTTPSoapPascalInvoker1: THTTPSoapPascalInvoker;
WSDLHTMLPublish1: TWSDLHTMLPublish;
procedure WebModule1DefaultHandlerAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
private
public
end; // TWebModule2
var WebModule1: TWebModule1;
implementation
uses WebReq;
{$R *.dfm}
procedure TWebModule1.WebModule1DefaultHandlerAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
WSDLHTMLPublish1.ServiceInfo(Sender, Request, Response, Handled);
end; // WebModule1DefaultHandlerAction
initialization
WebRequestHandler.WebModuleClass := TWebModule1;
end.
|
et:
En effet, le WebModule contient aussi une procédure
WebModule1DefaultHandlerAction. Cette événement correspond à un ActionItem
créé automatiquement par le Wizard. Nous pouvons le voir de la façon suivante:
L'action qui provoque les calculs (et pas la fourniture du fichier .WSDL) est
implicite (pas affichée dans l'éditeur d'actions).
3.1.5 - La fenêtre principale
Comme nous avons créé une application WAD, elle a une fenêtre (ce ne serait pas
le cas pour les ISAPI ou CGI purs).
Nous n'avons pas besoin d'y toucher. En général nous la colorions un peu pour
pouvoir la visualiser plus facilement, et nous la positionnons à droite de
l'écran (création de tForm.OnCreate et initialisation de Left et Top) pour
éviter que Delphi ne positionne la fenêtre de façon plutôt aléatoire.
3.2 - Compilation de notre Serveur Web
3.2.1 - Indy 9
Le Serveur HTTP intégré WAD est construit à partir du Serveur HTTP Indy.
Pour Delphi 2006, il s'avère que ce serveur WAD a été compilé avec Indy 9.
Lors de l'installation de Delphi 2006, il nous a été demandé de choisir entre
Indy 9 et Indy 10. Si vous avez sélectionné Indy 9, vous pouvez sauter ce
paragraphe.
Si vous avez sélectionné Indy 10, il faut permettre au compilateur de
retrouver les .DCU correspondant à Indy 9. Pour cela:
|
sélectionnez C:\Program Files\Borland\BDS\4.0\lib\Indy9
et collez cette adresse dans le press papier
|
|
sélectionnez "Project | Options | Directories | Search Path" et collez-y ce
chemin
|
3.2.2 - Compilation du Serveur
Nous compilons notre projet
Notez que:
3.3 - Création du fichier WSDL
3.3.1 - Pourquoi créer le fichier .WSDL
Normalement, pour créer le Client, nous n'avons pas besoin de créer un fichier
physique correspondant: nous pouvons interroger le Serveur Web Service pour
lui demander ces données. Notre version de Delphi 2006 ne permet cependant pas
de récupérer ces données par une URL. En revanche l'importation depuis un
fichier sur disque fonctionne.
Enregistrement de ServerInfo
Le Web App Debugger va nous présenter une liste des CGI disponibles. En
fait, il s'agit d'un service Web qui nous fournit la liste de NOS services web
(ce service interroge simplement la base de registres !).
Si vous venez d'installer Delphi 2006 (ou que vous n'avez jamais créé de
Services Web), SERVERINFO.EXE n'est pas enregistré.
Pour l'enregistrer la première fois:
|
sélectionnez C:\Program Files\Borland\BDS\4.0\Bin
et lancez SERVERINFO.EXE
|
Notez que:
- Cette opération n'aura plus besoin d'être effectuée
- à titre anectdotique, SERVERINFO figurait bien dans la liste des WAD de la
base de registre (image avant la précédente)
3.3.2 - Lancement de notre Serveur Web Service
Lançons à présent notre CGI:
Sur la dernière figure
- trois fenêtre sont à l'écran:
- la fenêtre d'information sur les Services Web disponibles actuellement,
soit 2 services
- la fenêtre de ServerInfo (A)
- la fenêtre de notre service en jaune (B)
- sur la fenêtre d'information, nous avons une partie pour chaque service (1
et 2).
- prenons notre service: en (3) il y a un hyper-lien qui permet de visualiser
le fichier WSDL
3.3.3 - Consultation et Sauvegarde du .WSDL
Pour visualiser le .WSDL:
|
cliquez sur le lien (3)
|
|
le texte du .WSDL est affiché dans une fenêtre Web:
Sans rentrer dans le détail des données .WSDL, vous pouvez reconnaître des
information sur les méthodes et les paramètres de ce qui est offert par
notre service
|
Pour sauvegarder les données dans un fichier
|
sélectionnez "Fichier | Enregistre Sous" et placez le fichier dans le même
répertoire que les autres fichiers (le nom i_server_euro_dollar.xml est
proposé par défaut et nous le conservons)
|
4 - Création d'un Client Web Service
4.1 - Consommation du Service
4.1.1 - Invocation d'une méthode Serveur
Nous allons à présent créer un Client qui va appeler nos méthodes depuis une
application connectée à notre Serveur par TCP/IP.
Nous pourrions construire manuellement et envoyer la requête SOAP au Serveur,
puis récupérer la réponse et la décortiquer. Cette solution est possible en
utilisant des Sockets TCP/IP. C'est en fait la solution que nous avions
adoptée pour notre conférence SOAP et DataSnap à la
conférence Borland en 2001.
Delphi utilise une technique qui automatise ces échanges sans que nous ayons à
traiter les messages SOAP. Au niveau principe:
Le résultat de cette mécanique est que le Client effectue ses appels de
FUNCTION ou de PROCEDURE "comme si" la CLASSe Serveur avait été
téléchargée sur son PC.
Le Proxy est mis en oeuvre en utilisant un composant tHttpRIO (RIO= Remote
Invocation Object)
4.1.2 - Les composants Delphi
Le composant tHttpRIO a pour objectif de nous retourner une INTERFACE
correspondant à l'INTERFACE sur le Serveur.
Pour cela, il faut que le Client ait la définition Pascal de cette
INTERFACE
Et pour importer la définition de l'INTERFACE, nous utilisons un Wizard qui:
- lit un .WSDL (par son URL ou en lisant un fichier .XML)
- construit l'UNITé contenant:
- l'INTERFACE de notre Service (ainsi que les autres définitions si
notre Service comporte des types ou CLASSes annexes particulières, ce
qui n'est pas le cas pour notre exemple)
- en bonus, une FUNCTION qui retourne automatiquement cette INTERFACE
Il s'avère que nous ne pouvons invoquer le Wizard qu'après avoir créé une
application Client.
Nous allons donc
- créer une application Client
- créer à l'aide du Wizard l'UNITé qui contiendra l'INTERFACE
- compléter, dans la tForm de notre client, quelques appels de notre Service
4.2 - Création du Client
Il s'agit d'une application Delphi des plus classiques:
|
sélectionnez "File | New | Vcl Application Form"
|
|
sauvegardez le projet. Nous suggérons de créer un répertoire CLIENT\ et de
nommer les fichiers U_F_CLIENT_EURO_DOLLAR et P_CLIENT_EURO_DOLLAR
|
|
compilez pour vérifier que tout est en ordre
|
Notez que
- vous pourriez aussi ajouter la nouvelle application au groupe par "Project
Manager | ProjectGroup1 | Add New Project etc
4.3 - Importation de l'INTERFACE par le Wizard
Maintenant nous pouvons utiliser le Wizard qui va transformer le .WSDL en une
CLASSe locale du Client:
|
sélectionnant "File | New | Other | Delphi Project | Web Services | Wsdl
Importer"
et cliquez "Ok"
|
|
le Wsdl Import Wizard est affiché (Ed: tronqué en largeur)

|
|
dans "Location of WSDL File", tapez le chemin et le nom du fichier .XML.
Dans notre cas:
C:\programs\fr\web\web_service_tutorial\server_wad\i_server_euro_dollar.xml
(vous pouvez utiliser l'ellipse ... pour localiser ce fichier
|
|
cliquez sur "Finish"
|
|
l'unité est présentée (Ed: tronqué en largeur):
|
|
cliquez "Finish"
|
|
l'unité est copiée dans notre projet Client, sous le nom de
I_SERVER_EURO_DOLLAR1
|
|
nous suggérons de renommer l'UNITé U_I_CLIENT_EURO_DOLLAR
|
L'unité d'importation comporte (en enlevant les commentaires):
unit u_i_client_euro_dollar1;
interface
uses InvokeRegistry, SOAPHTTPClient, Types, XSBuiltIns;
type i_server_euro_dollar =
interface(IInvokable)
'{091A6317-83B7-FB51-3FD6-4BD47A83325F}']
function f_dollar_to_euro(const p_dollar: Double): Double; stdcall;
procedure euro_to_dollar(const p_euro: Double;
var pv_dollar: Double); stdcall;
end; // i_server_euro_dollar
function Geti_server_euro_dollar(UseWSDL: Boolean=System.False;
Addr: string=''; HTTPRIO: THTTPRIO = nil): i_server_euro_dollar;
implementation
function Geti_server_euro_dollar(UseWSDL: Boolean;
Addr: string; HTTPRIO: THTTPRIO): i_server_euro_dollar;
const
defWSDL = 'C:\programs\fr\web\web_service_tutorial\server_wad\'
+ 'i_server_euro_dollar.xml';
defURL = 'http://localhost:8081/'
+ 'p_serveur_euro_dolar_wad.co_euro_dollar/soap/'
+ 'i_server_euro_dollar';
defSvc = 'i_server_euro_dollarservice';
defPrt = 'i_server_euro_dollarPort';
var RIO: THTTPRIO;
begin
Result := nil;
if (Addr = '')
then begin
if UseWSDL
then Addr := defWSDL
else Addr := defURL;
end;
if HTTPRIO = nil
then RIO := THTTPRIO.Create(nil)
else RIO := HTTPRIO;
try
Result := (RIO as i_server_euro_dollar);
if UseWSDL
then begin
RIO.WSDLLocation := Addr;
RIO.Service := defSvc;
RIO.Port := defPrt;
end
else RIO.URL := Addr;
finally
if (Result = nil) and (HTTPRIO = nil)
then RIO.Free;
end; // try ... finally
end; // Geti_server_euro_dollar
initialization
InvRegistry.RegisterInterface(TypeInfo(i_server_euro_dollar),
'urn:u_i_server_euro_dollar-i_server_euro_dollar', 'utf-8');
InvRegistry.RegisterDefaultSOAPAction(TypeInfo(i_server_euro_dollar),
'urn:u_i_server_euro_dollar-i_server_euro_dollar#%operationName%');
end.
|
et:
- nous retrouvons bien l'INTERFACE que nous avions côté Serveur
- la fonction Geti_server_euro_dollar permet simplement de nous retourner une
INTERFACE, avec plusieurs possibilités
- si nous ne fournissons aucun paramètres, un tHttpRio local est créé, et
est retourné transtypé en i_server_euro_dollar
- si nous demandons un .WSDL, i_server_euro_dollar pourra être utilisé
pour récupérer le .WSDL
- nous pouvons aussi placer un tHttpRio sur la tForm Client, et fournir
celui-ci comme paramètre après l'avoir initialisé
Comme nous appellerons Geti_server_euro_dollar sans paramètres, nous pouvons
(c'est optionnel) simplifier cette fonction.
Voici les modifications (cosmétiques) que nous apportons:
- changez le nom i_server_euro_dollar en i_client_euro_dollar (l'INTERFACE
correspond à un proxy)
- supprimez les paramètres de Geti_server_euro_dollar
- veillez à ce que les chaînes littérales du bas appellent bien
'i_server_euro_dollar': ce sont elles qui seront utilisées pour appeler le
Serveur
Voici notre unité modifiée:
unit u_i_client_euro_dollar;
interface
uses InvokeRegistry, SOAPHTTPClient, Types, XSBuiltIns;
type i_client_euro_dollar =
interface(IInvokable)
['{091A6317-83B7-FB51-3FD6-4BD47A83325F}']
function f_dollar_to_euro(const p_dollar: Double): Double; stdcall;
procedure euro_to_dollar(const p_euro: Double;
var pv_dollar: Double); stdcall;
end; // i_client_euro_dollar
function f_i_client_euro_dollar: i_client_euro_dollar;
implementation
function f_i_client_euro_dollar: i_client_euro_dollar;
const k_service_url= 'http://localhost:8081/'
+ 'p_serveur_euro_dolar_wad.co_euro_dollar/soap/'
+ 'i_server_euro_dollar';
var l_c_http_remote_invokation_object: THTTPRIO;
begin
l_c_http_remote_invokation_object:= THTTPRIO.Create(nil);
Try
Result := (l_c_http_remote_invokation_object as i_client_euro_dollar);
l_c_http_remote_invokation_object.URL:= k_service_url
finally
if Result = nil
then l_c_http_remote_invokation_object.Free;
end; // try ... finally
end; // f_i_client_euro_dollar
initialization
InvRegistry.RegisterInterface(TypeInfo(i_client_euro_dollar),
'urn:u_i_server_euro_dollar-i_server_euro_dollar', 'utf-8');
InvRegistry.RegisterDefaultSOAPAction(TypeInfo(i_client_euro_dollar),
'urn:u_i_server_euro_dollar-i_server_euro_dollar#%operationName%');
end.
|
Notez que
- les deux appels de InvRegistry correspondent aussi à des enregistrement
"locaux" (pas dans la Registry Windows) et permettent de transformer nos
appels de i_client_euro_dollar en des requêtes HTTP au format SOAP
- le GUID de l'INTERFACE importée n'a rien à voir avec le GUI sur le
Serveur. Ici aussi ce GUID sert uniquement à identifier de façon unique
cette INTERFACE dans la liste de correspondance locale au Client
4.4 - Utilisation de notre Proxy
Finissons par le programme Client. Nous allons simplement effectuer une
conversion d'une valeur contenue dans un tEdit, et la conversion sera
effectuée lorsque nous cliquons sur un tButton:
|
sélectionnez la tForm principale
|
|
placez un tEdit, un tLabel et un tButton sur Form1
|
|
créez l'événement Button1Click et tapez le code qui convertira un montant
(nous avons utilisé deux locales auxiliaires qui auraient pu être évitées):
procedure TForm1.dollar_to_euro_Click(Sender: TObject);
var l_euro_amount, l_dollar_amount: Double;
begin
l_dollar_amount:= StrToFloat(dollar_edit_.Text);
// -- appel du service
l_euro_amount:=
f_i_client_euro_dollar().f_dollar_to_euro(l_dollar_amount);
euro_label_.Caption:= FloatToStr(l_euro_amount);
end; // dollar_to_euro_Click
|
|
|
importez aussi U_I_CLIENT_EURO_DOLLAR dans la Forme principale
|
|
compilez, exécutez, tapez un montant en dollar, cliquez sur le bouton
|
|
voici le résultat:

|
Notez que
- nous avons pu exécuter directement notre Client, car notre Serveur était
encore chargé (même si le projet Serveur a été fermé, le CGI reste lancé
par le Web App Debbugger)
Le Web App Debugger stocke les messages échangés entre le Client et le
Serveur:
5 - Améliorations et Commentaire
5.1 - Compliqué ?
Il est à craindre que la lecture de ce document ne fasse apparaître les
Services Web comme horriblement compliqués. Pourtant Delphi a fait des
efforts considérables pour limiter les manipulations au plus strict nécessaire.
En réalité, le fait que nous ayons passé du temps à expliquer certains
fonctionnement a considérablement alourdi la présentation.
Si vous créez deux ou trois services
- les manipulations initiales (comme le premier lancement de ServerInfo, par
exemple) n'auront pas à être répétées
- l'utilisation d'un groupe de projet simplifie énormément les aller-retour
lorsque nous modifions l'INTERFACE du Service
- les différentes manipulations deviendront automatiques
Nous avons même simplifié le traitement en créant un programme de
transformation qui fonctionne ainsi:
- nous avons créer un Service Web (Serveur et Client), en nommant le
"service" (l'équivalent de "euro_dollar") "xxx"
- notre utilitaire copie les fichiers de cette application simple vers un
autre répertoire en renommant "xxx" par le nom que nous avons fourni
Dans ce cas, il ne reste plus qu'à
- au niveau Serveur, entrer les méthodes du Service, et les implémenter
- au niveau Client:
- copier les méthodes dans le client
- taper le code utilisateur du Client
5.2 - Conventions de noms
Nous avons utilisé des convention de préfixes forts (i_xxx, u_i_xxx, c_xxx
ainsi que le mot "serveur" pour le Serveur et "client" pour le Client, et les
suffixes "_wad", "_cgi" et "_isapi" pour distinguer les différents types
d'extension Serveur).
Comme d'habitude, vous n'êtes pas obligés de suivre ces conventions. En
particulier, le fait de supprimer "serveur" et "client" dans le nom de
l'INTERFACE (par exemple iEuroDollar au lien de i_serveur_euro_dollar et
i_client_euro_dollar) permet quasiment d'utiliser la même INTERFACE pour le
Serveur et le Client
Mais nous restons, quant à nous, fidèles aux préfixes. Dans ce cas, ils nous
ont permis de bien comprendre les relations entre les différentes pièces qui
entrent en jeu. Si tous les identificateurs sont identiques, il est aisé de
croire qu'il s'agit d'un objet alors qu'il s'agit d'un autre.
Et en utilisant l'utilitaire de transformation, les manipulation concernant les
préfixes sont quasiment inexistantes.
5.3 - Ce qui manque
Cet exemple simple avait pour unique but de montrer comment construire un
service web. Nous aurions pu ajouter
- le traitement de types complexes (tableaux, CLASSes, tDataSets,
- le transfert de données (fichiers) binaires
- les pièces attachées (SOAP Attachments)
- les transferts de données au format .XML
- les échanges avec Datasnap, en utilisant des tSoapConnection
- la création d'extensions CGI et ISAPI
- le déploiement sur des Serveurs HTTP (IIS, Apache)
- le débugging du Serveur Web Service en utilisant le Web App Debugger
- les conversions entre des applications CGI, ISAPI et Web App Debugger
- les diagrammes de Classe UML présentant les différents composants Delphi
(tWebApp, tHttpRio ...) mis en oeuvre
5.4 - Formation Web Service
Les points mentionnés ci-dessus sont abordés lors de nos
Formations XML Web Services. Nous avons
regroupé la présentation .XML avec la présentation Services Web, car une
connaissance du format .XML est utile, et il arrive que les applications
Service Web aient à traiter des données au format .XML, indépendemment de
SOAP.
5.5 - Et .Net dans tout cela ?
Notre présentation a été effectuée en mode Win32. En fait les Services Web
correspondent à un format, lequel peut être implémenté et utilisé sur la
plupart des ordinateur (Mac, PC), sous divers Operating Systems (Windows,
Linux, Unix, MacOs).
Par conséquent ils peuvent aussi être utilisés (Serveur et Client) dans
l'environnement .NET. Ce qui change ce sont les composants et les manipulations
de mise en oeuvre.
Celles-ci sont d'ailleurs présentées lors de nos formations
Delphi 2006 .Net ainsi que pendant les
formations ASP.NET Delphi et
ASP.NET 2.0 Delphi
5.6 - Next
Au niveau explications, nous aurions aussi pu détailler
- le fonctionnement de WebBroker
- la mécanique de iInvoke
Il existe une étape au-delà des Services Web : les objets distribués. Pour
expliquer la hiérarchie, nous avons
- les extensions CGI, qui nécessitent un explorateur Web, l'appel d'une page
contenant une balise <FORM
- les Services Web, que l'utilisateur peut invoquer depuis n'importe quelle
application exécutable (pas besoin d'explorateur internet)
Ces services sont limités à des appels de FUNCTIONs et de PROCEDUREs.
Les traitement réalisés peuvent être très compliqué (modifier une base de
donnée, retourner la carte de votre domicile), mais comme il s'agit
d'INTERFACE, la mécanique de base ne gère pas d'état. Une fois la réponse
retournée, le Serveur ne se souvient plus de vous. Si votre traitement
nécessite plusieurs étapes (lecture paginée de Tables), c'est à vous de
gérer ces sessions au niveau Client
- les objets distribués: comme les objets contiennent des données, ils sont
plus puissants qu'une simple collection de méthodes
Nous présentons les objets distribuées lors de nos formations .Net
6 - Télécharger le code source Delphi
Vous pouvez télécharger:
Ce .ZIP qui comprend:
- les fichiers .DPR, PAS, DFM
- le fichier .WSDL
Pour l'utiliser:
- dézippez, sélectionnez le groupe, compilez
Ce .ZIP ne modifie pas votre PC (pas de changement de la Base de Registre, de
DLL ou autre). Pour supprimer le projet, effacez le répertoire.
Comme d'habitude:
- nous vous remercions de nous signaler toute erreur, inexactitude ou
problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections
qui en résulteront pourront aider les prochains lecteurs
- tous vos commentaires, remarques, questions, critiques, suggestion
d'article, ou mentions d'autres sources sur le même sujet seront de même
les bienvenus à jcolibri@jcolibri.com.
- plus simplement, vous pouvez taper (anonymement ou en fournissant votre
e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en
cliquant "envoyer" :
- et si vous avez apprécié cet article, faites connaître notre site,
ajoutez un lien dans vos listes de liens ou citez-nous dans vos réponses
sur les messageries. C'est très simple: plus nous aurons de visiteurs et de
références Google, plus nous écrirons d'articles.
7 - Références
Quelques références concernant les Services Web:
- Web Services in Delphi 2007 par Bruneau
BABET de CodeGear - conférence en ligne CodeRage I - Mars 2007 - Video .SWF
de 39 Meg.
Mentionnons aussi que Bruneau BABET est très présent au niveau des newsgroup
Delphi (borland.public.delphi.webservices.soap), et répond bien volontiers
aux questions qui sont posées.
- au niveau de nos articles:
- présentation SOAP et
DataSnap : les transparent de notre présentation à la conférence
Borland en 2001.
Pour des Serveurs HTTP capables de traiter SOAP:
- pour le Serveur
CGI en source à partir des Sockets Delphi. Ce serveur capable de
gérer des requêtes CGI ne permet pas de traiter des messages SOAP en l'état
- il a été modifié pour permettre SOAP, mais cette version n'a pas été publiée
à ce jour
- il existe plusieurs serveurs à base d'Indy, démarrés par Dave NOTTAGE, et
amélioré par la suite. Google ou Code Central vous permettront de les
récupérer
Les points mentionnés ci-dessus sont abordés lors de nos différentes
formations:
8 - L'auteur
John COLIBRI est passionné par le développement
Delphi et les applications de Bases de Données. Il a écrit de nombreux livres
et articles, et partage son temps entre la
développement de projets pour
ses clients, le conseil et la formation. Son site contient des articles
avec code source, ainsi que le programme et le calendrier des
stages
de formation Delphi, base de données, Ado.Net, Asp.Net et UML qu'il
anime personellement tous les mois, à Paris, en province ou sur site client.
|