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