12.8. Solución de problemas en servicios web SOAP

Por supuesto, el mundo de los servicios web SOAP no es todo luz y felicidad. Algunas veces las cosas van mal.

Como ha visto durante este capítulo, SOAP supone varias capas. Primero está la capa HTTP, ya que SOAP envía y recibe documentos a y desde un servidor HTTP. Así que entran en juego todas las técnicas de depuración que aprendió en Capítulo 11, Servicios Web HTTP. Puede hacer import httplib y luego httplib.HTTPConnection.debuglevel = 1 para ver el tráfico HTTP subyacente.

Más allá de la capa HTTP hay varias cosas que pueden ir mal. SOAPpy hace un trabajo admirable ocultándonos la sintaxis de SOAP, pero eso también significa que puede ser difícil determinar dónde está el problema cuando las cosas no funcionan.

Aquí tiene unos pocos ejemplos de fallos comunes que he cometido al usar servicios web SOAP, y los errores que generaron.

Ejemplo 12.15. Invocación de un método con un proxy configurado incorrectamente

>>> 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 ¿Vio la equivocación? Estamos creando un SOAPProxy de forma manual, y especificamos correctamente la URL del servicio pero no el espacio de nombres. Dado que se puede acceder a servicios múltiples mediante la misma URL de servicio es esencial el espacio de nombres a la hora de determinar a cual de ellos queremos hablar, y por lo tanto a cual método estamos invocando realmente.
2 El servidor responde enviando una Fault SOAP, que SOAPpy convierte en una excepción de Python del tipo SOAPpy.Types.faultType. Todos los errores devueltos por cualquier servidor SOAP serán siempre Faults de SOAP, así que podemos capturar esta excepción fácilmente. En este caso, la parte legible por humanos de la Fault de SOAP nos da una pista del problema: el elemento del método no está en un espacio de nombres, porque no se configuró con un espacio de nombres el objeto SOAPProxy original.

Uno de los problemas que intenta resolver WSDL es la desconfiguración de elementos básicos del servicio SOAP. El fichero WSDL contiene la URL y el espacio de nombres del servicio, para que no pueda equivocarse. Por supuesto, hay otras cosas que pueden ir mal.

Ejemplo 12.16. Invocación de un método con argumentos equivocados

>>> 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 ¿Vio el fallo? Es sutil: estámos invocando server.getTemp con un entero en lugar de una cadena. Como vio en la introspección del fichero WSDL, la función getTemp() de SOAP acepta un único argumento, zipcode, que debe ser una cadena. WSDL.Proxyno hará conversión de tipos de datos por usted; debe pasar los tipos exactos que espera el servidor.
2 De nuevo, el servidor devuelve una Fault de SOAP y la parte legible del error nos da una pista del problema: estamos invocando la función getTemp con un valor entero, pero no hay definida una función con ese nombre que acepte un entero. En teoría, SOAP le permite sobrecargar funciones así que podría tener dos funciones en el mismo servicio SOAP con el mismo nombre y argumentos en mismo número, pero con tipo diferente. Por eso es importante que los tipos de datos se ajusten de forma exacta y es la razón de que WSDL.Proxy no transforme los tipos. Si lo hiciera, ¡podría acabar llamando a una función completamente diferente! Buena suerte depurando eso en tal caso. Es mucho más fácil ser detallista con los tipos de datos y obtener un fallo lo antes posible si los indicamos de forma errónea.

También es posible escribir código en Python que espere valores de retorno diferentes de los que devuelve realmente la función remota.

Ejemplo 12.17. Invocación de un método esperando una cantidad errónea de valores de retorno

>>> 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 ¿Vio el fallo? server.getTemp sólo devuelve un valor, un decimal, pero hemos escrito código que asume que vamos a obtener dos valores e intenta asignarlos a dos variables diferentes. Esto no se registra con una falta de SOAP. En lo que concierne al servidor remoto nada ha ido mal. El error ha ocurrido tras completar la transacción SOAP, WSDL.Proxy devolvió un número decimal, y el intérprete Python intentó en local acomodar nuestra petición de dividirlo en dos variables diferentes. Como la función sólo devuelve un valor, obtenemos una excepción de Python al intentar partirlo, y no una Fault de SOAP.

¿Qué hay del servicio web de Google? El problema más común que he tenido con él es que olvido indicar adecuadamente la clave de la aplicación.

Ejemplo 12.18. Invocación de un método con un error específico a la aplicació

>>> 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 ¿Puede encontrar el fallo? No hay nada malo en la sintaxis de la llamada, o el número de los argumentos, o los tipos. El problema es específico de la aplicación: el primer argumento debería ser mi clave de la aplicación, pero foo no es una clave válida en Google.
2 El servidor de Google responde con una Fault de SOAP y un mensaje de error increíblemente largo, que incluye una traza de pila de Java completa. Recuerde que todos los errores de SOAP se señalan mediante una Fault: los errores de configuración, de argumentos y los errores específicos de aplicaciones como éste. Enterrada en algún sitio está la información crucial: Invalid authorization key: foo.

Lecturas complementarias sobre solución de problemas con SOAP