XII. Services Web SOAP▲
Le Chapitre 11 était centré sur les services Web HTTP orientés documents. Le «paramètre d'entrée» était l'URL et la «valeur de retour» était un document XML qu'il était de notre responsabilité d'analyser.
Le présent chapitre sera centré sur les services Web SOAP, qui ont une approche plus structurée. Au lieu d'être directement confronté aux requêtes HTTP et aux documents XML, SOAP permet de simuler des appels de fonctions qui retourne des types de données natifs. Comme vous le verrez, l'illusion est presque parfaite, on peut «appeler» une fonction à travers une bibliothèque SOAP avec la syntaxe d'appel standard de Python et la fonction semble retourner des objets et des valeurs Python. Mais en coulisse, la bibliothèque SOAP effectue une transaction complexe impliquant de multiples documents XML et un serveur distant
SOAP est une spécification complexe et il est un peu trompeur de dire qu'il s'agit seulement d'appel de fonctions distants. Certains diraient qu'en plus SOAP permet le passage unidirectionnel asynchrone de messages et des services Web orientés documents et ils auraient raison. SOAP peut être utilisé de cette manière et de bien d'autres encore. Mais ce chapitre s'en tiendra à ce qu'on appelle SOAP «de type RPC», appeler une fonction distante et obtenir un résultat en retour.
XII-A. Plonger▲
Vous utilisez Google, n'est-ce pas ? N'avez-vous jamais souhaité accéder aux résultats de recherches Google par la programmation ? Maintenant, vous pouvez le faire, voici un programme Python qui fait des recherches sur Google.
Exemple 12.1. search.py▲
from
SOAPpy import
WSDL
# you'll need to configure these two values;
# see http://www.google.com/apis/
WSDLFILE =
'/path/to/copy/of/GoogleSearch.wsdl'
APIKEY =
'YOUR_GOOGLE_API_KEY'
_server =
WSDL.Proxy
(
WSDLFILE)
def
search
(
q):
"""Search Google and return list of {title, link, description}"""
results =
_server.doGoogleSearch
(
APIKEY, q, 0
, 10
, False
, ""
, False
, ""
, "utf-8"
, "utf-8"
)
return
[{"title"
: r.title.encode
(
"utf-8"
),
"link"
: r.URL.encode
(
"utf-8"
),
"description"
: r.snippet.encode
(
"utf-8"
)}
for
r in
results.resultElements]
if
__name__
==
'__main__'
:
import
sys
for
r in
search
(
sys.argv[1
])[:5
]:
print
r['title'
]
print
r['link'
]
print
r['description'
]
print
Ce programme peut être importé comme module pour être utilisé dans un plus grand programme, ou utilisé depuis la ligne de commande. En ligne de commande, la requête de recherche est donné comme argument de la commande et le programme affiche l'URL, le titre et la description des cinq première réponses données par Google.
Voici un exemple de sortie pour une recherche du mot «python».
Exemple 12.2. Exemple d'usage de search.py▲
C:\diveintopython\common\py>
python search.py "python"
<
b>
Python</
b>
Programming Language
http://
www.python.org/
Home page for
<
b>
Python</
b>
, an interpreted, interactive, object-
oriented,
extensible<
br>
programming language. <
b>
...</
b>
<
b>
Python</
b>
is
OSI Certified Open Source: OSI Certified.
<
b>
Python</
b>
Documentation Index
http://
www.python.org/
doc/
<
b>
...</
b>
New-
style classes (
aka descrintro). Regular expressions. Database
API. Email Us.<
br>
docs@<
b>
python</
b>
.org. (
c) 2004.
<
b>
Python</
b>
Software Foundation. <
b>
Python</
b>
Documentation. <
b>
...</
b>
Download <
b>
Python</
b>
Software
http://
www.python.org/
download/
Download Standard <
b>
Python</
b>
Software. <
b>
Python</
b>
2.3.3
is
the
current production<
br>
version of <
b>
Python</
b>
. <
b>
...</
b>
<
b>
Python</
b>
is
OSI Certified Open Source:
Pythonline
http://
www.pythonline.com/
Dive Into <
b>
Python</
b>
http://
diveintopython.org/
Dive Into <
b>
Python</
b>
. <
b>
Python</
b>
from
novice to pro. Find:
<
b>
...</
b>
It is
also available in
multiple<
br>
languages. Read
Dive Into <
b>
Python</
b>
. This book is
still being written. <
b>
...</
b>
Pour en savoir plus sur SOAP
- http://www.xmethods.net/ est un répertoire de services Web SOAP en accès public.
- La spécification de SOAP est étonnamment lisible, si vous aimez ce genre de choses.
XII-B. Installation des bibliothèques SOAP▲
Contrairement au reste de ce livre, ce chapitre utilise des bibliothèques qui ne sont pas distribuées avec Python.
Avant de plonger dans les services Web SOAP vous devez installer trois bibliothèques : PyXML, fpconst et SOAPpy.
XII-B-1. Installation PyXML▲
La première bibliothèque dont nous avons besoin est PyXML, un ensemble de bibliothèques XML avancées qui proposent plus de fonctionnalités que les bibliothèques XML prédéfinies que nous avons étudié au Chapitre 9.
Procédure 12.1. ▲
Voici la procédure pour installer PyXML:
- Allez à http://pyxml.sourceforge.net/, cliquez sur Downloads et téléchargez la dernière version correspondant à votre système d'exploitation.
- Si vous utilisez Windows, il y a plusieurs choix possibles. Assurez-vous de télécharger la version de PyXML qui correspond à la version de Python que vous utilisez.
- Double-cliquez sur le programme d'installation. Si vous téléchargez PyXML 0.8.3 pour Windows et Python 2.3, le programme d'installation sera nommé PyXML-0.8.3.win32-py2.3.exe.
- Suivez les étapes du programme d'installation.
- Une fois l'installation terminée, fermer le programme d'installation. Il n'y aura aucune indication visible de succès de l'installation (pas de programmes installés dans le menu Démarrer ni de raccourcis sur le bureau). PyXML est simplement une collection de bibliothèques XML utilisées par d'autres programmes.
Pour vérifier que vous avez installé PyXML correctement, lancez votre IDE Python et vérifiez la version des bibliothèques XML installées comme ci-dessous.
Exemple 12.3. Vérification de l'installation de PyXML▲
>>>
import
xml
>>>
xml.__version__
'0.8.3'
Le numéro de version affiché doit correspondre à celui du programme d'installation de PyXML que vous avez exécuté.
XII-B-2. Installation de fpconst▲
La deuxième bibliothèque dont nous avons besoin est fpconst, un ensemble de constantes et de fonctions pour manipuler les valeurs spéciales double précision IEEE754. Elles fournissent le support des valeurs spéciales Not-a-Number (NaN), Infinité positive (Inf) et Infinité négative (-Inf), qui font partie de la spécification des types de données SOAP
Procédure 12.2. ▲
Voici la procédure pour installer fpconst:
- Téléchargez la dernière version de fpconst à l'adresse http://www.analytics.washington.edu/statcomp/projects/rzope/fpconst/.
- Il y a deux fichiers disponibles en téléchargement, un au format .tar.gz et l'autre au format .zip. Si vous utilisez Windows, téléchargez le fichier .zip, sinon téléchargez le fichier .tar.gz.
- Décompressez le fichier téléchargez. Sous Windows XP, vous pouvez faire un clic droit sur le fichier et choisir Tout extraire, pour les versions antérieures de Windows vous aurez besoin d'un programme tiers comme WinZip. Sous Mac OS X, vous pouvez double-cliquer sur le fichier compressé pour le décompresser avec Stuffit Expander.
- Ouvrez une fenêtre de terminal et allez dans le répertoire où vous avez décompressé les fichiers de fpconst.
- Tapez python setup.py install pour lancer le programme d'installation.
Exemple 12.4. Vérifier l'installation de fpconst▲
>>>
import
fpconst
>>>
fpconst.__version__
'0.6.0'
Ce numéro de version doit correspondre à celui de l'archive fpconst que vous avez téléchargée et installée.
XII-B-3. Installation de SOAPpy▲
La troisième et dernière bibliothèque nécessaire est la bibliothèque SOAP elle-même : SOAPpy.
Voici la procédure pour installer SOAPpy :
- Allez à l'adresse http://pywebsvcs.sourceforge.net/ et sélectionnez la dernière version officielle de la section SOAPpy.
- Il y a deux téléchargements disponibles. Si vous utilisez Windows, téléchargez le fichier .zip, sinon téléchargez le fichier .tar.gz.
- Décompressez le fichier téléchargé, comme vous l'avez fait pour fpconst.
- Ouvrez une fenêtre de terminal et naviguez jusqu'au répertoire où vous avez décompressé les fichiers de SOAPpy.
- Tapez python setup.py install pour lancer le programme d'installation.
Pour vérifier que vous avez installé SOAPpy correctement, lancez votre IDE Python et vérifiez le numéro de version.
Exemple 12.5. Vérification de l'installation de SOAPpy▲
>>>
import
SOAPpy
>>>
SOAPpy.__version__
'0.11.4'
Ce numéro de version doit correspondre à celui de l'archive SOAPpy que vous avez téléchargée et installée.
XII-C. Premiers pas avec SOAP▲
Le coeur de SOAP est la l'appel de fonction distant. Il existe un certain nombre de serveurs publics SOAP qui fournissent des fonctions simples à titre de démonstration.
Le serveur public SOAP le plus populaire est http://www.xmethods.net/. L'exemple suivant utilise une fonction de démonstration qui prend un code postal des Etats-Unis et retourne la température actuelle dans cette région.
Exemple 12.6. Obtenir la température actuelle▲
>>>
from
SOAPpy import
SOAPProxy ***
1
***
>>>
url =
'http://services.xmethods.net:80/soap/servlet/rpcrouter'
>>>
namespace =
'urn:xmethods-Temperature'
***
2
***
>>>
server =
SOAPProxy
(
url, namespace) ***
3
***
>>>
server.getTemp
(
'27502'
) ***
4
***
80.0
***1*** On accède à un serveur distant SOAP à travers une classe de délégation (proxy), SOAPProxy. Cette classe se charge de tout le fonctionnement interne de SOAP pour nous, y compris la création des documents XML de requête à partir du nom de fonction et de la liste d'arguments, l'envoi des requêtes par HTTP au serveur distant SOAP, l'analyse du document XML de réponse et la création de valeurs de retour native Python. Nous verrons à quoi ressemblent ces documents XML dans la section suivante.
***2*** Chaque service SOAP a une URL qui gère toutes les requêtes. La même URL est utilisée pour tous les appels de fonction. Ici, le service n'a qu'une seule fonction, mais plus loin nous verrons des exemples de l'API Google qui a plusieurs fonctions. L'URL du service est partagée par toutes les fonctions. Chaque service SOAP a aussi un espace de noms, qui est défini par le serveur et est complètement arbitraire. Il fait simplement partie de la configuration nécessaire pour appeler les méthodes SOAP. Il permet au serveur de partager une URL de service unique et d'aiguiller les requêtes entre plusieurs services indépendants les uns des autres. C'est un peu comme la séparation de modules Python en paquetages.
***3*** Nous créons SOAPProxy avec l'URL du service et l'espace de noms du service. Cela ne provoque aucune connexion au serveur SOAP, mais crée simplement un objet Python local.
***4*** Maintenant que tout est bien configuré, nous pouvons réellement appeler les méthodes SOAP distantes comme si elles étaient des fonctions locales. Nous passons des arguments comme pour des fonctions ordinaires et nous recevons une valeur de retour comme avec des fonctions ordinaires. Mais en coulisses, il se passe énormément de choses.
Allons voir en coulisses.
XII-D. Débogage de services Web SOAP▲
Les bibliothèques SOAP fournissent une manière simple de voir ce qu'il se passe dans les coulisses.
Pour activer le débogage, il suffit d'assigner deux drapeaux dans la configuration de SOAPProxy.
Exemple 12.7. Débogage de services Web SOAP▲
>>>
from
SOAPpy import
SOAPProxy
>>>
url =
'http://services.xmethods.net:80/soap/servlet/rpcrouter'
>>>
n =
'urn:xmethods-Temperature'
>>>
server =
SOAPProxy
(
url, namespace=
n) ***
1
***
>>>
server.config.dumpSOAPOut =
1
***
2
***
>>>
server.config.dumpSOAPIn =
1
>>>
temperature =
server.getTemp
(
'27502'
) ***
3
***
***
Outgoing SOAP ******************************************************
<
?xml version=
"1.0"
encoding=
"UTF-8"
?>
<
SOAP-
ENV:Envelope SOAP-
ENV:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-
ENC=
"http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi=
"http://www.w3.org/1999/XMLSchema-instance"
xmlns:SOAP-
ENV=
"http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd=
"http://www.w3.org/1999/XMLSchema"
>
<
SOAP-
ENV:Body>
<
ns1:getTemp xmlns:ns1=
"urn:xmethods-Temperature"
SOAP-
ENC:root=
"1"
>
<
v1 xsi:type=
"xsd:string"
>
27502
</
v1>
</
ns1:getTemp>
</
SOAP-
ENV:Body>
</
SOAP-
ENV:Envelope>
************************************************************************
***
Incoming SOAP ******************************************************
<
?xml version=
'1.0'
encoding=
'UTF-8'
?>
<
SOAP-
ENV:Envelope xmlns:SOAP-
ENV=
"http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd=
"http://www.w3.org/2001/XMLSchema"
>
<
SOAP-
ENV:Body>
<
ns1:getTempResponse xmlns:ns1=
"urn:xmethods-Temperature"
SOAP-
ENV:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/"
>
<
return
xsi:type=
"xsd:float"
>
80.0
</
return
>
</
ns1:getTempResponse>
</
SOAP-
ENV:Body>
</
SOAP-
ENV:Envelope>
************************************************************************
>>>
temperature
80.0
***1*** D'abord, nous créons SOAPProxy normalement, avec l'URL et l'espace de noms du service.
***2*** Ensuite, nous activons le débogage en assignant server.config.dumpSOAPIn et server.config.dumpSOAPOut.
***3*** Troisièmement, nous appelons la méthode SOAP distante comme d'habitude. La bibliothèque SOAP affiche aussi bien le document XML de requête sortant que le document de réponse XML entrant. On voit là tout le travail que SOAPProxy fait pour nous. C'est un peu intimidant, nous allons le décomposer.
La majeure partie du document XML de requête qui est envoyé au serveur est composée de code administratif. Vous pouvez ignorer toutes les déclarations d'espaces de noms, elles sont semblables pour tous les appels SOAP. Le coeur de «l'appel de fonction» est ce fragment à l'intérieur de l'élément <Body> :
<
ns1
:
getTemp ***1***
xmlns
:
ns1
=
"urn:xmethods-Temperature"
***2***
SOAP-ENC
:
root
=
"1"
>
<v1
xsi
:
type
=
"xsd:string"
>
27502</v1>
***3**
</
ns1
:
getTemp>
***1*** Le nom de l'élément est le nom de fonction, getTemp. SOAPProxy utilise getattr comme sélecteur. Au lieu d'appeler des méthodes locales différentes en fonction du nom de méthode, il utilise le nom de méthode pour construire le document XML de requête.
***2*** L'élément XML de la fonction est contenu dans un espace de noms spécifique, qui est celui que nous avons spécifié lors de la création de l'objet SOAPProxy . Ne vous souciez pas de SOAP-ENC:root, c'est aussi du code administratif.
***3*** L'argument de la fonction a également été traduit en XML. SOAPProxy examine par introspection chaque argument pour déterminer son type de données (dans le cas présent c'est une chaîne). Le type de données de l'argument va dans l'attribut xsi:type, suivi de sa valeur.
Le document XML de retour est tout aussi facile à comprendre, une fois que l'on sait ce qu'on doit ignorer. Observons ce fragment à l'intérieur de <Body>:
<
ns1
:
getTempResponse ***1***
xmlns
:
ns1
=
"urn:xmethods-Temperature"
***2***
SOAP-ENV
:
encodingStyle
=
"http://schemas.xmlsoap.org/soap/encoding/"
>
<return
xsi
:
type
=
"xsd:float"
>
80.0</return>
***3***
</
ns1
:
getTempResponse>
***1*** Le serveur enveloppe la valeur de retour de la fonction dans un élément <getTempResponse>. Par convention, cet élément-enveloppe est le nom de la fonction plus Response. Mais il pourrait être n'importe quoi d'autre, la chose importante que SOAPProxy prend en compte n'est pas le nom de l'élément, mais l'espace de noms.
***2*** Le serveur retourne la réponse dans le même espace de noms que nous avons utilisé pour la requête, celui que nous avons spécifié à la création de SOAPProxy. Plus loin dans ce chapitre, nous verrons ce qu'il se passe si l'on oublie de spécifier l'espace de noms à la création de SOAPProxy.
***3*** La valeur de retour est spécifiée, ainsi que son type de données (c'est un nombre à virgule flottante). SOAPProxy utilise ce type de données explicite pour créer un objet Python du type natif correspondant et le retourne.
XII-E. Présentation de WSDL▲
Les appels de méthodes locaux sont délégués à la classe SOAPProxy qui les converti de manière transparente en appels de méthodes SOAP distants. Comme nous l'avons vu, c'est un gros travail et SOAPProxy le fait rapidement et de manière transparente. Mais ce que cette classe ne fait pas est de fournir un mode d'introspection de méthodes.
Considérez ceci : les deux sections précédentes ont montré un exemple d'appel distant à une méthode SOAP simple avec un seul argument et une seule valeur de retour, tous deux de types simples. Il faut pour cela connaître et gérer l'URL du service, l'espace de noms du service, le nom de la fonction, le nombre d'arguments et le type de données de chaque argument. Si l'une de ces informations est manquante ou fausse, l'appel ne se fait pas.
Cela ne devrait pas nous surprendre. Si nous voulons appeler une fonction locale, nous devons savoir dans quel paquetage ou module elle se trouve (l'équivalent de l'URL et de l'espace de noms du service). Nous devons aussi connaître le nom de la fonction et le nombre d'arguments. Python gère le typage de données sans types explicites, mais il nous faut quand même savoir combien d'arguments passer et combien de valeurs de retour attendre.
La grande différence, c'est l'introspection. Comme nous l'avons vu au Chapitre 4, Python excelle pour ce qui est de nous permettre de découvrir des informations sur les modules et les fonctions à l'exécution. Nous pouvons lister les fonctions d'un module et, avec un peu de travail, détailler les déclarations de fonctions et les arguments.
WSDL nous permet de faire cela avec les services Web SOAP. WSDL signifie «Web Services Description Language» (Langage de Description des Services Web). Bien que conçu de manière assez flexible pour décrire un grand nombre de types de services Web, il est le plus souvent utilisé pour décrire des services Web SOAP.
Un fichier WSDL est un fichier tout simple, plus précisement, c'est un fichier XML. En général il se trouve sur le serveur qui fournit les services Web SOAP qu'il décrit. Plus loin dans ce chapitre, nous téléchargerons le fichier WSDL de l'API Google et l'utiliserons localement. Cela ne veut pas dire que nous appellerons l'API Google localement, le fichier WSDL continuera de décrire les fonctions distantes fournies par le serveur de Google.
Un fichier WSDL contient une description de tout ce qui est nécessaire à l'appel d'un service Web SOAP :
- L'URL et l'espace de noms du service
- Le type de service Web (en général des appels de fonctions par SOAP, bien que, comme je l'ai dit plus haute, WSDL peut décrire une grande variété de services Web)
- La liste des fonctions disponibles
- Les arguments de chaque fonction
- Le type de données de chaque argument
- La valeur de retour de chaque fonction et le type de données de chaque valeur de retour
En d'autres termes, un fichier WSDL nous dit tout ce que nous avons besoin de savoir pour pouvoir appeler le service Web SOAP
XII-F. Introspection de services Web SOAP avec WSDL▲
Comme beaucoup de choses dans le domaine des services Web, WSDL a un longue et tortueuse histoire, pleine de controverses politiques et d'intrigue. Je n'aborderais pas du tout cette histoire, je la trouve ennuyeuse à pleurer. Il y a eu des normes concurrentes pour remplir ces fonctions, mais WSDL a gagné, donc apprenons à l'utiliser.
La chose la plus importante que WSDL permet de faire est la découverte des méthodes offertes par un serveur SOAP.
Exemple 12.8. Découverte des méthodes disponibles▲
>>>
from
SOAPpy import
WSDL ***
1
***
>>>
wsdlFile =
'http://www.xmethods.net/sd/2001/TemperatureService.wsdl'
)
>>>
server =
WSDL.Proxy
(
wsdlFile) ***
2
***
>>>
server.methods.keys
(
) ***
3
***
[u'getTemp'
]
***1*** SOAPpy intègre un analyseur WSDL. Au moment où j'écris ces lignes, il est considéré comme aux premiers stades de son développement, mais je n'ai eu aucun problème pour analyser les fichiers WSDL que j'ai testé.
***2*** Pour utiliser un fichier WSDL, nous utilisons à nouveau une classe de délégation, WSDL.Proxy, qui prend un seul argument : le fichier WSDL. Notez qu'ici nous lui passons l'URL d'un fichier WSDL situé sur le serveur distant, mais la classe de délégation marche tout aussi bien avec une copie locale du fichier WSDL. La création de la classe déclenchera le téléchargement et l'analyse du fichier WSDL, donc si il contient des erreurs (ou s'il est inaccessible à cause de problèmes de réseau) nous l'apprenons immédiatement.
***3*** La classe de délégation WSDL expose les fonctions disponibles sous la forme d'un dictionnaire Python, server.methods. Obtenir la liste des méthodes disponibles consiste donc simplement à appeler la méthode de dictionnaire keys().
Donc, nous savons que le serveur SOAP offre un méthode unique : getTemp. Mais comment l'appeler ? L'objet de délégation WSDL peut nous l'indiquer.
Exemple 12.9. Découverte des arguments d'une méthode▲
>>>
callInfo =
server.methods['getTemp'
] ***
1
***
>>>
callInfo.inparams ***
2
***
[<
SOAPpy.wstools.WSDLTools.ParameterInfo instance at 0x00CF3AD0
>
]
>>>
callInfo.inparams[0
].name ***
3
***
u'zipcode'
>>>
callInfo.inparams[0
].type ***
4
***
(
u'http://www.w3.org/2001/XMLSchema'
, u'string'
)
***1*** Le dictionnaire server.methods contient une structure spécifique à SOAPpy appelée CallInfo. Un objet CallInfo contient des informations au sujet d'une fonction spécifique, y compris ses arguments.
***2*** Les arguments de la fonction sont stockés dans callInfo.inparams, qui est une liste Python d'objets ParameterInfo qui contiennent des informations sur chaque paramètre.
***3*** Chaque objet ParameterInfo contient un attribut name, qui est le nom de l'argument. Il n'est pas nécessaire de connaître le nom de l'argument pour appeler la fonction par SOAP, mais SOAP permet l'appel de fonction avec des arguments nommés (tout comme Python) et WSDL.Proxy fera la correspondance entre les arguments nommés et la fonction distante si ils sont utilisés.
***4*** Chaque paramètre est explicitement typé, les types de données étant définis par XML Schema. Nous l'avons vu dans la sortie de la section précédente, l'espace de noms XML Schema faisait partie du «code administratif» que je vous avais dit d'ignorer et vous pouvez continuer de l'ignorer. Le paramètre zipcode est une chaîne et si vous passez une chaîne Python à l'objet WSDL.Proxy il l'enverra au serveur.
WSDL permet également de connaître par introspection les valeurs de retour d'une fonction.
Exemple 12.10. Découverte des valeurs de retour d'une fonction▲
>>>
callInfo.outparams ***
1
***
[<
SOAPpy.wstools.WSDLTools.ParameterInfo instance at 0x00CF3AF8
>
]
>>>
callInfo.outparams[0
].name ***
2
***
u'return'
>>>
callInfo.outparams[0
].type
(
u'http://www.w3.org/2001/XMLSchema'
, u'float'
)
***1*** L'équivalent de callInfo.inparams pour les valeurs de retour est callInfo.outparams. C'est aussi une liste, car les fonctions appelées par SOAP peuvent retourner des valeurs multiples, tout comme les fonctions Python.
***2*** Chaque objet ParameterInfo contient des variables name et type. Ici, la fonction retourne une seule valeur, appelée return et qui est un nombre à virgule flottante.
Assemblons tout cela et appelons un service Web SOAP avec un objet de délégation WSDL
Exemple 12.11. Appel d'un service Web avec un objet de délégation WSDL▲
>>>
from
SOAPpy import
WSDL
>>>
wsdlFile =
'http://www.xmethods.net/sd/2001/TemperatureService.wsdl'
)
>>>
server =
WSDL.Proxy
(
wsdlFile) ***
1
***
>>>
server.getTemp
(
'90210'
) ***
2
***
66.0
>>>
server.soapproxy.config.dumpSOAPOut =
1
***
3
***
>>>
server.soapproxy.config.dumpSOAPIn =
1
>>>
temperature =
server.getTemp
(
'90210'
)
***
Outgoing SOAP ******************************************************
<
?xml version=
"1.0"
encoding=
"UTF-8"
?>
<
SOAP-
ENV:Envelope SOAP-
ENV:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-
ENC=
"http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi=
"http://www.w3.org/1999/XMLSchema-instance"
xmlns:SOAP-
ENV=
"http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd=
"http://www.w3.org/1999/XMLSchema"
>
<
SOAP-
ENV:Body>
<
ns1:getTemp xmlns:ns1=
"urn:xmethods-Temperature"
SOAP-
ENC:root=
"1"
>
<
v1 xsi:type=
"xsd:string"
>
90210
</
v1>
</
ns1:getTemp>
</
SOAP-
ENV:Body>
</
SOAP-
ENV:Envelope>
************************************************************************
***
Incoming SOAP ******************************************************
<
?xml version=
'1.0'
encoding=
'UTF-8'
?>
<
SOAP-
ENV:Envelope xmlns:SOAP-
ENV=
"http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd=
"http://www.w3.org/2001/XMLSchema"
>
<
SOAP-
ENV:Body>
<
ns1:getTempResponse xmlns:ns1=
"urn:xmethods-Temperature"
SOAP-
ENV:encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/"
>
<
return
xsi:type=
"xsd:float"
>
66.0
</
return
>
</
ns1:getTempResponse>
</
SOAP-
ENV:Body>
</
SOAP-
ENV:Envelope>
************************************************************************
>>>
temperature
66.0
***1*** La configuration est plus simple que pour appeler un service SOAP directement, car le fichier WSDL contient à la fois l'URL et l'espace de noms dont nous avons besoin pour appeler le service. La création de l'objet WSDL.Proxy entraîne le téléchargement et l'analyse du fichier WSDL et la configuration de l'objet SOAPProxy utilisé pour appelé le service Web SOAP
***2*** Une fois l'objet WSDL.Proxy créé, nous pouvons appeler une fonction aussi simplement que nous l'avions fait avec l'objet SOAPProxy. Cela n'est pas surprenant, WSDL.Proxy n'est qu'une enveloppe autour de SOAPProxy avec quelques méthodes d'introspection en plus, la syntaxe d'appel de fonctions est donc la même.
***3*** Nous pouvons accéder à l'objet SOAPProxy de WSDL.Proxy par server.soapproxy. C'est utile pour activer le débogage, pour que l'objet SOAPProxy de l'objet de délégation WSDL affiche les documents XML en sortie et en entrée.
XII-G. Recherche Google▲
Revenons au code d'exemple que nous avons vu au début du chapitre, qui effectue quelque chose de plus intéressant et de plus utile qu'obtenir la température.
Google fournit une API SOAP pour accéder aux résultats de recherche par la programmation. Pour l'utiliser, il faut s'inscrire aux Services Web Google.
Procédure 12.4. S'inscrire au Services Web Google▲
- Allez à l'adresse http://www.google.com/apis/ et créez un compte Google. Il suffit d'une adresse e-mail. Après vous être inscrit, vous recevrez une clé de licence pour l'API Google par e-mail. Vous devrez passer cette clé en paramètre pour appeler les fonctions de recherche.
- Toujours à l'adresse http://www.google.com/apis/, téléchargez le kit de développement des API Google. Il contient des exemples de code en de nombreux langages de programmation (mais pas en Python) et, surtout, il contient le fichier WSDL.
- Décompressez le kit de développement et cherchez le fichier GoogleSearch.wsdl. Copiez ce fichier quelque part sur votre disque, vous en aurez besoin plus tard dans ce chapitre.
Exemple 12.12. Introspection des services Web Google▲
>>>
from
SOAPpy import
WSDL
>>>
server =
WSDL.Proxy
(
'/path/to/your/GoogleSearch.wsdl'
) ***
1
***
>>>
server.methods.keys
(
) ***
2
***
[u'doGoogleSearch'
, u'doGetCachedPage'
, u'doSpellingSuggestion'
]
>>>
callInfo =
server.methods['doGoogleSearch'
]
>>>
for
arg in
callInfo.inparams: ***
3
***
... print
arg.name.ljust
(
15
), arg.type
key (
u'http://www.w3.org/2001/XMLSchema'
, u'string'
)
q (
u'http://www.w3.org/2001/XMLSchema'
, u'string'
)
start (
u'http://www.w3.org/2001/XMLSchema'
, u'int'
)
maxResults (
u'http://www.w3.org/2001/XMLSchema'
, u'int'
)
filter (
u'http://www.w3.org/2001/XMLSchema'
, u'boolean'
)
restrict (
u'http://www.w3.org/2001/XMLSchema'
, u'string'
)
safeSearch (
u'http://www.w3.org/2001/XMLSchema'
, u'boolean'
)
lr (
u'http://www.w3.org/2001/XMLSchema'
, u'string'
)
ie (
u'http://www.w3.org/2001/XMLSchema'
, u'string'
)
oe (
u'http://www.w3.org/2001/XMLSchema'
, u'string'
)
***1*** Pour commencer à utiliser les services Web Google, il suffit de créer un objet WSDL.Proxy et de lui indiquer votre copie locale du fichier WSDL de Google.
***2*** D'après le fichier WSDL, Google fournit trois fonctions : doGoogleSearch, doGetCachedPage et doSpellingSuggestion. Elles font exactement ce que leur nom suggère : effectuer une recherche Google et retourner les résultats, obtenir la version dans le cache de Google d'une page Web et suggérer une orthographe différente pour les mots couramment mal orthographiés dans les recherches.
***3*** La fonction doGoogleSearch prend de nombreux paramètres de différents types. Notez que si le fichier WSDL peut vous dire le nom et le type des arguments, il ne peut pas vous dire leur signification et comment les utiliser. Il pourrait théoriquement vous indiquer la plage de valeurs légales pour chaque paramètre si seules certaines valeurs était acceptées, mais le fichier WSDL de Google n'est pas aussi détaillé. WSDL.Proxy ne fait pas de miracle, il ne peut vous donner que l'informtion contenue dans le fichier WSDL.
Voici un bref résumé de tous les paramètres de la fonction doGoogleSearch :
- key - La clé de licence reçue à l'inscription aux services Web Google.
- q -Le mot ou la phrase recherchés. La syntaxe est exactement la même que sur le formulaire Web de Google, vous pouvez donc utiliser les paramètres de recherche avancés.
- start - L'index de départ des résultats. Comme sur le site Web, la fonction retourne 10 résultats à la fois, si vous souhaitez accéder à la deuxième «page» de résultats, mettez start à 10.
- maxResults - Le nombre de résultats à retourner. Pour l'instant limité à 10, mais vous pouvez en demander moins si vous n'êtes intéressés que par quelques résultats et voulez préserver votre bande passante.
- filter - Si mis à True, Google filtrera les doublons des résultats.
- restrict - Donnez lui comme valeur country plus le code de pays pour obtenir des résultats d'un pays en particulier. Par exemple countryUK limitera la recherche au Royaume Uni. Vous pouvez également spécifier linux, mac ou bsd pour rechercher parmis un ensemble de sites techniques défini par Google ou unclesam pour rechercher des sites traitant du gouvernement des Etats-Unis.
- safeSearch - Si il est mis à True, Google filtrera les sites pornographiques.
- lr («language restrict») - Donnez lui comme valeur le code d'une langue pour obtenir des résultats limités à une langue.
- ie et oe («input encoding», encodage de l'entrée et «output encoding», encodage de la sortie) - N'est plus utilisé, l'entrée comme la sortie sont encodés en utf-8.
Exemple 12.13. Rechercher avec Google▲
>>>
from
SOAPpy import
WSDL
>>>
server =
WSDL.Proxy
(
'/path/to/your/GoogleSearch.wsdl'
)
>>>
key =
'YOUR_GOOGLE_API_KEY'
>>>
results =
server.doGoogleSearch
(
key, 'mark'
, 0
, 10
, False
, ""
,
... False
, ""
, "utf-8"
, "utf-8"
) ***
1
***
>>>
len(
results.resultElements) ***
2
***
10
>>>
results.resultElements[0
].URL ***
3
***
'http://diveintomark.org/'
>>>
results.resultElements[0
].title
'dive into <b>mark</b>'
***1*** Après l'initialisation de l'objet WSDL.Proxy, nous pouvons appeler server.doGoogleSearch avec les dix paramètres. Rappelez-vous d'utiliser votre propre clé de licence pour les services Web Google.
***2*** Il y a beaucoup d'information retournée, mais regardons d'abord les résultats de la recherche proprement dite. Ils sont stockés dans results.resultElements et nous pouvons y accéder comme à n'importe quelle liste Python.
***3*** Chaque élément de resultElements est un objet qui a de nombreux attributs utiles comme URL, title et snippet. A ce stade, nous pouvons utiliser les techniques d'introspection habituelles de Python comme dir(results.resultElements[0]) pour voir les attributs disponibles. Nous pouvons aussi utiliser l'introspection sur l'objet de délégation WSDL pour examiner les outparams de la fonction. Les deux techniques vous donneront accès à la même information.
L'objet results contient plus que les résultats de la recherche proprement dits. Il contient également des informations sur la recherche elle-même, telles que le temps qu'elle a pris et le nombre de résultats trouvés (même si seuls 10 ont été retournés). L'interface Web Google montre ces informations et elle sont également disponibles par la programmation.
Exemple 12.14. Accéder aux informations secondaires de Google▲
>>>
results.searchTime ***
1
***
0.224919
>>>
results.estimatedTotalResultsCount ***
2
***
29800000
>>>
results.directoryCategories ***
3
***
[<
SOAPpy.Types.structType item at 14367400
>
:
{'fullViewableName'
:
'Top/Arts/Literature/World_Literature/American/19th_Century/Twain,_Mark'
,
'specialEncoding'
: ''
}]
>>>
results.directoryCategories[0
].fullViewableName
'Top/Arts/Literature/World_Literature/American/19th_Century/Twain,_Mark'
***1*** Cette recherche a pris 0.224919 secondes. Cela n'inclut pas le temps passé à envoyer et recevoir les documents XML SOAP. C'est uniquement le temps que Google a passé à traiter notre requête une fois celle-ci reçue.
***2*** Au total, il y a approximativement 30 millions de résultats. Nous pouvons y accéder 10 par 10 en changeant le paramètre start et en appelant à nouveau server.doGoogleSearch.
***3*** Pour certaines requêtes, Google retourne aussi une liste des catégories de l'Annuaire Google qui s'y rapportent. Il suffit alors d'ajouter ces URL à http://directory.google.com/ pour construire le lien vers la page de la catégorie.
XII-H. Recherche d'erreurs dans les services Web SOAP▲
Bien sûr, le monde des services Web SOAP n'est pas différent du reste. Parfois ça ne marche pas.
Comme nous l'avons vu au cours de ce chapitre, SOAP met en oeuvre un certain nombre de couches. Il y a la couche HTTP, puisque SOAP envoi des documents XML vers un serveur HTTP (et en reçoit en réponse). Donc, toutes les techniques de débogage que nous avons vu au Chapitre 11, Services Web HTTP entrent en jeu ici. Nous pouvons assigner httplib.HTTPConnection.debuglevel = 1 pour afficher la communication HTTP.
Au-delà de la couche HTTP sous-jacente, il y a un certain nombre de choses qui peuvent mal se passer. SOAPpy remplit à merveille sa tâche de nous masquer la syntaxe SOAP, mais cela veut aussi dire qu'il peut être difficile de localiser le problème quand ça ne marche pas.
Voici quelques exemples d'erreurs communes que j'ai commises en utilisant SOAP et des erreurs qu'elles ont générées.
Exemple 12.15. Appel d'une méthode avec un objet de délégation mal configuré▲
>>>
from
SOAPpy import
SOAPProxy
>>>
url =
'http://services.xmethods.net:80/soap/servlet/rpcrouter'
>>>
server =
SOAPProxy
(
url) ***
1
***
>>>
server.getTemp
(
'27502'
) ***
2
***
<
Fault SOAP-
ENV:Server.BadTargetObjectURI:
Unable to determine object id from
call: is
the method element namespaced?>
Traceback (
most recent call last):
File "<stdin>"
, line 1
, in
?
File "c:\python23\Lib\site-packages\SOAPpy\Client.py"
, line 453
, in
__call__
return
self.__r_call
(*
args, **
kw)
File "c:\python23\Lib\site-packages\SOAPpy\Client.py"
, line 475
, in
__r_call
self.__hd, self.__ma)
File "c:\python23\Lib\site-packages\SOAPpy\Client.py"
, line 389
, in
__call
raise
p
SOAPpy.Types.faultType: <
Fault SOAP-
ENV:Server.BadTargetObjectURI:
Unable to determine object id from
call: is
the method element namespaced?>
***1*** Avez-vous localisé l'erreur ? Nous créons un SOAPProxy manuellement et nous spécifions correctement l'URL du service, mais nous ne spécifions pas d'espace de noms. Puisque de multiples services peuvent être aiguillés depuis la même URL, l'espace de noms est essentiel pour déterminer le service auquel nous nous adressons et donc la méthode que nous appelons.
***2*** Le serveur répond en envoyant une Faute SOAP, que SOAPpy transforme en exception Python de type SOAPpy.Types.faultType. Toutes les erreurs renvoyées par un serveur SOAP seront des Fautes SOAP, il est donc simple d'intercepter cette exception. Ici, la partie textuelle de la Faute SOAP nous donne un indice sur le problème : l'élément-méthode n'est pas dans un espace de noms car l'objet SOAPProxy n'a pas été configuré avec un espace de noms.
La mauvais configuration des éléments de base du service SOAP est un des problèmes que WSDL cherche à résoudre. Le fichier WSDL contient l'URL et l'espace de noms du service, il est donc impossible de se tromper. Bien sûr, il y a d'autres choses qui peuvent se passer.
Exemple 12.16. Appel de méthode avec de mauvais arguments▲
>>>
wsdlFile =
'http://www.xmethods.net/sd/2001/TemperatureService.wsdl'
>>>
server =
WSDL.Proxy
(
wsdlFile)
>>>
temperature =
server.getTemp
(
27502
) ***
1
***
<
Fault SOAP-
ENV:Server: Exception
while
handling service request:
services.temperature.TempService.getTemp
(
int) --
no signature match>
***
2
***
Traceback (
most recent call last):
File "<stdin>"
, line 1
, in
?
File "c:\python23\Lib\site-packages\SOAPpy\Client.py"
, line 453
, in
__call__
return
self.__r_call
(*
args, **
kw)
File "c:\python23\Lib\site-packages\SOAPpy\Client.py"
, line 475
, in
__r_call
self.__hd, self.__ma)
File "c:\python23\Lib\site-packages\SOAPpy\Client.py"
, line 389
, in
__call
raise
p
SOAPpy.Types.faultType: <
Fault SOAP-
ENV:Server: Exception
while
handling service request:
services.temperature.TempService.getTemp
(
int) --
no signature match>
***1*** Avez-vous localisé l'erreur ? Elle est assez subtile : nous appelons server.getTemp avec un entier au lieu d'une chaîne. Comme nous l'avons vu avec l'introspection du fichier WSDL, la fonction SOAP getTemp() prend un seul argument, zipcode, qui doit être une chaîne. WSDL.Proxy ne convertit pas les types de données, vous devez fournir exactement les types que le serveur attend.
***2*** A nouveau, le serveur retourne une Faute SOAP et la partie textuelle de l'erreur nous indique ou se trouve le problème : nous appelons la fonction getTemp avec un entier, mais il n'y a pas de fonction définie ayant ce nom et prenant un entier en paramètre. En théorie, SOAP permet de surcharger les fonctions, il peut donc y avoir dans un même service SOAP deux fonctions ayant le même nom et le même nombre d'arguments si les arguments sont de types différents. C'est pourquoi il est important de faire correspondre exactement les types de données et pourquoi WSDL.Proxy ne convertit pas les types pour nous. Si il le faisait, nous pourrions en fin de compte appeler une fonction complètement différente, ce qui serait très difficile à déboguer. Il vaut mieux qu'il soit pointilleux en ce qui concerne les types de données et qu'une erreur se produise le plus vite possible si ils ne sont pas corrects.
Il est également possible d'écrire du code Python qui attend un nombre de valeurs de retour différents de celui que la fonction distante retourne effectivement.
Exemple 12.17. Appeler une méthode en attendant un nombre érroné de valeurs de retour▲
>>>
wsdlFile =
'http://www.xmethods.net/sd/2001/TemperatureService.wsdl'
>>>
server =
WSDL.Proxy
(
wsdlFile)
>>>
(
city, temperature) =
server.getTemp
(
27502
) ***
1
***
Traceback (
most recent call last):
File "<stdin>"
, line 1
, in
?
TypeError
: unpack non-
sequence
***1*** Avez vous vu l'erreur ? server.getTemp ne retourne qu'une valeur, un nombre à virgule flottante, mais nous avons écrit du code qui considère qu'il va recevoir deux valeurs et essaye de les assigner à deux variables différentes. Notez que cela ne provoque pas de Faute SOAP. En ce qui concerne le serveur distant, tout s'est bien passé. L'erreur s'est produite après que la transaction SOAP se soit achevée, WSDL.Proxy a retourné un nombre à virgule flottante et l'interpréteur Python a tenté d'exécuter votre demande de le séparer entre deux variables différentes. Comme la fonction n'a retourné qu'une seule valeur, nous obtenons une exception Python et non une Faute SOAP.
Et le service Web de Google ? Le problème le plus courant que j'ai eu est d'oublier d'assigner la clé de licence correctement.
Exemple 12.18. Appel d'une méthode avec une erreur spécifique à l'application▲
>>>
from
SOAPpy import
WSDL
>>>
server =
WSDL.Proxy
(
r'/path/to/local/GoogleSearch.wsdl'
)
>>>
results =
server.doGoogleSearch
(
'foo'
, 'mark'
, 0
, 10
, False
, ""
, ***
1
***
... False
, ""
, "utf-8"
, "utf-8"
)
<
Fault SOAP-
ENV:Server: ***
2
***
Exception
from
service object: Invalid authorization key: foo:
<
SOAPpy.Types.structType detail at 14164616
>
:
{'stackTrace'
:
'com.google.soap.search.GoogleSearchFault: Invalid authorization key: foo
at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe(
QueryLimits.java:220)
at com.google.soap.search.QueryLimits.validateKey(QueryLimits.java:127)
at com.google.soap.search.GoogleSearchService.doPublicMethodChecks(
GoogleSearchService.java:825)
at com.google.soap.search.GoogleSearchService.doGoogleSearch(
GoogleSearchService.java:121)
at sun.reflect.GeneratedMethodAccessor13.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.apache.soap.server.RPCRouter.invoke(RPCRouter.java:146)
at org.apache.soap.providers.RPCJavaProvider.invoke(
RPCJavaProvider.java:129)
at org.apache.soap.server.http.RPCRouterServlet.doPost(
RPCRouterServlet.java:288)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:760)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
at com.google.gse.HttpConnection.runServlet(HttpConnection.java:237)
at com.google.gse.HttpConnection.run(HttpConnection.java:195)
at com.google.gse.DispatchQueue$WorkerThread.run(DispatchQueue.java:201)
Caused by: com.google.soap.search.UserKeyInvalidException: Key was of wrong size.
at com.google.soap.search.UserKey.<init>(UserKey.java:59)
at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe(
QueryLimits.java:217)
... 14 more
'
}>
Traceback (
most recent call last):
File "<stdin>"
, line 1
, in
?
File "c:\python23\Lib\site-packages\SOAPpy\Client.py"
, line 453
, in
__call__
return
self.__r_call
(*
args, **
kw)
File "c:\python23\Lib\site-packages\SOAPpy\Client.py"
, line 475
, in
__r_call
self.__hd, self.__ma)
File "c:\python23\Lib\site-packages\SOAPpy\Client.py"
, line 389
, in
__call
raise
p
SOAPpy.Types.faultType: <
Fault SOAP-
ENV:Server: Exception
from
service object:
Invalid authorization key: foo:
<
SOAPpy.Types.structType detail at 14164616
>
:
{'stackTrace'
:
'com.google.soap.search.GoogleSearchFault: Invalid authorization key: foo
at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe(
QueryLimits.java:220)
at com.google.soap.search.QueryLimits.validateKey(QueryLimits.java:127)
at com.google.soap.search.GoogleSearchService.doPublicMethodChecks(
GoogleSearchService.java:825)
at com.google.soap.search.GoogleSearchService.doGoogleSearch(
GoogleSearchService.java:121)
at sun.reflect.GeneratedMethodAccessor13.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.apache.soap.server.RPCRouter.invoke(RPCRouter.java:146)
at org.apache.soap.providers.RPCJavaProvider.invoke(
RPCJavaProvider.java:129)
at org.apache.soap.server.http.RPCRouterServlet.doPost(
RPCRouterServlet.java:288)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:760)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
at com.google.gse.HttpConnection.runServlet(HttpConnection.java:237)
at com.google.gse.HttpConnection.run(HttpConnection.java:195)
at com.google.gse.DispatchQueue$WorkerThread.run(DispatchQueue.java:201)
Caused by: com.google.soap.search.UserKeyInvalidException: Key was of wrong size.
at com.google.soap.search.UserKey.<init>(UserKey.java:59)
at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe(
QueryLimits.java:217)
... 14 more
'
}>
***1*** Avez vous vu l'erreur ? La syntaxe d'appel est correcte, ainsi que le nombre d'arguments et leurs types. Le problème est propre à l'application : le premier argument est supposé être ma clé de licence, mais foo n'est pas une clé Google valide.
***2*** Le serveur Google répond par une Faute SOAP et un message d'erreur incroyablement long, qui comprend une trace de pile Java complète. Rappelez-vous que toutes les erreurs SOAP sont signalées par des Fautes SOAP : les erreurs de configurations, les erreurs dans les arguments de fonctions et les erreurs spécifiques à l'application, comme c'est le cas ici. Enterrée quelque part dans ce message d'erreur, il y a cette information cruciale : Invalid authorization key: foo.
Pour en savoir plus sur la recherche d'erreurs avec SOAP
- New developments for SOAPpy explique pas à pas une tentative de connexion à un service SOAP qui ne fonctionne pas comme il est dit.
XII-I. Résumé▲
Les services Web SOAP sont très complexes. La spécification est très ambitieuse et tente de répondre à de nombreux cas d'utilisation différents des services Web. Ce chapitre a abordé quelques uns des cas d'utilisation les plus simples.
Avant de plonger dans le prochain chapitre, assurez-vous d'être à l'aise pour :
- Vous connecter à un serveur SOAP et appeler des fonctions distantes
- Charger un fichier WSDL et examiner les méthodes distantes par introspection
- Déboguer les appels SOAP avec le traçage des communications
- Rechercher les erreurs courantes avec SOAP