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.
De par sa simplicité et les standards utilisés, SOAP doit permettre d'embarquer des services distants dans n'importe quelle application.
De par sa simplicité et les standards utilisés, SOAP doit permettre d'embarquer des services distants dans n'importe quelle application.
<?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>
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>
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>
L'entête Header est optionnelle:
Le corps Body est obligatoire:
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 :
Note: par analogie avec les modèles RPC existants, 1. est souvent dénommé proxy.
<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.
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 (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 |
<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.
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é).
<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>.
<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.
<binding name="..." type="..."> <soap:binding style="rpc|document" transport="...http..."/> <operation name="..."> .... input et output de l'operation ... </operation > </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>
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: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;
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;
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');
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.');
// // 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,"");
// ... 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();} } }
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 ].
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); }
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;
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); }
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"); ?>
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")); ?>
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 ].
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 ].
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.