02.06.2013 Views

Invocation D'un Service Web avec PL - Trivadis

Invocation D'un Service Web avec PL - Trivadis

Invocation D'un Service Web avec PL - Trivadis

SHOW MORE
SHOW LESS

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

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!