Invocation D'un Service Web avec PL - Trivadis
Invocation D'un Service Web avec PL - Trivadis
Invocation D'un Service Web avec PL - Trivadis
Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
Objet : <strong>Invocation</strong> d'un service web <strong>avec</strong> <strong>PL</strong>/SQL – Partie 2<br />
Auteur : Christine Hansen, Fabrizio Fresco, Patrick Malcherek<br />
Nature des informations : Informations à caractère technique (octobre 2002)<br />
Source : http://otn.oracle.com/<br />
Introduction<br />
Si la première partie de notre article était uniquement consacrée à la théorie des services web,<br />
la deuxième partie expliquera comment utiliser de façon pratique les services web disponibles<br />
sur Internet. Pour cela, nous verrons comment procède Oracle <strong>PL</strong>/SQL pour interroger ce type<br />
de services. Compte tenu de ce que nous avons appris sur Oracle dans la première partie, nous<br />
pouvons d'ores et déjà supposer que les services web se conçoivent comme une extension<br />
moderne des applications traditionnelles.<br />
Notre exemple, emprunté au site Oracle TechNet, est à la portée de chaque développeur<br />
<strong>PL</strong>/SQL. Nous pourrons l'adapter en toute simplicité pour pouvoir interroger nos propres<br />
services web, ou ceux d'autres fournisseurs.<br />
Les parties 3 et 4 de l'article nous apprendront à programmer nos propres services web, et nous<br />
présenterons les conditions requises pour une telle programmation.<br />
1. Conditions requises<br />
La première condition nécessaire à la réalisation de notre exemple est une base de données<br />
Oracle 9i créée sous la version Release 2 (9.2.0.1). Cette condition est importante car nous<br />
devrons exploiter certaines caractéristiques propres à Oracle 9i Release 2, notamment le<br />
nouveau type de données XMLTYPE ainsi que les fonctions spéciales du package UTL_HTTP. Le<br />
compte utilisé dans notre exemple sera celui d'un utilisateur standard nommé SCOTT ; le mot<br />
de passe de ce compte sera TIGER. Les codes, les tables et les vues seront créés sous ce compte.<br />
Bien entendu, il nous faudra également une connexion à Internet, puisque nous devrons<br />
invoquer le service web désiré à partir d'un site publié sur le Net.<br />
La version actuelle de la base de données Oracle peut être téléchargée en version d'évaluation<br />
sur le site Oracle TechNet. Le code de notre exemple est à votre disposition dans la zone de<br />
téléchargement du site <strong>Trivadis</strong>.<br />
2. Technique<br />
Dans la première partie de notre article, nous avons évoqué en détail l'architecture générale des<br />
services web, qui nous est à présent familière. Abordons maintenant les techniques spécifiques<br />
associées au langage <strong>PL</strong>/SQL.<br />
2.1. Principes de base<br />
Pour invoquer un service web, nous devons envoyer un message SOAP : la Requête. Le<br />
service web délivre en retour un autre message SOAP contenant les résultats attendus : la<br />
Réponse. SOAP (Simple Object Access Protocole) est un mécanisme conçu pour expédier<br />
des messages standardisés, contenant des données structurées au format XML.
Dans le cas d'une invocation de service web, SOAP exécute un appel de procédure à<br />
distance, ou Remote Procedure Call (RPC). Cette méthode est la plus largement répandue<br />
(sauf dans l'environnement .NET qui exploite principalement des messages de type<br />
Document).<br />
Pour un meilleur suivi de notre exposé sur le code <strong>PL</strong>/SQL, nous vous conseillons de<br />
reproduire dès maintenant la requête envoyée ainsi que la réponse retournée.<br />
Outgoing SOAP Message<br />
<br />
<br />
<br />
<br />
94065<br />
<br />
<br />
<br />
<br />
Return SOAP Message<br />
<br />
<br />
<br />
<br />
<br />
7/7/2002 9:05:42 PM<br />
<br />
<br />
<br />
<br />
Listing 1 - Requête et Réponse SOAP<br />
2.2. Types de données<br />
Pour créer des requêtes et des réponses en <strong>PL</strong>/SQL, il est nécessaire de les enregistrer <strong>avec</strong><br />
des types de données abstraits.<br />
Pour cela, nous devons commencer par créer le type de données request en tant<br />
qu'enregistrement (Record) de plusieurs variables.
TYPE request IS RECORD (<br />
method VARCHAR2(256),<br />
namespace VARCHAR2(256),<br />
body VARCHAR2(32767));<br />
Listing 2 - Définition du type de données request<br />
Nous devons ensuite définir le type de données de la réponse. Pour cela, nous utiliserons le<br />
nouveau type de données XMLTYPE qui offre l'avantage d'être idéalement adapté aux<br />
contraintes des documents XML. Oracle 9i présente une multitude de caractéristiques<br />
permettant une exploitation très efficace du langage XML. Pour en apprendre davantage sur<br />
le type de données XMLTYPE, n'hésitez pas à consulter la documentation Oracle9i<br />
XMLType.<br />
TYPE response IS RECORD (doc xmltype);<br />
Listing 3 - Définition du type de données response<br />
2.3. Création du Corps SOAP<br />
Abordons maintenant la création du message SOAP et commençons tout naturellement par<br />
son élément noyau : le Corps (body). Le fondement d'un RPC repose dans la création d'une<br />
méthode et de différents paramètres.<br />
Le listing ci-dessous contient le package demo_soap dont deux composants permettent<br />
justement de créer ces éléments :<br />
La fonction new_request permet d'assembler facilement l'objet req à partir des paramètres<br />
délivrés.<br />
La procédure add_parameter permet de visualiser une nouvelle fois la requête du listing 1,<br />
afin de mieux comprendre de quelle façon elle est assemblée. Dans un message SOAP de<br />
type RPC, il est nécessaire de définir les types de données des arguments délivrés en<br />
respectant le schéma XML. C'est justement ce que fait la procédure add_parameter en<br />
créant la définition correspondante à partir des paramètres qui lui sont transmis.<br />
FUNCTION new_request(method IN VARCHAR2, namespace IN<br />
VARCHAR2) RETURN request AS<br />
req request;<br />
BEGIN<br />
req.method := method;<br />
req.namespace := namespace;<br />
RETURN req;<br />
END;<br />
PROCEDURE add_parameter(req IN OUT NOCOPY request,<br />
name IN VARCHAR2, type IN VARCHAR2, value IN VARCHAR2) AS<br />
BEGIN<br />
req.body := req.body ||<br />
''||value||'';<br />
END;<br />
Listing 4 - Méthodes de création de la requête SOAP<br />
Pour créer la requête, ces deux composants sont appelés à l'intérieur du pack time_service<br />
dans lequel ils reçoivent les paramètres requis.
eq := demo_soap.new_request('LocalTimeByZipCode',<br />
'xmlns="http://www.alethea.net/webservices/"');<br />
demo_soap.add_parameter(req, 'ZipCode', 'xsd:string', zipcode);<br />
Listing 5 - Appel dans le package time_service<br />
2.4. Création de l'Enveloppe SOAP<br />
Avant de pouvoir expédier le message SOAP, il nous faut encore créer son Enveloppe. Cette<br />
enveloppe se compose d'un En-tête standard (voir le listing 1), inscrit dans le message avant<br />
le Corps. Le message SOAP peut contenir plusieurs En-têtes identiques pour appeler des<br />
services web différents. Comme le montre le listing ci-dessous, il n'existe que trois éléments<br />
variables pouvant être insérés dans l'En-tête du message. Et nous venons justement de voir<br />
comment créer ces éléments.<br />
PROCEDURE generate_envelope(req IN OUT NOCOPY request, env IN OUT<br />
NOCOPY VARCHAR2) AS<br />
BEGIN<br />
env := '<br />
'||<br />
req.body||'';<br />
END;<br />
Listing 6 - Procédure de création d'une Enveloppe SOAP<br />
2.5. <strong>Invocation</strong> d'un service web<br />
Maintenant que notre message SOAP est composé, nous devons l'envoyer par HTTP à<br />
l'adresse (URL) du service web désiré. Pour cela, nous utiliserons la fonction invoke,<br />
présentée dans le listing 7.<br />
Afin de pouvoir échanger des informations <strong>avec</strong> Internet, à partir de la base de données,<br />
nous aurons recours au package UTL_HTTP d'Oracle qui intègre une gamme complète de<br />
fonctions et de procédures permettant d'assembler, d'expédier et de réceptionner des<br />
messages SOAP. Pour obtenir des informations détaillées sur le package UTL_HTTP, vous<br />
pouvez consulter la documentation Oracle9i Database Release 2 disponible sur le site<br />
d'Oracle.<br />
Attardons-nous un instant sur la fonction sollicitée.<br />
Dans la configuration de l'en-tête HTTP (lignes 8 à 11), il est nécessaire de définir le type du<br />
message ainsi que le type de son contenu. Dans le cas présent, le message est de type post<br />
tandis que son contenu est de type text/xml. La fonction write_text (ligne 12) est utilisée en<br />
complément pour envoyer la requête HTTP contenant le message SOAP. Le contenu du<br />
message est alors inclus dans la variable env qui est associée à la fonction en tant que<br />
paramètre. Dès que le service web délivre une réponse, celle-ci est réceptionnée par la<br />
fonction get_response et inscrite dans la variable env (lignes 14 à 16). Cette variable est<br />
ensuite copiée dans l'enregistrement resp de type response (voir le listing 3). Le type<br />
response contient le champ doc de type XMLTYPE ; ce type (XMLTYPE) permet au<br />
développeur d'extraire le Corps de l'Enveloppe en toute simplicité (voir aussi la<br />
documentation Oracle9i XMLType).
1 FUNCTION invoke(req IN OUT NOCOPY request, url IN VARCHAR2,<br />
action<br />
IN VARCHAR2) RETURN response AS<br />
2 env VARCHAR2(32767);<br />
3 http_req utl_http.req;<br />
4 http_resp utl_http.resp;<br />
5 resp response;<br />
6 BEGIN<br />
7 generate_envelope(req, env);<br />
8 http_req := utl_http.begin_request(url, 'POST');<br />
9 utl_http.set_header(http_req, 'Content-Type', 'text/xml');<br />
10 utl_http.set_header(http_req, 'Content-Length',<br />
length(env));<br />
11 utl_http.set_header(http_req, 'SOAPAction', action);<br />
12 utl_http.write_text(http_req, env);<br />
13<br />
14 http_resp := utl_http.get_response(http_req);<br />
15 utl_http.read_text(http_resp, env);<br />
16 utl_http.end_response(http_resp);<br />
17<br />
18 resp.doc := xmltype.createxml(env);<br />
19<br />
20 resp.doc :=<br />
resp.doc.extract('/soap:Envelope/soap:Body/child::node()',<br />
21 'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"');<br />
22<br />
23 check_fault(resp);<br />
24 RETURN resp;<br />
25 END;<br />
Listing 7 - <strong>Invocation</strong> du service web à l'aide du package UTL_HTTP<br />
La ligne 23 nous indique qu'une autre procédure – check_fault – a été appelée à l'intérieur<br />
du package. Là encore, le type de données XMLTYPE est essentiel car la procédure<br />
check_fault exécute une requête XPath (requête à l'intérieur du code XML) dans le contenu<br />
de la réponse, afin de détecter si le service web a éventuellement retourné une quelconque<br />
erreur. S'il est vrai qu'une requête sous forme de boucle <strong>PL</strong>/SQL (exploitant une variable<br />
VARCHAR) aurait pu s'appliquer de la même façon, notre variante offre toutefois l'avantage<br />
majeur de se limiter à une seule ligne de code. Si la procédure détecte une ou plusieurs<br />
erreurs, celles-ci sont extraites et affichées dans quelques lignes de code supplémentaires,<br />
en tant de fault_code et fault_string.<br />
PROCEDURE check_fault(resp IN OUT NOCOPY response) AS<br />
fault_node xmltype;<br />
fault_code VARCHAR2(256);<br />
fault_string VARCHAR2(32767);<br />
BEGIN<br />
fault_node := resp.doc.extract('/soap:Fault',<br />
'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/');<br />
IF (fault_node IS NOT NULL) THEN<br />
fault_code :=<br />
fault_node.extract('/soap:Fault/faultcode/child::text()',<br />
'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/').ge<br />
tstringval();
fault_string :=<br />
fault_node.extract('/soap:Fault/faultstring/child::text()',<br />
'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/').ge<br />
tstringval();<br />
raise_application_error(-20000, fault_code || ' - ' ||<br />
fault_string);<br />
END IF;<br />
END;<br />
Listing 8 - Traitement des erreurs SOAP<br />
Le listing 9 contient l'appel du service web par la fonction invoke :<br />
resp := demo_soap.invoke(req,<br />
'http://www.alethea.net/webservices/LocalTime.asmx',<br />
'http://www.alethea.net/webservices/LocalTimeByZipCode');<br />
Listing 9 - <strong>Invocation</strong> du service web LocalTimeByZipCode<br />
2.6. Présentation finale du résultat<br />
Traiter le document XML à l'aide de la fonctionnalité XMLTYPE permet d'économiser un<br />
temps précieux. Toutefois, si le résultat délivré par le service web se révèle être une simple<br />
chaîne (string), il est alors judicieux d'utiliser une autre fonction auxiliaire pour gérer le<br />
traitement de cette chaîne et sa présentation finale (voir le listing 10). La fonctionnalité<br />
XMLTYPE XPath est alors des plus utiles, encore une fois.<br />
FUNCTION get_return_value(resp IN OUT NOCOPY response, name IN<br />
VARCHAR2, namespace IN VARCHAR2) RETURN VARCHAR2 AS<br />
BEGIN<br />
RETURN resp.doc.extract('//'||name||'/child::text()',<br />
namespace).getstringval();<br />
END;<br />
Listing 10 - Lire la chaîne de résultat dans le message SOAP<br />
Le listing 11 présente de quelle manière la réponse SOAP est transmise à la fonction<br />
get_return_value pour en extraire la chaîne VARCHAR.<br />
RETURN demo_soap.get_return_value(resp,<br />
'LocalTimeByZipCodeResult',<br />
'xmlns="http://www.alethea.net/webservices/"');<br />
Listing 11 - Transfert de la réponse SOAP<br />
3. Exécution de l'exemple<br />
Pour réaliser soi-même l'exemple de notre article, il suffit de suivre quelques étapes très<br />
simples.<br />
Pour commencer, exécutez le script demosoap.sql dans SQL*Plus, afin de créer le package<br />
demo_soap mentionné précédemment.<br />
Le listing 12 affiche le package time_service contenant le code nécessaire à l'exécution du<br />
service web LocalTimeByZipCode. Les étapes présentées ci-dessous sont analogues aux étapes<br />
développées dans les paragraphes précédents : l'appel de la fonction demo_soap.new_request
permet d'abord de créer la requête SOAP <strong>avec</strong> le nom des méthodes et l'espace de noms<br />
correspondants. Puis, la fonction demo_soap.add_parameter transmet les paramètres que le<br />
service web réceptionne. La fonction demo_soap.invoke peut dès lors invoquer le service web.<br />
Pour finir, le résultat est retourné par l'intermédiaire de la fonction<br />
demo_soap.get_return_value.<br />
CREATE OR RE<strong>PL</strong>ACE PACKAGE time_service AS<br />
FUNCTION get_local_time(zipcode IN VARCHAR2) RETURN<br />
VARCHAR2;<br />
END;<br />
/<br />
CREATE OR RE<strong>PL</strong>ACE PACKAGE BODY time_service AS<br />
-- Location of <strong>Web</strong> service definition<br />
-- http://www.alethea.net/webservices/LocalTime.asmx?WSDL<br />
FUNCTION get_local_time(zipcode IN VARCHAR2) RETURN VARCHAR2<br />
IS<br />
req demo_soap.request;<br />
resp demo_soap.response;<br />
BEGIN<br />
req := demo_soap.new_request('LocalTimeByZipCode',<br />
'xmlns="http://www.alethea.net/webservices/"');<br />
demo_soap.add_parameter(req, 'ZipCode', 'xsd:string',<br />
zipcode);<br />
resp := demo_soap.invoke(req,<br />
'http://www.alethea.net/webservices/LocalTime.asmx',<br />
'http://www.alethea.net/webservices/LocalTimeByZipCode');<br />
RETURN demo_soap.get_return_value(resp,<br />
'LocalTimeByZipCodeResult',<br />
'xmlns="http://www.alethea.net/webservices/"');<br />
END;<br />
BEGIN<br />
/* * If the <strong>Web</strong> service resides outside of the firewall, we would need to<br />
* set the proxy in the current session before invoking the service.<br />
*/<br />
-- utl_http.set_proxy('www-your-proxy', NULL);<br />
utl_http.set_persistent_conn_support(TRUE);<br />
END;<br />
Listing 11 - Package time_service<br />
La fonction SET_PROXY du package UTL_HTTP ne doit être appelée que si la configuration de<br />
l'accès Internet requiert l'utilisation d'un proxy. Si la connexion de l'utilisateur fonctionne<br />
indépendamment d'un quelconque proxy, cette ligne peut être désactivée (elle l'est d'ailleurs<br />
par défaut dans le code d'exemple).<br />
Pour réaliser volontairement plusieurs invocations successives du service web, pensez à régler<br />
le paramètre SET_PERSISTENT_CONN_SUPPORT sur TRUE. Ce paramètre empêche la
fermeture systématique de la connexion réseau et permet, par conséquent, d'améliorer la<br />
performance de la procédure en cas d'invocations multiples. Le bénéfice est considérable si<br />
vous invoquez souvent les mêmes services web ou si vous gérez de très nombreux utilisateurs.<br />
Pour démarrer le service web la première fois, exécutez entièrement le script local.sql dans<br />
SQL*Plus. Si le package time_service a déjà été créé de cette façon, vous pouvez utiliser la<br />
variante simple de l'appel dans SQL*Plus, comme indiqué dans le listing 12.<br />
SET serveroutput ON<br />
exec dbms_output.put_line(time_service.get_local_time('94065'));<br />
Listing 12 - Appel dans SQL*Plus<br />
Résumons<br />
Comme nous venons de le constater, il est très facile d'invoquer un service web existant par<br />
l'intermédiaire d'Oracle <strong>PL</strong>/SQL. L'exemple que nous avons développé peut d'ailleurs servir de<br />
modèle à l'invocation d'autres services web de type similaire.<br />
Sur le site TechNet d'Oracle, vous trouverez un autre exemple exploitant lui aussi le package<br />
central demo_soap. Les développeurs <strong>PL</strong>/SQL pourront utiliser cet exemple comme structure de<br />
base de leur syntaxe SOAP, qui se distinguera uniquement par l'appel du service recherché.<br />
Il n'est pas difficile d'imaginer qu'à l'avenir les applications basées sur Oracle (les formulaires<br />
web, par exemple) trouveront d'avantageuses extensions dans la mise en œuvre des services<br />
web.<br />
Christine Hansen, Fabrizio Fresco et Patrick Malcherek<br />
<strong>Trivadis</strong> GmbH E-mail : christine.hansen@trivadis.com<br />
Cityforum à Eichsfeld fabrizio.fresco@trivadis.com<br />
Ferdinand-Stuttmann-Str. 13 patrick.malcherek@trivadis.com<br />
D-65428 Rüsselsheim Tél. : +49 6142 210 18 0<br />
Internet : http://www.trivadis.com Fax : +49 6142 210 18 29