Les services Web avec Java¶
Note
Pour ce chapitre, vous aurez besoin de l’outil SoapUI. Vous devez le télécharger à l’adresse https://www.soapui.org/downloads/soapui/source-forge.html
La quête de l’interopérabilité¶
Une problématique courante dans la mise en place de systèmes d’information et de savoir comment échanger des informations entre plusieurs processus. Cette problématique est souvent résumée par la notion d’interopérabilité. D’un point de vue technique, l’interopérabilité est rendue plus complexe par un ou plusieurs facteurs :
les programmes sont écrits dans des langages différents
les processus s’exécutent sur des machines différentes
les machines appartiennent à des réseaux différents
Avant l’an 2000, on trouve des solutions telles que Sun RPC, CORBA, DCOM, RMI. À partir des années 2000, le succès des réseaux IP, de HTTP et de XML apporte une nouvelle perspective : définir un protocole d’échange de messages XML via HTTP entre deux processus.
Ce protocole originellement défini par Microsoft puis maintenu par le W3 s’appelle SOAP. Depuis SOAP a été enrichi de nombreuses spécifications et l’ensemble de ces technologies sont désignées sous le terme WS-*.
WS-* est un ensemble de technologies, il ne s’agit ni d’un modèle de conception ni d’un modèle d’architecture.
SOAP : structure des messages¶
SOAP est une recommandation pour l’échange de messages au format XML entre un expéditeur (SOAP sender) et un destinataire (SOAP receiver). La structure des messages SOAP est identique qu’elle provienne de l’expéditeur (requête) ou du destinataire (réponse). Un message est constitué par une enveloppe (SOAP envelope) qui peut contenir un en-tête (SOAP header) et ensuite une charge utile (SOAP body).
Pour une utilisation avec le protocole HTTP, le message doit être envoyé avec la méthode POST à l’URL du service (appelée aussi endpoint)
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:m="http://www.mymeteo.com/meteo">
<soapenv:Header>
</soapenv:Header>
<soapenv:Body>
<m:releve>
<m:temperature>
<m:valeur>6.0</m:valeur>
<m:unite>celsius</m:unite>
</m:temperature>
<m:lieu>
<m:ville>Bordeaux</m:ville>
</m:lieu>
</m:releve>
</soapenv:Body>
</soapenv:Envelope>
Dans l’exemple ci-dessus le message contient de toute évidence un relevé météorologique pour la ville de bordeaux. Ce message peut aussi bien être une requête d’un expéditeur qu’une réponse d’un destinataire (la structure des messages est la même).
Note
Du fait de son utilisation exclusive du XML pour la composition des messages, une bonne compréhension de SOAP nécessite une connaissance approfondie de XML (Cf. le complément au cours sur XML).
Un message SOAP est défini pour le namespace XML http://schemas.xmlsoap.org/soap/envelope/
<!-- l'enveloppe du message -->
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<!-- l'en-tête du message -->
<soapenv:Header>
<!-- les éventuelles méta-informations -->
</soapenv:Header>
<!-- le corps de la requête -->
<soapenv:Body>
<!-- le message (payload) -->
</soapenv:Body>
</soapenv:Envelope>
SOAP définit un format pour un type de réponse particulière : une SOAP fault. Une SOAP fault signale un problème lors du traitement du service.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Server</faultcode>
<faultstring>Internal server error</faultstring>
</soap:Fault>
</soap:Body>
</soap:Envelope>
Pour l’élément faultcode
, on peut utiliser les valeurs :
{http://schemas.xmlsoap.org/soap/envelope/}:Server pour signaler que l’origine de l’incident provient du traitement du serveur
{http://schemas.xmlsoap.org/soap/envelope/}:Client pour signaler que l’origine de l’incident provient de la requête du client
Web Service Description Language (WSDL)¶
La condition pour utiliser un service Web SOAP est de connaître les opérations disponibles et le format des messages autorisés en entrée et/ou en sortie de ses opérations. WSDL (Web Service Description Language) est un langage XML permettant la description complète d’un service Web. Ainsi un fichier WSDL est analysable par programme et on trouve des outils dans différents langages de programmation pour générer du code (les stubs) facilitant le développement des programmes (client ou serveur).
Note
La plupart des stacks SOAP donne accès au WSDL en ligne (en le générant dynamiquement si nécessaire).
Par exemple, pour les serveurs d’application Java EE, le WSDL est disponible à la même URL que le service en ajoutant wsdl comme paramètre.
http://localhost:8080/MeteoService?wsdl
<?xml version='1.0' encoding='UTF-8'?>
<definitions
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata"
xmlns:tns="http://www.mymeteo.com/webservices/meteo"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ns1="http://www.mymeteo.com/meteo"
targetNamespace="http://www.mymeteo.com/webservices/meteo"
name="MeteoService"
>
<!-- Les types de données au format XML schema -->
<types>
<xs:schema version="1.0" targetNamespace="http://www.mymeteo.com/meteo"
elementFormDefault="qualified">
<xs:complexType name="temperature">
<xs:sequence>
<xs:element name="valeur" type="xs:double"/>
<xs:element name="unite" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="lieu">
<xs:sequence>
<xs:element name="ville" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="releve">
<xs:sequence>
<xs:element name="temperature" type="ns1:temperature"/>
<xs:element name="lieu" type="ns1:lieu"/>
</xs:sequence>
</xs:complexType>
<xs:element name="lieu" type="ns1:lieu"/>
<xs:element name="releve" type="ns1:releve"/>
</xs:schema>
</types>
<!--
La description de tous les messages possibles.
Un message est défini par un ensemble de parties (une partie pour le payload
et une partie par en-tête)
-->
<message name="releveMeteo">
<part name="partLieu" element="ns1:lieu"/>
</message>
<message name="releveMeteoResponse">
<part name="partReleve" element="ns1:releve"/>
</message>
<!--
Description des interfaces (indépendantes de SOAP)
Une interface est composée d'un ensemble d'opérations.
Chaque opération est définie par les messages en entrée et en sortie.
-->
<portType name="MeteoService">
<operation name="releveMeteo">
<input
wsam:Action="http://www.mymeteo.com/webservices/meteo/MeteoService/releveMeteoRequest"
message="tns:releveMeteo"/>
<output
wsam:Action="http://www.mymeteo.com/webservices/meteo/MeteoService/releveMeteoResponse"
message="tns:releveMeteoResponse"/>
</operation>
</portType>
<!-- Liaison des interfaces avec le protocole SOAP -->
<binding name="MeteoServicePortBinding" type="tns:MeteoService">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="releveMeteo">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<!--
Localisation du service
Dans le cas d'un binding SOAP avec un transport HTTP, on trouve ici l'URL du service.
-->
<service name="MeteoService">
<port name="MeteoServicePort" binding="tns:MeteoServicePortBinding">
<soap:address location="http://www.mymeteo.com/ws/meteo"/>
</port>
</service>
</definitions>
Développer un service Web SOAP¶
Pour le langage Java, on dispose de JAX-WS qui est l’API permettant d’implémenter des services Web et JAXB (Java API for XML Binding) permettant d’associer une classe Java à une représentation XML.
On distingue deux approches pour développer un service Web WS-* :
- Contract first
On définit le contrat du service en spécifiant le WSDL et on utilise des outils pour générer le code des stubs.
- Contract last
On développe le service et on utilise des outils pour générer le WSDL.
Exercice¶
Accéder à un service Web
Testez le service Web SOAP disponible à cette adresse : http://ws-meteo.herokuapp.com/
Pour tester ce service Web, vous pouvez utiliser le client SoapUI.
Les outils Java de génération¶
Il est possible d’utiliser le plugin maven JAX-WS pour générer les classes Java à partir d’un fichier WSDL. Il est possible de développer une application cliente et/ou une application serveur à partir des classes générées.
WSDL → Java (Contract first)¶
Si on dispose du fichier WSDL, on peut générer le code Java. On dit que l’on
part du contrat de service : c’est l’approche contract first. On utilise
pour cela l’outil wsimport
.
<build>
<plugins>
<plugin>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>2.3.2</version>
<executions>
<execution>
<id>wsimport</id>
<goals>
<goal>wsimport</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<wsdlDirectory>src/main/wsdl</wsdlDirectory>
<sourceDestDir>src/main/java</sourceDestDir>
<xnocompile>true</xnocompile>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
La configuration ci-dessus déclare pour Maven de sélectionner tous les fichiers
*.wsdl
qui se trouvent dans le répertoire src/main/wsdl
et de
générer le code Java correspondant dans le répertoire src/main/java
.
Astuce
Pour générer les fichiers depuis Eclipse, faites un clic droit sur votre projet et sélectionnez Run As > Maven generate-sources.
Consommer un service Web avec JAX-WS¶
Une application cliente d’un service Web SOAP peut utiliser un stub. Un stub est une classe générée qui masque la complexité des échanges de messages avec le serveur.
Il est possible de créer un client à partir du code généré par wsimport.
// L'URL du WSDL (peut-être un fichier sur disque ou un lien HTTP)
java.net.URL wsdlDocumentLocation = new java.net.URL("file:HelloService.wsdl");
// Le qualified name du service spécifié par les attributs
// targetNamespace et name de la balise racine dans le WSDL
javax.xml.namespace.QName serviceName =
new javax.xml.namespace.QName("http://spoonless.github.io/ws/hello","HelloService");
// Creation d'une stub vers le service Web
javax.xml.ws.Service service = javax.xml.ws.Service.create(wsdlDocumentLocation, serviceName);
// La SEI offrant les méthodes du service Web sous la forme d'un interface Java
// L'interface est celle générée par wsimport
HelloService helloService = service.getPort(HelloService.class);
// Exemple d'appel du service
System.out.println(helloService.sayHello("world"));
Le code précédent pose un problème : le client utilise l’URL du service spécifiée dans la section wsdl:service du WSDL. Il est souvent utile de pouvoir changer dynamiquement l’URL du endpoint.
// ...
// La SEI offrant les méthodes du service Web sous la forme d'un interface Java
// L'interface est celle générée par wsimport
HelloService helloService = service.getPort(HelloService.class);
// Un service implémente l'interface javax.xml.ws.BindingProvider
javax.xml.ws.BindingProvider bp = (javax.xml.ws.BindingProvider) helloService;
// ENDPOINT_ADDRESS_PROPERTY est une propriété donnant l'URL du endpoint
bp.getRequestContext().put(javax.xml.ws.BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
"http://monendpoint.com/monservice");
// Exemple d'appel du service
System.out.println(helloService.sayHello("world"));
Exercice¶
Écrire un programme client d’un service Web
Écrivez une client pour le service Web SOAP disponible à cette adresse : http://ws-meteo.herokuapp.com/.
Par exemple, vous pouvez écrire un programme qui accepte le nom d’une ville en paramètre et qui affiche la température obtenue pour cette ville après un appel au service Web.
- Modèle de projet
Vous pouvez télécharger un modèle de projet Maven. Ajoutez un fichier WSDL dans le répertoire
src/main/wsdl
et faites générer les classes Java à partir de ce contrat. Pour cela, vous devez lancer la cible Mavengenerate-sources
. Dans Eclipse, vous pouvez appeler Maven en faisant un clic droit sur le nom de votre projet et en choisissant Run As > Maven generate-sources.
Implémenter des services Web avec JAX-WS¶
L’implémentation d’un service Web passe par l’écriture et l’implémentation d’une SEI (Service Endpoint Implementation). Cette interface et son implémentation peuvent être fournies par le développeur ou peuvent être générées à partir d’un ficher WSDL (Cf. ci-dessous).
package dev.gayerie;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
@WebService(targetNamespace="http://spoonless.github.io/ws/hello")
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface HelloService {
@WebMethod
@WebResult(name = "helloMessage", targetNamespace = "http://spoonless.github.io/ws/hello")
public String sayHello(
@WebParam(name = "who", targetNamespace="http://spoonless.github.io/ws/hello") String name);
}
package dev.gayerie;
import javax.jws.WebService;
@WebService(endpointInterface="dev.gayerie.HelloService",
targetNamespace="http://spoonless.github.io/ws/hello", serviceName="HelloService")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "Hello " + name;
}
}
Note
Il est également possible de définir une SEI directement avec une classe Java sans passer par une interface :
package dev.gayerie;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
@WebService(targetNamespace="http://spoonless.github.io/ws/hello", serviceName="HelloService")
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public class HelloService {
@WebMethod
@WebResult(name = "helloMessage", targetNamespace = "http://spoonless.github.io/ws/hello")
public String sayHello(
@WebParam(name = "who", targetNamespace="http://spoonless.github.io/ws/hello") String name) {
return "Hello " + name;
}
}
Cette SEI permet de gérer un échange de messages comme celui-ci :
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:hel="http://spoonless.github.io/ws/hello">
<soapenv:Header/>
<soapenv:Body>
<hel:who>world</hel:who>
</soapenv:Body>
</soapenv:Envelope>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<helloMessage xmlns="http://spoonless.github.io/ws/hello">Hello world</helloMessage>
</soapenv:Body>
</soapenv:Envelope>
Nous allons maintenant décrire les annotations Java utilisées pour
transformer la classe HelloService
en service Web :
- @WebService
Désigne une classe comme étant une SEI ou une implémentation d’une SEI. Elle possède entre-autres les attributs suivants :
- serviceName
Le nom du service tel qu’exposé dans le WSDL
- targetNamespace
Le namespace XML du service tel qu’exposé dans le WSDL
- endpointInterface
Désigne l’interface Java correspondante au SEI. À spécifier sur la classe d’implémentation du SEI.
- @SOAPBinding
Indique le support SOAP pour le service. Les informations données par les attributs de cette annotation correspondent pour la plupart à celles que l’on trouve dans la section
binding
du WSDL. Cette annotation possède entre-autres les attributs suivants :- style
Définit s’il s’agit d’un service RPC ou Document.
- parameterStyle
Détermine si un paramètre de la méthode représente la charge utile du message SOAP (le body) ou si ce paramètre doit être encapsulé (wrapped) dans un élément racine XML nommé d’après le nom de l’opération SOAP.
- @WebMethod
Indique si la méthode correspond à une opération du service Web
- @WebParam
Cette annotation permet d’associer le paramètre de la méthode sur laquelle elle porte à une partie d’un message (l’élément
part
demessage
dans le WSDL). Elle possède les attributs suivants :- name
le nom de l’élément XML
- targetNamespace
l’espace de nom de l’élément XML
- header
un booléen indiquant si l’élément XML correspond à un en-tête ou s’il s’agit du corps du message.
- mode
Il s’agit d’une énumération pouvant prendre les valeurs IN, OUT ou INOUT. Pour les valeurs OUT et INOUT, le type du paramètre est obligatoirement de type javax.xml.ws.Holder<T>. IN signifie que l’élément est extrait du message de l’expéditeur tandis que OUT indique que l’élément fait partie de la réponse.
public String sayHello( @WebParam String name, @WebParam(header=true) UserCredentials userCredentials, @WebParam(header=true, mode=Mode.OUT) Holder<ServerInfo> serverInfo);
- @WebResult
Cette annotation a la même sémantique que l’annotation
@WebParam
sauf qu’elle est ajoutée à la méthode et qu’elle porte sur la valeur de retour de la méthode. Elle possède les attributs suivants :- name
le nom de l’élément XML
- targetNamespace
l’espace de nom de l’élément XML
- header
un booléan indiquant si l’élément XML correspond à un en-tête ou s’il s’agit du corps du message.
Avec JAX-WS, les exceptions lancées par une méthode d’une SEI sont automatiquement transformées en SOAP fault.
Déployer un service Web dans un serveur Tomcat¶
Tomcat ne gère pas directement le déploiement de service Web JAX-WS. Vous pouvez
ajouter jaxws-rt
dans votre projet. Cette bibliothèque fournit les classes
nécessaires pour développer une application compatible JAX-WS.
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.3.2</version>
</dependency>
Dans le fichier web.xml
de déploiement, il faut déclarer un listener
fourni par jaxws-rt :
<listener>
<listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
</listener>
Il faut ensuite déclarer les services dans le fichier sun-jaxws.xml
. Ce
fichier devra être présent dans le répertoire WEB-INF
de l’application.
Pour un projet Maven, il faut donc ajouter le fichier :file`src/main/webapp/WEB-INF/sun-jaxws.xml`.
<?xml version="1.0" encoding="UTF-8"?>
<endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime" version="2.0">
<endpoint name="meteoService"
implementation="dev.gayerie.HelloServiceImpl"
url-pattern="/hello" />
</endpoints>
Ce fichier permet de déclarer chaque endpoint en fournissant son nom, sa classe d’implémentation Java et le motif d’URL pour lequel le service est disponible.
Note
Il n’est pas nécessaire de fournir de document WSDL, le serveur le générera
automatiquement lorsque l’on ajoute ?wsdl
à la fin de l’URL du endpoint.
Exercice¶
Écrire un service Web
Reprenez l’implémentation de la SEI donnée en exemple plus haut et créez une application Web incorporant ce service. Déployez ensuite cette application dans un serveur Tomcat. Vérifiez que le service fonctionne en utilisant par exemple SoapUI.
Vous pouvez ensuite écrire un programme Java client de votre service.
- Modèle de projet
Vous pouvez télécharger un modèle de projet Maven.