Ellipse Tutorial

Invocation AJAX (Javascript) de services Web



ATTENTION : Tutorial en cours d'écriture ! N'hésitez pas à nous signaler toute erreur ou suggestion.

Accès rapide :
   Limitations induites par un navigateur
   Génération du proxy Javascript via le WSDL
   Implémentation d'un client pour un logiciel de chat
   Appels synchrones VS appels asynchrones
   Echange d'objets via un service Web

Nous allons, dans ce chapitre, nous intéresser à l'utilisation des services web au sein d'une page web, via le langage Javascript. Néanmoins, si vous avez des questions sur la nature d'un service web ou bien sur comment coder et déployer un service web via le framework Ellipse, je vous renvois préalablement vers le chapitre dédié à ce sujet : Mise en oeuvre de services Web.

Limitations induites par un navigateur

ATTENTION : pour éviter certains risques de virus ou de hack de sites Web, un navigateur Web, et plus précisément les composants AJAX (qui sont utilisés dans la solution qui va vous être présentée), ne peuvent ouvrir des connexions HTTP que, et uniquement que, vers le serveur Web d'où la page HTML qui, réalise la requête, est issue. En d'autres termes, vous ne pouvez utiliser, via une page Web Ellipse incorporant du Javascript, uniquement que des services web hébergés par le serveur HTTP d'où provient la page.

Toute autre tentative de connexion à un service Web hébergé par un autre serveur HTTP se soldera par une erreur dans le contexte d'un navigateur Web.

Génération du proxy Javascript via le WSDL

Pour invoquer un service web, une requête HTTP contenant un flux SOAP doit être générée. De même la valeur de retour du service web vous sera retournée via une réponse HTTP contenant, elle aussi, un flux SOAP. Il serait peu efficace que vous soyez obligé de prendre en charge cette gestion du formalisme SOAP : vous êtes normalement là pour vous consacrer sur ce que doit faire votre application Web, mais pas toujours sur comment elle doit le faire. La communication SOAP n'est donc pas de votre responsabilité.

La solution qui vous est proposée pour invoquer un service Web via Javascript prend donc en charge la génération d'un objet Javascript (appelé proxy) qui représentera le service Web distant. La génération de ce proxy est automatique : elle est réalisée en téléchargeant le WSDL (Web Service Description Language) du service considéré. Ce proxy exposera autant de méthodes que celle exposées par le service Web. Elles auront, qui plus est, les mêmes noms et pourront avoir (dans le cas d'un appel synchrone) les mêmes signatures (prototypes) que leurs homologues sur le service web distant.

Pour générer le proxy, les choses sont très simples : il suffit d'invoquer la fonction de construction WebService en lui passant en paramètre la localisation du service (URL). Certains services sont sécurisés : vous pouvez aussi passer en second et troisième paramètre l'identifiant de connexion et le mot de passe associé. Voici un petit exemple de code qui accède au service Web ChatRoom développé dans le chapitre Mise en oeuvre de services Web.

 
importPackage( "corelib/services/web/javascript/webservices/WebService.js" );

var proxy = new WebService( "http://localhost:8080/VirtualCaddy/ChatRoom.ws" );
proxy.subscribe( "Essai" );
proxy.unsubscribe( "Essai" );
Exemple de génération d'un proxy de service web

Implémentation d'un client pour un logiciel de chat

En guise de démonstration, nous allons coder un client (sous forme de page Web Ellipse) pour accéder à notre service de salle de discussion développé précédemment. Notez que cette page Web fait partie de l'application de démonstration du framework Ellipse. Ce client va être constitué de deux fichiers : une page web Ellipse (sans nécessité de classe de page) ainsi qu'un fichier de code Javascript rattaché à cette page. Je vous rappelle que le framework Ellipse charge automatiquement un fichier de code Javascript, pour une page web donnée, et ce à partir du moment où les noms de fichiers (hors extensions, bien entendu) sont les mêmes. Vous ne trouverez donc pas de tag <script> dans l'exemple ci dessous. Tout l'intérêt de cet exemple et de permettre à la page de se réactualiser, mais sans jamais totalement décharger cette page. Il n'y aura donc pas d'effet de scintillement.

 
01 <?xml version="1.0" encoding="ISO-8859-1" ?>
02 <web:Html xmlns:web="corelib.services.web.components"
03           codeBehind="corelib.services.web.webapplications.WebPage">
04     <head>
05         <title>ChatUser.wp - Web Service Javascript Invocation</title>
06     </head>
07     <body style="padding: 0px; margin: 0px; overflow: hidden">
08         <table style="width: 100%; height: 100%" border="0" cellspacing="0" cellpadding="0">
09         
10             <tr>
11                 <td rowspan="3" style="width: 20%">
12                     <textarea id="txtUsers" readonly="readonly"
13                               style="width: 100%; height: 100%">
14                     </textarea>                    
15                 </td>
16 
17                 <td style="width: 70%; height:24px;">
18                     <input id="txtUsername" type="text"
19                            style="width: 100%; height: 100%" />
20                 </td>
21 
22                 <td style="width: 10%; height:24px;">
23                     <input id="btnSubscribe" type="button"
24                            style="width: 100%; height: 100%" value="Subscribe" />
25                 </td>                
26             </tr>
27             
28             <tr>
29                 <td colspan="2" style="height: 100%">
30                     <textarea id="txtOutput" style="width: 100%; height: 100%"
31                               readonly="readonly"></textarea>
32                 </td>
33             </tr>
34             
35             <tr>
36                 <td style="width: 70%; height:24px;">
37                     <input id="txtMessage" type="text"
38                            style="width: 100%; height: 100%" />
39                 </td>
40 
41                 <td style="width: 10%; height:24px;">
42                     <input id="btnSend" type="button"
43                            style="width: 100%; height: 100%" value="Send" />
44                 </td>                
45             </tr>
46         
47         </table>
48     </body>
49 </web:Html>
Fichier de page web "ChatUser.wp"

Il ne reste plus qu'à rajouter les gestionnaires d'événements, via Javascript, pour réagir à l'activation des différents éléments graphiques de l'interface. Notez bien que le proxy est construit en début de programme (ligne 04), puis ensuite réutilisé dans les gestionnaires d'événements. Effectivement, lors de la construction du proxy, le WSDL est téléchargé et analysé via un parseur DOM : cela consomme un certain nombre de ressources. Il est donc préférable de le construire une fois pour toute.

 
 01 importPackage( "corelib/services/web/javascript/webservices/WebService.js" );
 02 
 03 
 04 var chatRoom = new WebService( "http://localhost/VirtualCaddy/samples/ChatRoom.ws" );
 05 
 06 var txtUsername = null;
 07 var txtOutput = null;
 08 var txtUsers = null;
 09 var txtMessage = null;
 10 
 11 var btnSubscribe = null;
 12 var btnSend = null;
 13 
 14 var idClock = null;
 15 
 16 var strToSend = null;
 17 
 18 function enabledOrDisabledTextBoxes() {
 19     if ( idClock == null ) {
 20         txtUsername.disabled = false;
 21         txtUsername.focus();
 22         txtMessage.disabled = true;
 23     } else {
 24         txtUsername.disabled = true;
 25         txtMessage.disabled = false;
 26         txtMessage.focus();
 27     }
 28 }
 29 
 30 
 31 function refreshTextBoxes() {
 32 
 33     if ( strToSend != null ) {
 34         chatRoom.sendMessage( txtUsername.value, strToSend );
 35         strToSend = null;
 36     }
 37 
 38     var users = chatRoom.getUsers();
 39     txtUsers.value = users.join( "\n" );
 40 
 41     var messages = chatRoom.getMessages( txtUsername.value );
 42     var newContent = messages.join( "\n" );
 43     if ( newContent != "" ) newContent += "\n";
 44     txtOutput.value += newContent;
 45     
 46 }
 47 
 48 
 49 addEventHandler( window, "onload", function() {
 50 
 51     txtUsername  = document.getElementById( "txtUsername" );
 52     txtOutput    = document.getElementById( "txtOutput" );
 53     txtUsers     = document.getElementById( "txtUsers" );
 54     txtMessage   = document.getElementById( "txtMessage" );
 55     
 56     btnSubscribe = document.getElementById( "btnSubscribe" );
 57     btnSend      = document.getElementById( "btnSend" );
 58 
 59     addEventHandler( btnSubscribe, "onclick", function( event ) {
 60         if ( idClock == null ) {
 61             if ( txtUsername.value == null ) return;
 62             chatRoom.subscribe( txtUsername.value );
 63             btnSubscribe.value = "Unsubscribe";
 64             
 65             idClock = window.setInterval( "refreshTextBoxes();", 1000 );
 66         } else {
 67             chatRoom.unsubscribe( txtUsername.value );            
 68             btnSubscribe.value = "Subscribe";
 69             
 70             window.clearInterval( idClock );
 71             idClock = null;
 72 
 73             txtUsers.value = "";
 74             txtOutput.value = "";
 75         }
 76         enabledOrDisabledTextBoxes();
 77     });
 78     
 79     addEventHandler( btnSend, "onclick", function( event ) {
 80         strToSend = txtMessage.value;
 81         txtMessage.value = "";
 82         txtMessage.focus();
 83     });
 84     
 85     addEventHandler( txtUsername, "onkeyup", function ( event ) {
 86         if ( event.keyCode == 13 ) btnSubscribe.click();
 87     });
 88 
 89     addEventHandler( txtMessage, "onkeyup", function ( event ) {
 90         if ( event.keyCode == 13 ) btnSend.click();
 91     });
 92 
 93     enabledOrDisabledTextBoxes();
 94     
 95 });
 96 
 97 
 98 addEventHandler( window, "onunload", function() {
 99 	if ( idClock != null ) { 
100 		window.clearInterval( idClock );
101 		chatRoom.unsubscribe( txtUsername.value );			
102 	}
103 });
Fichier de code client "ChatUser.js"

Appels synchrones VS appels asynchrones

Ellipse vous permet deux types d'appels. Soit un appel synchrone : tant que la méthode web n'est pas terminée, le proxy ne rend pas la main. Soit un appel asynchrone. Le code client invoque la méthode sur le proxy et rend la main immédiatement. Dès que le service web à finit son travail, une méthode de rappel (une callback) sera invoquée pour vous notifier de la fin du traitement et vous renvoyer l'éventuelle valeur de retour.

Le choix entre un appel synchrone ou un appel asynchrone se fait simplement en ajoutant un paramètre supplémentaire (la callback) à l'invocation. Effectivement, via le WSDL, on connait le nombre de paramètres attendus par la méthode web : il est donc simple de différencier les deux types d'invocation. Le fichier "samples/UseWebService.wp" de l'application de démonstration du framework Ellipse vous montre un exemple d'utilisation d'appels asynchronne et synchrone : vous pourrez ainsi comparer les deux types d'appel. Voici le contenu de ce fichier (notez que dans cet exemple simpliste, le javascript est embarqué dans la page).

 
01 <?xml version="1.0" encoding="ISO-8859-1" ?>
02 <web:Html xmlns:web="corelib.services.web.components"
03           xmlns:demo="corelib.services.web.samples.virtualcaddy.webcomponents"
04           codeBehind="corelib.services.web.webapplications.WebPage">
05     <head>
06         <title>Web service invocation sample</title>
07         <link rel="stylesheet" type="text/css" href="CssStyles.css" />
08     </head>
09     <body>
10         <h1>Web service invocation sample</h1> <br />
11     
12         <table border="0" width="300">
13         
14             <tr>
15                 <td width="45%"><input id="btnSync" type="button"
16                          value="Synchronized call" style="width: 100%" /></td>
17                 <td><input id="txtSync" style="width: 100%" /></td>
18             </tr>
19             <tr>
20                 <td><input id="btnAsync" type="button"
21                          value="Asynchronized call" style="width: 100%" /></td>
22                 <td><input id="txtAsync" style="width: 100%" /></td>
23             </tr>
24         </table>
25 
26         <script language="javascript">
27             importPackage( "corelib/services/web/javascript/webservices/WebService.js" );
28         
29             var addService = new WebService( "http://localhost/VirtualCaddy/Add.ws" );
30         
31             addEventHandler( document.getElementById( "btnSync" ), "onclick", function( event ) {
32                 var a = new Date().getMinutes();
33                 var b = new Date().getSeconds();
34                 var strResult = a + " + " + b + " == " + addService.add( a, b );
35                 document.getElementById( "txtSync" ).value = strResult;
36             });
37 
38             addEventHandler( document.getElementById( "btnAsync" ), "onclick", function( event ) {
39                 var a = new Date().getMinutes();
40                 var b = new Date().getSeconds();
41                 addService.add( a, b, function( result ) {
42                     var strResult = a + " + " + b + " == " + result;
43                     document.getElementById( "txtAsync" ).value = strResult;
44                 });
45             });
46         
47         </script>
48 
49     </body>
50 </web:Html>
Fichier "samples/UseWebService.wp"

Echange d'objets via un service Web

Les services web peuvent permettre, par le biais des paramètres de méthodes et des valeurs de retour, d'échanger des données sous formes d'objets. Les solutions offertes par le framework Ellipse sont, bien entendu, compatibles avec ces possibilités. L'utilisation de services web via l'API Javascript (fournie par le framework) permet de reconstruire des objets Javascript représentant les données (de type objet) acquises lors d'un appel. Attention : les méthodes du serveur ne seront pas reconstruite côté Javascript. Seules les propriétés publiques des objets serveurs transportés seront reconstruites.

A titre d'exemple, voici encore une page web issue de l'application de démonstration du framework Ellipse. Celle-ci invoque le service web de récupération des articles du catalogue (voir le chapitre sur les services web côté serveur) et affiche les articles retournés par le service dans la page.

 
01 <?xml version="1.0" encoding="ISO-8859-1" ?>
02 <web:Html xmlns:web="corelib.services.web.components"
03           xmlns:demo="corelib.services.web.samples.virtualcaddy.webcomponents"
04           codeBehind="corelib.services.web.webapplications.WebPage">
05     <head>
06         <title>Web service invocation advanced sample</title>
07         <link rel="stylesheet" type="text/css" href="CssStyles.css" />
08     </head>
09     <body>
10         <h1>Web service invocation advanced sample</h1> <br />
11     
12         <p>
13             Please, enter an identifier:
14             <input type="text" id="txtIdentifier" value="1" style="width: 100px" />
15             <input type="button" id="btnDisplay" value="Display" />
16         </p>
17     
18         <h2>Liste des articles</h2>    
19 
20         <p>
21             <script language="javascript">
22 
23                 importPackage( "corelib/services/web/javascript/webservices/WebService.js" );    
24                 var catalogBrowser = new WebService( "http://localhost/VirtualCaddy/CatalogBrowser.ws" );
25 
26                 addEventHandler( document.getElementById( "btnDisplay" ), "onclick", function() {
27                     var identifier = document.getElementById( "txtIdentifier" ).value;
28                     var article = catalogBrowser.getArticle( identifier );
29                     alert( article.idArticle + ": " + article.brand + " " + 
30                            article.designation + " - " + article.unitaryPrice );
31                 });
32 
33                 var articles = catalogBrowser.getAllArticles();
34                 for( var i=0; i != articles.length; i++ ) {
35                     var theArticle = articles[i];
36                     document.write( theArticle.idArticle + ": " + theArticle.brand + " " + 
37                         theArticle.designation + " - " + theArticle.unitaryPrice + "&lt;br/&gt;" );
38                 }
39             </script>
40         </p>
41 
42     </body>
43 </web:Html>
Fichier "samples/UseComplexWebService.wp"

ATTENTION : Ellipse Framework vous est proposé en version beta (d'évaluation) afin de vous permettre d'évaluer ce framework. Infini Software se dégage de toutes responsabilités relatives à l'utilisation de ce framework. De plus, Infini Software ne pourra nullement être tenu responsable de l'utilisation des informations présentes dans ces tutoriaux.

Dominique LIARD - © 2007..2010 SARL Infini Software - Tous droits réservés
Les autres marques et les noms de produits cités dans ces documents sont la propriété de leurs détenteurs respectifs.