Les Web services

Stéphane Genaud

Introduction

Les web-services sont aujourd'hui implémentés dans leur écrasante majorité en SOAP et WSDL.

Pas de nouveauté conceptuelle par rapport à  d'autres propositions antérieures (e.g. DCE RPC ou CORBA IIOP), mais un engouement inégalé de la part de l'industrie (e.g. Microsoft, IBM, Sun Microsystems, BEA, HP, Oracle, SAP, ...).

Une part du succès de SOAP vient de son adossement au W3C, qui publie la spécification de SOAP sous la forme d'une recommandation.

Qu'est ce que SOAP ?

Définition du W3C:

SOAP

Dans les grandes lignes:

De par sa simplicité et les standards utilisés, SOAP doit permettre d'embarquer des services distants dans n'importe quelle application.

SOAP intérêts

De par sa simplicité et les standards utilisés, SOAP doit permettre d'embarquer des services distants dans n'importe quelle application.

architecture example

Exemple de message

<?xml version="1.0" encoding="UTF-8"?>
  <soap:Envelope
        xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" >
     <soap:Body>
        <po:purchaseOrder orderDate="2003-09-22" 
               xmlns:po="http://www.Monson-Haefel.com/jwsbook/PO">
          <po:accountName>Amazon.com</po:accountName>
          <po:accountNumber>923</po:accountNumber>
          <po:address>
             <po:name>AMAZON.COM</po:name>
             <po:street>1850 Mercer Drive</po:street>
             <po:city>Lexington</po:city>
             <po:state>KY</po:state>
             <po:zip>40511</po:zip>
          </po:address>
          <po:book>
             <po:title>J2EE Web Services</po:title>
             <po:quantity>300</po:quantity>
             <po:wholesale-price>24.99</po:wholesale-price>
          </po:book>
        </po:purchaseOrder>
     </soap:Body>
  </soap:Envelope>

Message SOAP - structure

message structure Un message SOAP est dans une Envelope, qui contient :

Remarques

Namespaces (encart)

Rappel: les namespaces (espaces de nommage) permettent d'éviter les conflits de noms. L'objectif est de permettre l'emploi de plusieurs langages dans un seul document.

<balise xmlns:mynsp="http://icps.u-strasbg.fr/course/sd/2005">
<mynsp:syllabus>
    ...
</mysnp:syllabus>
</balise>

Namespaces (encart - suite)

On peut utiliser des default namespaces pour ne pas avoir à  spécifier le préfixe à  chaque balise.
Ils sont définis avec xmlns sans préfixe.

<html xmlns="http://www.w3.org/1999/xhtml">
   <title>...</title>
</html>

Message SOAP - Header et Body

L'entête Header est optionnelle:

Le corps Body est obligatoire:

Messages RPC

SOAP 1.2 :
La définition du format des messages de style RPC se trouve dans la partie 2, section 4 de la recommandation.

Les éléments nécessaires à  un appel RPC sont :

  1. l'adresse du noeud SOAP final (destinataire),
  2. le nom du service (ou classe, ou méthode) à  invoquer,
  3. l'identification et le type des paramètres d'entrée et de retour.

Note: par analogie avec les modèles RPC existants, 1. est souvent dénommé proxy.

SOAP RPC requête/réponse

Soit la méthode foisdeux() de signature: int foisdeux(int).
La requête foisdeux(63) est encodé dans un message comme:
   <body>
     <ns1:foisdeux xmlns:ns1="urn:montest">
        <param1 xsi:type="xsd:int">63</param1>
     </ns1:foisdeux>
   </body>

Le message en réponse devrait être:

   <body>
     <ns1:foisdeuxResponse xmlns:ns1="urn:montest">
	  <return xsi:type="xsd:int">126</return>
     </ns1:foisdeux>
   </body>

Toujours un seul paramètre en retour, de type simple ou complexe. Utilise l'espace de nommage de XML Schema pour les types.

Spécification du service

Dans l'exemple précédent, l'utilisateur du service doit connaître, le nom des opérations disponibles, le type des entrées et sorties, à  quelle machine s'adresser, ....

Un format de description à  été proposé, permettant de décrire des web-services: WSDL.

WSDL

WSDL (Web Service Description Language) permet la définition de web services dans un langage XML. C'est une note du W3C.

Un document WSDL utilise les éléments suivants:

Element Définit
<portType> Les opérations proposées par le web service
<message> Les messages utilisés par le web service
<types> les types de données utilisées par le web service
<binding> Les protocoles de communication utilisés par le web service

Structure d'un document WSDL

<definitions>
<types>
... (utilise la notation des XML Schema) ....
</types>

<message>
...
</message>

<portType>
...
</portType>

<binding>
...
</binding>
</definitions>

Un doc. WSDL peut contenir d'autres éléments, e.g. les éléments extension et services permettant de gouper les définitions de plusieurs web services en 1 seul doc.

Hello World en WSDL

Exemple: si vous passez le paramètre "monde", le service renvoie la chaine "Bonjour, monde !"

<definitions name="HelloService"
    targetNamespace="http://www.ecerami.com/wsdl/HelloService.wsdl"
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:tns="http://www.ecerami.com/wsdl/HelloService.wsdl"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">

Définition des Namespaces pour éviter les conflits de noms. targetNamespace permet de référencer le document lui-même. Le namespace par défaut est http://schemas.xmlsoap.org/wsdl/ (i.e. sans préfixe, c'est le namespace utilisé).

Hello World en WSDL (message)

<message name="DisBonjourRequest">
   <part name="monom" type="xsd:string"/>
</message>
<message name="DisBonjourResponse">
   <part name="greeting" type="xsd:string"/>
</message>

Les deux messages définis correspondent aux entrées et sorties. Pour plusieurs arguments en entrée ou en sortie, on aurait plusieurs éléments <part>.

Hello World en WSDL (portType)

<portType name="bonjour_PortType">
   <operation name="DisBonjour">
      <input message="tns:DisBonjourRequest"/>
      <output message="tns:DisBonjourResponse"/>
   </operation>
</portType>

L'élément <portType> est le plus important: il définit le web service=l'opération (méthode) qu'on peut invoquer et les messages impliqués.

Hello World en WSDL (binding)

Indique par quel protocole les opérations vont être transportées.

<binding name="..." type="...">
      <soap:binding style="rpc|document" transport="...http..."/>
      <operation name="...">

		.... input et output de l'operation ...

      </operation >

</binding>

Hello World en WSDL (binding)

Indique par quel protocole les opérations vont être transportées. Le plus souvent, à  travers SOAP comme ici.

<binding name="bonjour_Binding" type="tns:bonjour_PortType">
      <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
      <operation name="DisBonjour">
         <soap:operation soapAction="DisBonjour"/>
         <input>
            <soap:body
               encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
               namespace="urn:examples:helloservice"
               use="encoded"/>
         </input>
         <output>
            <soap:body
               encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
               namespace="urn:examples:helloservice"
               use="encoded"/>
         </output>
      </operation>
</binding>

Outils intéressants

Toolkits pour SOAP

Les messages XML sont la plupart du temps cachés au programmeur car il existe des outils existent dans de nombreux langages pour encoder/décoder les messages XML. (On parle de sérialisation/dé-sérialisation).

Quelques toolkits sont présentés ici:

Exemple client Perl

SOAP::Lite permet de construire une requête RPC de manière très concise de la forme:

  use SOAP::Lite;

  print SOAP::Lite -> uri($namespace)
                   -> proxy($service_URL)
                   -> $method_name($param)
                   -> result;

Exemple client Perl: BabelFish

Detailed Description
IMPORTANT NOTE: Our own access to the Altavista Babelfish data has been terminated, so this web service has been disabled. The service will remain down indefinitely until a suitable replacement translation engine can be found.

Appel au service de traduction BabelFish.
source : [ babelfish.pl ].

  #!perl -w

  use SOAP::Lite;

  print SOAP::Lite                                            
    -> uri('urn:xmethodsBabelFish')
    -> proxy('http://services.xmethods.net:80/perl/soaplite.cgi')
    -> BabelFish('en_fr','The weather is fine. Let us go.')
    -> result;

Exemple client Perl pour WSDL

SOAP::Lite permet d'invoquer indirectement une méthode décrite dans un fichier WSDL. Dans ce cas l'objet est construit par:

  use SOAP::Lite;

  print SOAP::Lite -> service($URL_to_wsdl_file)
                   -> $method_name($param);

Une requête est faite pour récupérer la description, la parse, construit le stub (i.e. l'objet équivalent évoquant le service).

Note 1: on peut invoquer plusieurs fois le service sans refaire la requête au WSDL :

 
  my $service= SOAP::Lite -> service('http://... .wsdl');
  print $service->method('a') + $service->method('a');

Note 2: le fichier peut être local :

 
  ->service('file:// ... .wsdl');

Exemple client Perl: BabelFish

Appel au service de traduction BabelFish à  travers une description WSDL.
source : [ babelfish2.pl ].

  #!perl -w

  use SOAP::Lite;

  print SOAP::Lite                                            
    -> service('http://www.xmethods.net/sd/2001/BabelFishService.wsdl')
    -> BabelFish('en_fr','The weather is fine. Let us go.');

Exemple client Java (Apache SOAP)

//
// XMethods sample client for the Stock Quote service
//
import java.io.*;
import java.net.*;
import java.util.*;
import org.apache.soap.util.xml.*;
import org.apache.soap.*;
import org.apache.soap.rpc.*;

public class stockquoteClient{

  public static float getQuote (URL url, String symbol) throws Exception {

    Call call = new Call ();

    // Service uses standard SOAP encoding
    String encodingStyleURI = Constants.NS_URI_SOAP_ENC;
    call.setEncodingStyleURI(encodingStyleURI);

    // Set service locator parameters
    call.setTargetObjectURI ("urn:xmethods-delayed-quotes");
    call.setMethodName ("getQuote");

    // Create input parameter vector
    Vector params = new Vector ();
    params.addElement (new Parameter("symbol", String.class, symbol, null));
    call.setParams (params);

    // Invoke the service ....
    Response resp = call.invoke (url,"");
(../..)

Exemple client Java (2/2)

    // ... and evaluate the response
    if (resp.generatedFault ()) {
	throw new Exception();
    } else {
      // Call was successful. Extract response parameter and return result
      Parameter result = resp.getReturnValue ();
      Float rate=(Float) result.getValue();
      return rate.floatValue();
    }
  }

// Driver to illustrate service invocation
  public static void main(String[] args) {
    try {
          URL url=new URL("http://services.xmethods.net:80/soap");
          String symbol= "TRW";
          float quote = getQuote(url,symbol);
          System.out.println(quote);
    }
    catch (Exception e) {e.printStackTrace();}
  }
}

Exemple client C

La librairie libsoup est utilisée dans le projet GNOME.

Exemple:
Recherche dans des dictionnaires. Le noeud SOAP http://services.aonaware.com/DictService/DictService.asmx fournit différents services, dont Define.

listing complet : [ dict.c ].

Exemple client C

int main (int argc, char **argv)
{
	SoupUri *proxy = NULL;
	SoupSoapMessage *msg;

	session = soup_session_async_new_with_options (SOUP_SESSION_PROXY_URI, proxy,NULL);
	msg = soup_soap_message_new ("POST",
				     "http://services.aonaware.com/DictService/DictService.asmx",
				     FALSE, NULL, NULL, NULL);

	soup_message_add_header (SOUP_MESSAGE (msg)->request_headers,
				 "SOAPAction", "http://services.aonaware.com/webservices/Define");
	soup_soap_message_start_envelope (msg);
	soup_soap_message_start_body (msg);
	soup_soap_message_start_element (msg,"Define",NULL,"http://services.aonaware.com/webservices/");
	soup_soap_message_add_namespace (msg, NULL, "http://services.aonaware.com/webservices/");
	soup_soap_message_start_element (msg, "word", NULL, NULL);
	soup_soap_message_write_string (msg, argv[0]);
	soup_soap_message_end_element (msg);
	soup_soap_message_end_element (msg);
	soup_soap_message_end_body (msg);
	soup_soap_message_end_envelope (msg);
	soup_soap_message_persist (msg);
	soup_session_queue_message (session, SOUP_MESSAGE (msg), got_response, NULL);
	loop = g_main_loop_new (NULL, TRUE);
	g_main_run (loop);
}

Exemple client C (libsoup) 2/3

Décodage de la réponse:
static void got_response (SoupMessage *msg, gpointer user_data)
{
	SoupSoapResponse *response;
	SoupSoapParameter *param, *subparam;
	char *word, *dict, *def;
	int count = 0;

	if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
		fprintf (stderr, "%d %s\n", msg->status_code, msg->reason_phrase); exit (1);
	}
	response = soup_soap_message_parse_response (SOUP_SOAP_MESSAGE (msg));
	if (!response) {
		fprintf (stderr, "Could not parse SOAP response\n"); exit (1);
	}
	param = soup_soap_response_get_first_parameter_by_name (response, "DefineResult");
	if (!param) {
		fprintf (stderr, "Could not find result in SOAP response\n"); exit (1);
	}
	param = soup_soap_parameter_get_first_child_by_name (param, "Definitions");
	if (!param)
		goto done;
(../..)

Exemple client C (libsoup) 3/3

	for (param = soup_soap_parameter_get_first_child_by_name (param, "Definition");
	     param;
	     param = soup_soap_parameter_get_next_child_by_name (param, "Definition")) {
		subparam = soup_soap_parameter_get_first_child_by_name (param, "Word");
		if (!subparam)
			continue;
		word = soup_soap_parameter_get_string_value (subparam);

		subparam = soup_soap_parameter_get_first_child_by_name (param, "Dictionary");
		if (subparam)
			subparam = soup_soap_parameter_get_first_child_by_name (subparam, "Name");
		if (subparam)
			dict = soup_soap_parameter_get_string_value (subparam);
		else
			dict = NULL;

		printf ("% 2d. %s (%s):\n", ++count, word, dict);
		g_free (word);
		g_free (dict);

		subparam = soup_soap_parameter_get_first_child_by_name (param, "WordDefinition");
		if (subparam) {
			def = soup_soap_parameter_get_string_value (subparam);
			printf ("%s\n", def);
			g_free (def);
		}
	}

 done:
	if (count == 0)
		printf ("No definition\n");

	g_object_unref (response);
	g_main_quit (loop);
}

Exemple client PHP

PHP contient depuis PHP5 un support natif de SOAP (si compilé avec --enable-soap).

<?php
    $client = new SoapClient(NULL,
        array("location" => "http://64.124.140.30:9090/soap",
              "uri"      => "urn:xmethods-delayed-quotes",
              "style"    => SOAP_RPC,
              "use"      => SOAP_ENCODED));

print($client->__call(
        "getQuote", 
        /* Method params */
         array(new SoapParam( "ibm", "symbol")),
        /* Options */
        array(
            /* SOAP Method Namespace */
            "uri" => "urn:xmethods-delayed-quotes",
            /* SOAPAction HTTP Header for SOAP Method */
            "soapaction" => "urn:xmethods-delayed-quotes#getQuote"
        )). "\n");
?>

Exemple client PHP pour WSDL

La classe permet également (comme SOAP::Lite) de parser un WSDL avant d'invoquer une méthode. Le WSDL téléchargé est ou non mis en cache (soap.wsdl_cache_enabled dans phph.ini).

<?php
$client = new
    SoapClient(
    "http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl"
    );

print($client->getQuote("ibm"));
?>

Exercices

Attention: cet exercice nécessite de créer un compte chez Google (gratuité totale). Je n'ai aucun intérêt dans cette entreprise. Néanmoins, La contrepartie à  la création d'un compte est l'utilisation de services puissants qui justifient l'exercice.

Google fournit des services SOAP (moyennant la création d'une clé). Ceux ci sont décrits dans le fichier WSDL suivant : [ GoogleSearch.wsdl ].

  1. Créer un compte pour obtenir une clé.
  2. Ecrire un client SOAP::Lite qui donne le nombre de résultats obtenus (estimatedTotalResultsCount) par une recherche doGoogleSearch.

Exercices

Le site XMethods recense un ensemble de web-services publics.

Parmi ceux-ci un service de localisation géographique capable de localiser une IP. La fiche de ce service est ici

Ecrire une requête SOAP pour trouver la latitude et longitude d'une IP dont vous connaissez l'emplacement géographique.

Pour les détails, voir l'exercice de mise en application: [ html ].

Démo Mozilla

Mozilla (et les navigateurs basés sur Mozilla) sont dotés d'une API Javascript pour SOAP. Des détails sont donnés dans cet article.

Attention: pour fonctionner, les fichiers doivent être locaux.

Phrase à  vérifier:

Google Results: