I. Concepts généraux▲
Un service Web (ou Web service, en anglais) est un objet hébergé par un serveur HTTP sur lequel il est possible d'invoquer des méthodes à distance. Chaque appel de méthode est réalisé en transportant la valeur des paramètres via une requête HTTP (donc, du poste client vers le serveur HTTP) et en transportant la valeur de retour de la méthode distante considérée via une réponse HTTP (du serveur vers le client). Une autre caractéristique des services Web réside dans l'interopérabilité de la technologie : qu'il s'agisse du code client ou du service Web, ils peuvent tous les deux être développés via différentes technologies (Java, .Net, PHP…).
En fait, la technologie relative aux services Web est une recommandation du W3C (World Wide Web consortium : http://www.w3.org). Le W3C est un consortium réunissant de multiples entreprises ayant un intérêt à promouvoir et à faire développer les technologies du Web. La recommandation est le niveau le plus abouti dans le processus de standardisation du W3C. Les services Web représentent donc une technologie mature : en tant que telle, elle est notamment supportée par le Java SE 6.0.
L'intérêt de cette technologie est de mettre les données présentes sur le Web à disposition de personnes physiques (via les pages Web) mais aussi de programmes (via les services Web). On peut donc imaginer deux serveurs Web, pour deux entreprises différentes, échangeant des données via cette technologie dans le but de produire une unique proposition commerciale intégrant des informations des deux entreprises considérées. Dans un tel cas, les deux serveurs échangent l'information par de simples appels de méthodes (donc, avec des paramètres et des valeurs de retour) sur des objets de type « service Web ».
L'intérêt de faire héberger vos services Web par un serveur HTTP réside dans le fait que par défaut, les requêtes/réponses réseaux passent par le port 80. Si entre le client qui émet la requête et le serveur qui fournit le service, l'infrastructure réseau utilisée met en jeux des firewalls ou des proxys HTTP, cela ne posera à priori aucun problème, car sur ces systèmes, le port 80 est normalement ouvert. Notez aussi que l'utilisation de services Web peut permettre de bâtir une architecture SOA (Service Oriented Architecture).
II. SOAP (Simple Object Access Protocol) et WSDL (Web Service Description Language)▲
Avec les services Web, les requêtes et les réponses HTTP contiendront des informations structurées via la syntaxe XML (elle aussi étant une recommandation du W3C). N'oubliez pas que XML est un méta langage : il sert à produire des langages adaptés à vos besoins, mais basés sur une syntaxe à base de tags.
Deux langages XML (on parle aussi de grammaires XML) sont très importants pour la compréhension du fonctionnement des services Web. Il s'agit de SOAP (Simple Object Access Protocol) et de WSDL (Web Service Description Language).
SOAP permet de structurer les données qui vont être échangées entre l'utilisateur du service et le fournisseur (le service Web). Bien qu'on puisse envisager d'autres alternatives à XML, ce choix permet de facilement garantir l'interopérabilité. Effectivement, il existe des parseurs XML sur quasiment toutes les plates-formes (Solaris, Linux…) et pour tous les langages (Java, C++, PHP…).
L'intérêt de WSDL est un petit peu plus subtil à cerner. Disons, pour simplifier que les deux équipes qui vont développer le service Web et le code du consommateur du service, n'ont pas l'obligation de se connaître. Elles peuvent, qui plus est, être localisées n'importe où sur le globe, cela ne gênera pas pour le bon fonctionnement du modèle. Or, un appel de méthode sur un objet distant, dans l'absolu, ne veut strictement rien dire : si on descend au niveau du code machine, on peut dire qu'un appel de méthode ne peut fonctionner qu'au sein d'un même processus (du moins pour les processeurs modernes). Donc, l'appel au niveau de l'application cliente est réalisé entre votre code et un représentant local du service Web. Ce représentant local (souvent appelé proxy - à ne pas confondre avec les proxy HTTP) nous permet de « simuler » l'appel distant dans le sens ou il se comporte comme l'objet distant. Il fournit exactement les mêmes méthodes, mais leurs implémentations se chargent de réaliser la communication HTTP. La question est de savoir comment on génère ce représentant local. C'est là qu'intervient le WSDL : cette description des méthodes accessibles via ce service Web servira à un outil (wsimport avec le Java SE 6.0) pour générer le proxy. Qui plus est, le WSDL peut être récupéré via une simple requête HTTP sur le service. Donc, oui, les deux équipes de développement peuvent ne pas se connaître et ne pas avoir discuté sur la manière d'utiliser le service Web considéré.
III. Présentation de L'API JAX-WS▲
Historiquement parlant, la première API ayant permis le développement de services Web avec le langage Java fut JAX-RPC. Ce modèle était relativement lourd à mettre en œuvre et n'utilisait pas les dernières technologies objets introduites dans le Java SE 5.0 (je pense, bien entendu, aux annotations Java). Ces considérations ont abouti à produire un second modèle de mise en œuvre de services Web.
Il est donc maintenant préférable d'utiliser l'API JAX-WS (Java Api for Xml - Web Services). Cette API est notamment localisée dans le package javax.jws. Si vous prenez soin d'ouvrir la Javadoc sur ce package, vous vous apercevrez qu'elle est constituée d'un certain nombre d'annotations (javax.jws.WebService et javax.jws.WebMethod notamment). Cela induit le fait que la nouvelle API de mise en œuvre de services Web est très simple d'emploi : il suffit de coder une classe Java traditionnelle et de lui adjoindre un certain nombre d'annotations pour qualifier les méthodes qui seront accessibles via l'extérieur.
À titre d'exemple, nous allons considérer une application de chat en ligne accessible via le Web. Cette application est découpée en deux parties bien distinctes : la console cliente, permettant d'afficher les messages échangés et le composant serveur faisant office de salle de discussion. C'est sur cette dernière partie que nous allons commencer à porter notre attention. La mise en œuvre, vous allez vous en rendre compte, est relativement simple. Une seule classe de type service Web va nous suffire pour coder la partie serveur. Nous appellerons cette classe ChatRoomServiceImpl.
Cette classe va comporter cinq méthodes accessibles via HTTP: subscribe, unsubscribe, sendMessage, getMessages et getUsers. Voici un descriptif rapide de la responsabilité de chacune des ces méthodes.
@WebMethod
public
void
subscribe
(
String pseudo ) : cette méthode permet de s'inscrire à la salle de discussion considérée. Effectivement, HTTP est un protocole déconnecté et, qui plus est, l'architecture Web ne permet pas d'envoyer une information au navigateur (du moins à l'initiative du serveur HTTP) : c'est donc l'application cliente qui doit périodiquement interroger la salle de discussion pour savoir si de nouveaux messages ont été reçus. La salle de discussion doit donc, pour chaque utilisateur, mémoriser les nouveaux messages qui ont été reçus. Dans le système, un utilisateur sera identifié par son pseudo.@WebMethod
public
void
unsubscribe
(
String pseudo ) : à l'inverse de la précédente, cette méthode permet de désinscrire une personne de la salle de discussion. À partir de là, il n'est plus nécessaire de mémoriser les nouveaux messages pour cet utilisateur.@WebMethod
public
void
sendMessage
(
String pseudo, String message ) : cette méthode est utilisée par le client pour lui permettre d'envoyer une message à la salle de discussion. Cette méthode accepte deux paramètres : le pseudo et le message à proprement parler.@WebMethod
public
ArrayListgetMessages
(
String pseudo ) : cette méthode permet de pouvoir retrouver l'ensemble des messages reçus par cet utilisateur depuis la précédente invocation de la méthode getMessages. Cette méthode sera invoquée périodiquement.@WebMethod
public
ArrayListgetUsers
(
) : permet de retrouver tous les utilisateurs connectés à la salle de discussion. Cette méthode sera invoquée périodiquement.
Voici le code complet de la classe de salle de discussion. Notez l'utilisation des annotations JAX-WS. Notez aussi qu'une méthode statique main est adjointe : elle permet de lancer le service Web en stand-alone sans forcément nécessiter de serveur HTTP. Cela est fort pratique pour tester le service Web.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
package
corelib.services.web.samples.webservices;
import
java.util.ArrayList;
import
java.util.Hashtable;
import
javax.jws.WebMethod;
import
javax.jws.WebService;
import
javax.xml.ws.Endpoint;
@WebService
(
serviceName =
"ChatRoomService"
, name =
"ChatRoom"
)
public
class
ChatRoomServiceImpl {
private
Hashtable<
String, ArrayList<
String>>
userMessages =
new
Hashtable<
String, ArrayList<
String>>(
);
@WebMethod
public
void
subscribe
(
String pseudo ) {
sendMessage
(
"ChatRoom"
, "User "
+
pseudo +
" connected"
);
userMessages.put
(
pseudo, new
ArrayList
(
) );
}
@WebMethod
public
void
unsubscribe
(
String pseudo ) {
userMessages.remove
(
pseudo );
sendMessage
(
"ChatRoom"
, "User "
+
pseudo +
" disconnected"
);
}
@WebMethod
public
void
sendMessage
(
String pseudo, String message ) {
String fullMessage =
pseudo +
" >>> "
+
message;
System.out.println
(
fullMessage );
for
(
ArrayList<
String>
array : userMessages.values
(
) ) {
array.add
(
fullMessage );
}
}
@WebMethod
public
ArrayList<
String>
getMessages
(
String pseudo ) {
if
(
pseudo ==
null
) return
null
;
ArrayList<
String>
messages =
(
ArrayList) userMessages.get
(
pseudo );
if
(
messages ==
null
) return
null
;
userMessages.put
(
pseudo, new
ArrayList
(
) );
return
messages;
}
@WebMethod
public
ArrayList<
String>
getUsers
(
) {
ArrayList<
String>
users =
new
ArrayList
(
);
if
(
users ==
null
) return
null
;
for
(
String user : userMessages.keySet
(
) ) users.add
(
user );
return
users;
}
public
static
void
main
(
String[] args ) {
// For standalone tests
Endpoint.publish
(
"http://localhost:8080/webapp/simple"
, new
ChatRoomServiceImpl
(
) );
}
}
Attention : ce code est un prototype : il pourrait sans aucune difficulté être amélioré pour permettre un fonctionnement encore plus poussé. Par exemple, avec cette implémentation, si deux utilisateurs se connectent avec exactement le même pseudo, les choses risquent de ne pas bien se passer.
IV. Déploiement d'un service Web dans une application Web▲
Pour déployer le service Web sur un serveur HTTP (Tomcat en l'occurrence), deux dernières étapes sont nécessaires. La première consiste à rajouter une configuration dans le fichier WEB-INF/web.xml. Le framework Ellipse gère notamment deux types d'éléments Web : les pages Web (d'extension .wp) et les services Web (d'extension .ws). Les extensions proposées ne sont absolument pas imposées : si vous souhaitez les changer, vous le pouvez. Vous pourriez même faire en sorte que ces deux types de composants utilisent une même extension. Mais, la proposition qui vous est faite a l'avantage de nous rappeler immédiatement la nature du fichier auquel nous avons à faire. Voici donc la configuration minimale permettant l'invocation d'un service Web (voir lignes 19 à 22).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd"
>
<web-app>
<!-- Begin of file -->
<servlet>
<servlet-name>
Ellipse Servlet</servlet-name>
<servlet-class>
corelib.services.web.webapplications.ControllerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>
Ellipse Servlet</servlet-name>
<url-pattern>
*.wp</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>
Ellipse Servlet</servlet-name>
<url-pattern>
*.ws</url-pattern>
</servlet-mapping>
<!-- End of file -->
</web-app>
La seconde étape consiste à définir le fichier d'extension .ws qui servira de point d'invocation du service Web (on parle de « end point »). Ce fichier ressemble, d'une certaine manière, à une page Web Ellipse dans le sens ou il référence la classe de Web service à utiliser. À terme, le framework Ellipse devrait permettre de rajouter d'autres informations dans ce fichier.
2.
3.
<?xml version="1.0" encoding="ISO-8859-1" ?>
<
web
:
Service
xmlns
:
web
=
"corelib.services.web.webservices"
codeBehind
=
"corelib.services.web.samples.virtualcaddy.webservices.ChatRoomServiceImpl"
/>
Vous pouvez maintenant déployer votre application Web sur votre serveur HTTP. Afin de tester que le service Web est correctement déployé, vous pouvez invoquer l'URL suivante : http://localhost:8080/VirtualCaddy/ChatRoom.ws?wsdl. Si tout ce passe bien, le WSDL de votre service devrait vous être retourné.
Pour ceux qui auraient déjà testé certaines solutions de déploiement de services Web (avec Metro sous Tomcat par exemple), notez un point remarquable : les générations du proxy serveur ainsi que du WSDL (normalement réalisé via l'outil wsgen fournit par le Java SE) sont ici complètement automatisées. C'est lors de la première invocation du service Web que les informations manquantes seront produites. Il en découle un point important : pour fonctionner, Ellipse a besoin d'un Java SE 6.0 (pas d'un JRE) sans quoi l'outil de production wsgen ne serait pas disponible.
V. Utilisation du plugin Ellipse pour produire un nouveau service Web▲
Comme nous venons de le voir, un minimum de deux fichiers est utile pour la mise en œuvre d'un service Web : la classe d'implémentation du service et le fichier XML définissant l'URL d'attaque du service. Vous pouvez produire ces deux fichiers en une seule manipulation si vous utilisez le plugin Ellipse (un plugin s'intégrant sur l'IDE Eclipse). Pour ce faire, placez-vous dans l'explorateur de paquetages de votre Eclipse, cliquez avec le bouton droit de la souris et sélectionnez « New », puis « Web Service ». Une boîte de dialogue devrait s'ouvrir : en voici une capture d'écran.
Renseignez-y correctement les informations relatives de votre nouveau service Web et vos deux fichiers devraient être correctement créés.
VI. Appel d'un service Web en Java▲
La première chose à faire pour pouvoir coder notre application cliente est de générer le proxy client. Pour ce faire, le Java SE 6.0 met à notre disposition l'outil wsimport. Cet outil prend en paramètre de ligne de commande l'adresse Web du WSDL de votre service. La ligne de commande suivante devrait donc vous permettre de produire ce proxy. Notez bien que ce sont directement les fichiers .class qui seront produits (vous pourriez aussi demander à obtenir les .java : option -keep).
$> wsimport http://localhost:8080/VirtualCaddy/ChatRoom.ws?wsdl
Ensuite il vous faut coder votre application cliente. Dans l'exemple ci-dessous, nous nous contentons de nous inscrire à la salle de discussion, d'envoyer un message, de demander tous nos messages disponibles, de lister les utilisateurs connectés et enfin de nous désinscrire de la salle de discussion (le tout séquentiellement).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
import
corelib.services.web.samples.virtualcaddy.webservices.*;
import
java.util.*;
public
class
Client {
public
static
void
main
(
String [] args ) {
ChatRoom room =
new
ChatRoomService
(
).getChatRoomPort
(
);
room.subscribe
(
"Donald"
);
List users =
room.getUsers
(
);
for
(
Object user : users ) System.out.println
(
user );
room.sendMessage
(
"Donald"
, "Trop fort"
);
List messages =
room.getMessages
(
"Donald"
);
for
(
Object message : messages ) System.out.println
(
message );
room.unsubscribe
(
"Donald"
);
}
}
Vous pouvez maintenant compiler votre application cliente et l'exécuter. Normalement elle devrait se connecter correctement à votre service Web. Et vous devriez vous passer des messages dans les logs du serveur HTTP utilisé. Dans un prochain tutoriel nous verrons comment utiliser ce service Web au travers d'une IHM basée sur HTML/JavaScript en utilisant le framework Ellipse : effectivement, le framework propose une API permettant l'appel de services Web directement à partir de JavaScript.
VII. Échange d'objets via un service Web▲
Les services Web permettent aussi d'échanger des paramètres (ainsi qu'une valeur de retour de méthode) de type objet. Pour ce faire, les classes utilisées pour les données échangées doivent fournir un constructeur par défaut. Si ce n'est pas le cas, une exception sera déclenchée. À titre d'exemple, voici un objet de catalogue distant qui pourrait permettre de manipuler des instances d'une classe d'articles.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
package
corelib.services.web.samples.virtualcaddy.webservices;
import
javax.jws.WebMethod;
import
javax.jws.WebService;
import
corelib.services.web.samples.virtualcaddy.business.Article;
import
corelib.services.web.samples.virtualcaddy.business.ArticleHome;
@WebService
(
serviceName =
"CatalogBrowserService"
, name =
"CatalogBrowser"
)
public
class
CatalogBrowserServiceImpl {
@WebMethod
public
Article [] getAllArticles
(
) {
return
ArticleHome.findAll
(
);
}
@WebMethod
public
Article getArticle
(
int
idArticle ) {
return
ArticleHome.findByPrimaryKey
(
idArticle );
}
}
Il faut bien comprendre que le service Web pourra être utilisé par un autre langage que Java (.NET, JavaScript…). La terminologie d'échange d'objets est donc peut-être un peu exagérée. En fait, seules les données des objets seront transférées entre le client et le service Web (et non les méthodes, dont le code est spécifique à un environnement d'exécution). L'application cliente cherchera donc à représenter les objets du service Web, via des types similaires, mais qui exposeront seulement les propriétés publiques des objets serveur.
VIII. Remerciements▲
Merci à Mickael Baron d'avoir cru en Ellipse Framework et de nous avoir permis de publier nos tutoriels sur « Developpez.com ».
Merci à Jacques Jean pour sa relecture orthographique.
Merci à tous ceux qui font confiance à Ellipse Framework.
L'équipe Ellipse.