Services Web - EPSI POE mars 2017 - David Gayerie Licence Creative Commons

Les services (WS-*) : SOAP/WSDL

  1. La quête de l'interopérabilité
  2. SOAP : structure des messages
  3. Web Service Description Language (WSDL)
  4. Développer un service Web SOAP
  5. Excercice : Accéder à un service Web
  6. Implémenter des services Web avec JAX-WS
  7. Déployer un service Web dans un serveur d'application Java EE
  8. Les outils Java de génération
  9. Consommer un service Web avec JAX-WS
  10. Excercice : Écrire un programme client d'un service Web
  11. Excercice : Écrire un service Web
  12. Pour aller plus loin

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 :

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-*.

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)

Exemple de message SOAP

<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>

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/

Structure d'un message SOAP

<!-- 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.

Une réponse contenant une SOAP fault

<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>

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).

Exemple de 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

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.

Excercice : 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.

Implémenter des services Web avec JAX-WS

Depuis Java 6, l'environnement d'exécution Java inclut 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.

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).

Une SEI avec JAX-WS SOAP

package fr.epsi.i4;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;

@WebService(targetNamespace="http://epsi.fr/ws/hello")
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface HelloService {

  @WebMethod
  @WebResult(name = "helloMessage", targetNamespace = "http://epsi.fr/ws/hello")
  public String sayHello(
      @WebParam(name = "who", targetNamespace="http://epsi.fr/ws/hello") String name);

}

La classe d'implémentation de la SEI

package fr.epsi.i4;

import javax.jws.WebService;

@WebService(endpointInterface="fr.epsi.i4.HelloService", 
    targetNamespace="http://epsi.fr/ws/hello", serviceName="HelloService")
public class HelloServiceImpl implements HelloService {

  @Override
  public String sayHello(String name) {
    return "Hello " + name;
  }

}

Cette SEI permet de gérer un échange de messages comme celui-ci :

Message de l'expéditeur

<soapenv:Envelope 
  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:hel="http://epsi.fr/ws/hello">
   <soapenv:Header/>
   <soapenv:Body>
      <hel:who>world</hel:who>
   </soapenv:Body>
</soapenv:Envelope>

Message du destinataire

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Body>
      <helloMessage xmlns="http://epsi.fr/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 de message dans le WSDL). Elle possède les attributs suivants :
name
le nom de l'élement XML
targetNamespace
l'espace de nom de l'élement XML
header
un booléan indiquant si l'élement 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'élement XML
targetNamespace
l'espace de nom de l'élement XML
header
un booléan indiquant si l'élement XML correspond à un en-tête ou s'il s'agit du corps du message.

Déployer un service Web dans un serveur d'application Java EE

Pour déployer un service Web développé avec JAX-WS, soit vous disposez d'un serveur d'application Java EE (comme Glassfish ou TomEE), soit vous ne disposez que d'un conteneur de Servlet (comme Tomcat ou Jetty).

Pour un déploiement dans un serveur d'application Java EE, il suffit d'embarquer votre implémentation dans une application Web Java et de déployer cette application. Le serveur d'application s'occupe du reste...

Pour un déploiement dans un conteneur de Servlet, il faut incorporer une implémentation de JAX-WS dans votre application Web et se conformer à sa documentation car JAX-WS ne définit pas de standard de configuration et de déploiement.

Il existe plusieurs implémentations de JAX-WS : Metro, CXF... Pour un déploiement en utilisant l'implémentation Metro sous Tomcat, vous pouvez suivre ce tutoriel.

Les outils Java de génération

Java → WSDL (Contract last)
L'utilitaire wsgen livré avec le JDK peut générer un fichier WSDL à partir d'une classe compilée.
WSDL → Java (Contract first)
L'utilitaire wsimport livré avec le JDK peut générer toutes les classes Java nécessaires à l'implémentation d'un client ou d'un service à partir d'un fichier WSDL.
Générer les classes compilées
wsimport http://localhost:8080/hello-service/webservices/HelloService?wsdl
Générer uniquement les sources
wsimport -Xnocompile http://localhost:8080/hello-service/webservices/HelloService?wsdl

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.

Exemple d'accès à la SEI pour un client

// 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://epsi.fr/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.

Modification de l'URL du endpoint du service

// ...

// 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"));

Excercice : É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.

Excercice : Écrire un service Web

Reprenez l'implémentation de la SEI donnée en exemple plus haut et créez une application Web Java EE incorporant ce service. Déployez ensuite cette application dans un serveur d'application comme TomEE Plus. Vérifiez que le service fonctionne en utilisant par exemple SoapUI.

Vous pouvez ensuite écrire un programme Java client de votre service.

Pour aller plus loin

WSDL
http://www.w3schools.com/webservices/ws_wsdl_intro.asp
Tutoriels JAX-WS
http://www.mkyong.com/tutorials/jax-ws-tutorials/