TP 8 Initiation à Google Web Toolkit (GWT) - Communication Client-Serveur 8.1 Avant-propos Lors du premier, nous avons découvert le fonctionnement de GWT par l intermédiaire d un exemple simple : gestion d une liste d étudiants. À l issue de cette première séance, nous avons la possibilité de : afficher la liste des étudiants enregistrés, ajouter un étudiant, supprimer un étudiant. Cette gestion est réalisée exclusivement côté client. Plus précisément, le code Java développé et compilé génère du code Javascript exécuté sur le navigateur du client. Il n y a pas, à ce stade, d utilisation d un quelconque mécanisme AJAX et de communication client-serveur. Lors de cette séance, nous allons découvrir une introduction de ces nouvelles notions. 8.2 Définition de GWT RPC Le framework GWT RPC permet de faciliter la communication entre un client et les différents composants du serveur d une application web et ainsi d échanger des objets Java à travers le protocole HTTP. Le code côté serveur que le client peut invoquer définit ce que l on nomme un service. L implémentation d un service GWT RPC est basée sur l architecture des servlets Java. Dans le code côté client, nous utiliserons une classe proxy générée automatiquement qui se chargera de faire les appels aux services. Le framework GWT s occupe de sérialiser aussi bien les objets échangés que les objets passés en arguments ou retours de méthodes. Il est à noter que GWT RPC est complètement propre au framework GWT et n est pas similaire à la notion de services web tels que REST ou encore SOAP. C est une fonctionnalité propriétaire et optimisée permettant l échange d objets client-serveur. Le mécanisme de GWT RPC (voir la figure 8.1) s apparente au mécanisme proposé par Corba. Au niveau architectural, pour un service donné, nous avons : une interface de service (signature de méthodes distantes), une interface similaire à l interface de service, mais asynchrone : c est elle qui sera utilisée dans le code du client GWT, une implémentation du service, située côté serveur. Le point faible de ce mécanisme réside dans le couplage fort entre le code client et le code serveur, car le contrat d interface utilise une interface Java. 8.3 GWT RPC et la gestion des étudiants Suivant la figure 8.1, afin de définir une interface GWT RPC (ou RPC, tout simplement), nous avons besoin d écrire trois composants : 1
Figure 8.1: Architecture générale du mécanisme GWT RPC 1. définir une interface EtudiantService pour notre service qui étend la classe RemoteService et qui liste toutes les méthodes RPC, 2. créer une classe EtudiantServiceImpl qui étend RemoteServiceServlet et qui implémente l interface que nous venons de définir, 3. définir une interface asynchrone EtudiantServiceAsync pour notre service et qui sera appelée par le code coté client. L implémentation d un service doit étendre RemoteServiceServlet et doit implémenter l interface de service correspondante. Notons que l implémentation du service n implémente pas la version asynchrone de l interface de service. Chaque service est obligatoirement une servlet, mais plutôt que d étendre HttpServlet comme dans le cas d un développement JEE classique par exemple, elle étend RemoteServiceServlet. Cette dernière s occupe automatiquement de la sérialisation des données échangées entre le client et le serveur et invoquées par la méthode correspondante à l implémentation de notre service. 8.3.1 Création du service Dans ce TP, nous allons transférer la gestion des étudiants de la partie cliente vers la partie serveur (il ne faut jamais faire confiance au client). Actuellement, vous avez l affichage de tous les étudiants dans un widget GWT, l ajout (et la suppression) d un étudiant. 1 public class GestionEtudiant implements EntryPoint { 2 3 [...] 4 5 private FlexTable gridetd = new FlexTable (); 6 private TextBox addname = new TextBox (); 7 private TextBox addlastname = new TextBox (); 8 private Button btnaddetd = new Button (" Ajouter "); 9 T. Morsellino, version du November 6, 2012 2 / 7
10 11 private void addetd () { 12 final String nom = addetd. gettext (). trim (); 13 final String prenom = addetd2. gettext (). trim (); 14 addetd. setfocus ( true ); 15 16 addetd. settext (""); 17 addetd2. settext (""); 18 19 int row = gridetd. getrowcount (); 20 21 gridetd. settext (row, 0, nom ); 22 gridetd. settext (row, 1, prenom ); 23 } 24 25 public void onmoduleload () { 26 27 List < Etudiant > list = new ArrayList (); 28 list. add ( new Etudiant (" Toto ", "L asticaot ")); 29 list. add ( new Etudiant (" Mickey ", " Mouse ")); 30 list. add ( new Etudiant (" Luke ", " Donald - Skywalker ")); 31 32 gridetd. settext (0, 0, " Nom "); 33 gridetd. settext (0, 1, " Prenom "); 34 35 int i = 1; 36 for ( Etudiant etd : list ){ 37 gridetd. settext (i, 0, etd. getnom ()); 38 gridetd. settext (i, 1, etd. getprenom ()); 39 i ++; 40 } 41 42 gridetd. addstylename (" grid "); 43 44 addpanel. add ( addetd ); 45 addpanel. add ( addetd2 ); 46 addpanel. add ( btnaddetd ); 47 48 mainpanel. add ( gridetd ); 49 mainpanel. add ( addpanel ); 50 51 RootPanel. get ().add ( mainpanel ); 52 53 addetd. setfocus ( true ); 54 addetd. settitle (" Nom "); 55 addetd2. settitle (" Prenom "); 56 57 btnaddetd. addclickhandler ( new ClickHandler () { 58 public void onclick ( ClickEvent event ) { 59 addetd (); 60 } 61 }); 62 63 [...] Question 1 Suivant les explications précédentes, la figure 8.1, en utilisant Eclipse correctement (génération automatique du corps de l interface), créer le service correspondant : définir l interface de service (client) : EtudiantService, implémenter l interface (serveur) : EtudiantServiceImpl, ajouter l annotation @RemoteServiceRelativePath( manager ) à la classe EtudiantService qui associe le service avec un chemin relatif par défaut à l URL de base du module. Attention de ne pas se tromper de package. T. Morsellino, version du November 6, 2012 3 / 7
Noter que l implémentation d un service se place coté serveur et que celui-ci est exécuté en tant que bytecode Java. En conséquence, il est possible d utiliser toutes les bibliothèques Java classiques (List, Random,... ). Une fois ces deux classes écrites, il est nécessaire d inclure le code serveur dans le module GWT qui va être déployé par le serveur web (Tomcat). Pour cela, il faut ajouter les balises <servlet> et <servlet-mapping> dans le descripteur de déploiement de l application web (fichier projet /war/web-inf/web.xml) et pointer vers la classe d implémentation (EtudiantServiceImpl). 1 [...] 2 <web -app > 3 <welcome -file -list > 4 <welcome -file > Etudiant.html </ welcome -file > 5 </ welcome -file -list > 6 7 <servlet > 8 < servlet - name > etudiantserviceimpl </ servlet - name > 9 <servlet - class > com.lp. student. server. EtudiantServiceImpl </ servlet - class > 10 </ servlet > 11 12 <servlet - mapping > 13 < servlet - name > etudiantserviceimpl </ servlet - name > 14 <url - pattern >/ student / manager </ url - pattern > 15 </ servlet - mapping > 16 </web -app > 8.3.2 Invocation du service Les appels de méthodes depuis un client se font de manière asynchrone afin de suivre la logique du mécanisme proposé par AJAX et ainsi de concevoir des applications web de type RIA. Nous avons besoin d ajouter un paramètre AsyncCallback sur toutes les méthodes d un service. Pour cela, nous devons définir une nouvelle interface comme suit : elle doit avoir le même nom de que l interface de service suffixé avec le mot-clé Async (p. ex. EtudiantServiceAsync), elle doit être situé dans le même package que l interface de service (client), chaque méthode doit avoir le même nom et la même signature que celles présente dans l interface de service avec une différence importante : une méthode ne retourne rien et le dernier paramètre est un objet de type AsyncCallback. Question 2 Encore une fois, à l aide d Eclipse, créer l interface préalablement décrite et expliquer son fonctionnement en commentaire. Ne pas hésiter à utiliser le moteur d Eclipse pour la génération automatique de cette interface. Une interface de service sans son correspondant asynchrone entrainera une erreur de la part d Eclipse. La correction automatique s occupera de générer le corps de l interface. Lorsque de l appel d une méthode distante, il est possible de spécifier une méthode callback qui sera appelée lorsque l appel sera terminé. La spécification d une méthode callback se fait en passant un objet de type AsyncCallback à la classe proxy. Un objet AsyncCallback contient deux méthodes appelées selon que le résultat de l appel distant soit une réussite onsuccess() ou un échec (onfailure()). T. Morsellino, version du November 6, 2012 4 / 7
Question 3 La procédure à suivre est la suivante : 1. Dans la classe principale de votre projet, créer une instance de la classe proxy du service en appelant GWT.create(). 1 private List < Etudiant > etds = new ArrayList < Etudiant >() ; 2 private EtudiantServiceAsync etudiantservice = GWT. create ( EtudiantService. class ); 2. initialiser le classe proxy du service, spécifier l objet callback et faire appel à un procédure distante. 1 [...] 2 3 if ( etudiantservice == null ) 4 etudiantservice = GWT. create ( EtudiantService. class ); 5 6 AsyncCallback < Etudiant [] > callback = new AsyncCallback < Etudiant [] >() { 7 public void onfailure ( Throwable caught ) { 8 // TODO : en cas d erreur. 9 } 10 11 public void onsuccess ( Etudiant [] result ) { 12 // TODO : en cas de succes. 13 } 14 }; 15 16 // appel a la methode du service. 17 etudiantservice. getlist ( callback ); Dès lors, nous avons créé un service, fait en sorte de pouvoir l utiliser (modification du fichier web.xml), mis en place un mécanisme permettant des appels asynchrones et générer un service proxy sur le client pour appeler ce service. Testons-le : Question 4 1. lancer l application (en mode développement) en utilisant le débogueur Eclipse, 2. regarder les messages d erreur dans la console, 3. conclure sur une éventuelle solution. La sérialisation permet de packager le contenu d un objet de telle sorte qu il est possible de l échanger entre différentes applications ou à travers un réseau. Ainsi, l utilisation de GWT RPC impose que les objets échangés soit sérialisables, aussi bien les paramètres de méthodes que les retours : tous les types primitifs (int, char, boolean,... ) ainsi que les objets Java correspondants (Integer, Char, Boolean) sont sérialisables par défaut, un tableau composé d éléments sérialisables est sérialisable, une classe est sérialisable si toutes les conditions suivantes sont satisfaites : elle implémente l interface Java Serializable ou GWT IsSerializable soit directement soit indirectement par une classe intermédiaire, les attributs non finaux, non transients sont eux aussi sérialisables, il existe un constructeur par défaut (public, privé ou protéger). GWT préserve la politique imposée par le mot-clé transient : les valeurs associées à ces attributs ne sont pas sérialisées (et donc non échangées durant un appel RPC), la sérialisation GWT est différent de celle proposée par défaut par Java. Pour plus de détails, se référer à la documentation Google. T. Morsellino, version du November 6, 2012 5 / 7
8.3.3 Gestion des erreurs Lorsque l appel à une procédure distante échoue, la cause peut être classée en deux catégories : exception inattendue : problème réseau, problème au niveau du serveur web ou d un serveur DNS ou autre. Il se peut, comme dans le cas d une application Java classique, qu une exception inattendue soit levée comme NullPointerException. Lors de la levée d une exception inattendue, il est possible de trouver la trace complète de la pile d exécution dans la console de développement. Côté client, la méthode callback onfailure() recevra une exception de type InvocationException avec un message générique : l appel a échoué sur le serveur. Voir les logs complets pour le détail. exception vérifiée/attendue : erreurs attendues gérées par l application elle-même. GWT préserve la gestion des exceptions Java (mots-clés throws et throw). Noter toutefois que les exceptions doivent être aussi sérialisables. Question 5 Toute classe d exception étend la classe Exception et implémente Serializable : 1. Créer une classe DoublonException permettant de gérer les doublons dans la liste d étudiants. 2. Mettre à jour l interface du service en spécifiant les méthodes pouvant être susceptible de lever une exception de type DoublonException : 19 public interface EtudiantService extends RemoteService { 20 21 void addetds ( Etudiant e) throws DoublonException ; 22 23 } 3. Mettre à jour les implémentations des méthodes concernées : 24 public void addetds ( Etudiant e) throws DoublonException { 25 [...] 26 27 if ( listetds. contains (e)) { 28 throw new DoublonException ( e); 29 } 30 31 [...] 32 } 4. Ajouter un widget de type label invisible permettant d afficher, si besoin, un message d erreur (voir la documentation et la méthode setvisible(boolean)), 5. Ajouter un mécanisme de traitement d erreur dans la méthode callback correspondante : 33 public void onfailure ( Throwable caught ) { 34 // si doublon alors exception 35 String details = caught. getmessage (); 36 if ( caught instanceof DoublonException ) { 37 details = " L etudiant " + 38 (( DoublonException ) caught ). getetudiant (). getname () + " existe deja."; 39 } 40 41 errormsglabel. settext (" Erreur : " + details ); 42 errormsglabel. setvisible ( true ); 43 } Dans le cas où tout se passe correctement ensuite (aucune exception levée), ne pas oublier d effacer le message d erreur ensuite. Tester l application à chaque étape! T. Morsellino, version du November 6, 2012 6 / 7
8.4 Pour aller plus loin (et s avancer pour le prochain TP) L échange d objets s effectue, jusqu à présent, par l intermédiaire de sérialisation et de désérialisation en un format (propriétaire à Google). Pour des raisons évidentes, ce format n est pas forcément une bonne solution dès lors que notre application doit échanger des informations avec d autres applications. Il existe plusieurs formats pour le transport de données à travers un réseau comme le format XML. Réputé comme étant très verbeux, XML est de moins en moins utilisé. Il est préférable d utiliser un format plus léger, lisible, interopérable et surtout simple à traiter automatiquement comme JSON. Voici un exemple d une liste d étudiant en JSON : 1 [ 2 { 3 " nom ": " Toto ", 4 " prenom ": "L asticot " 5 " age ": 22 6 }, 7 { 8 " nom ": " Mickey ", 9 " prenom ": " Mouse ", 10 " age ": 140 11 }, 12 { 13 " nom ": " Luke ", 14 " prenom ": " Donald - Skywalker ", 15 " age ": 39 16 } 17 ] Question 6 En s aidant de la documentation (et de son instinct) : faire en sorte que le serveur renvoie la liste d étudiant au format JSON au client, traiter cette liste JSON coté client et l afficher comme auparavant dans un widget. Question 7 Afin de s avancer pour le TP/Projet suivant : réfléchir à des scénarios d utilisation d une TODO-Liste simple, proposer des tests unitaires, proposer une architecture logicielle simplifiée, commencer le développement de cette application. Ce document est publié sous Licence Creative Commons By- NonCommercial-ShareAlike. Cette licence vous autorise une utilisation libre de ce document pour un usage non commercial et à condition d en conserver la paternité. Toute version modifiée de ce document doit être placée sous la même licence pour pouvoir être diffusée. http://creativecommons.org/licenses/by-nc-sa/2.0/fr/ T. Morsellino, version du November 6, 2012 7 / 7