12.8. SOAP Web 服务故障排除

当然,SOAP Web 服务的世界并非总是充满快乐和光明。有时也会出错。

正如您在本章中所见,SOAP 涉及多个层。 由于 SOAP 正在向 HTTP 服务器发送 XML 文档并从 HTTP 服务器接收 XML 文档,因此存在 HTTP 层。 所以你在 第 11 章,*HTTP Web 服务* 中学到的所有调试技术在这里都适用。 您可以 import httplib,然后设置 httplib.HTTPConnection.debuglevel = 1 来查看底层 HTTP 流量。

除了底层 HTTP 层之外,还有很多事情可能会出错。 SOAPpy 在对您隐藏 SOAP 语法方面做得令人钦佩,但这同时也意味着当事情不顺利时,可能很难确定问题出在哪里。

以下是我在使用 SOAP Web 服务时犯的一些常见错误的示例,以及它们产生的错误。

示例 12.15. 使用配置错误的代理调用方法

>>> 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 你发现错误了吗? 您正在手动创建一个 SOAPProxy,并且您已经正确指定了服务 URL,但您没有指定命名空间。 由于多个服务可以通过同一个服务 URL 路由,因此命名空间对于确定您要与哪个服务通信至关重要,从而确定您真正调用的方法。
2 服务器通过发送 SOAP 错误来响应,SOAPpy 将其转换为类型为 SOAPpy.Types.faultTypePython 异常。 从任何 SOAP 服务器返回的所有错误都将始终是 SOAP 错误,因此您可以轻松捕获此异常。 在这种情况下,SOAP 错误中人类可读的部分提供了一个有关问题的线索:method 元素没有命名空间,因为原始的 SOAPProxy 对象没有配置服务命名空间。

错误配置 SOAP 服务的基本元素是 WSDL 旨在解决的问题之一。 WSDL 文件包含服务 URL 和命名空间,因此您不会出错。 当然,您仍然可以犯其他错误。

示例 12.16. 使用错误的参数调用方法

>>> 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 你发现错误了吗? 这是一个微妙的错误:您使用整数而不是字符串调用 server.getTemp。 正如您从内省 WSDL 文件中看到的那样,getTemp() SOAP 函数接受一个参数 zipcode,它必须是一个字符串。 WSDL.Proxy 不会 为您强制转换数据类型;您需要传递服务器期望的确切数据类型。
2 同样,服务器返回一个 SOAP 错误,并且错误中人类可读的部分提供了一个有关问题的线索:您正在使用整数值调用 getTemp 函数,但是没有定义接受整数的同名函数。 理论上,SOAP 允许您对函数进行 重载,因此您可以在同一个 SOAP 服务中拥有两个具有相同名称和相同数量参数的函数,但参数的数据类型不同。 这就是为什么精确匹配数据类型很重要的原因,以及为什么 WSDL.Proxy 不为您强制转换数据类型。 如果是这样,您最终可能会调用一个完全不同的函数! 祝你好运调试那个。 如果您弄错了数据类型,最好对数据类型挑剔,并尽快失败。

还可以编写期望与远程函数实际返回值数量不同的返回值数量的 Python 代码。

示例 12.17. 调用一个方法并期望错误数量的返回值

>>> 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 你发现错误了吗? server.getTemp 只返回一个值,一个浮点数,但您编写的代码假设您正在获取两个值并尝试将它们分配给两个不同的变量。 请注意,这不会因 SOAP 错误而失败。 就远程服务器而言,一切正常。 错误仅在 SOAP 事务完成 之后 发生,WSDL.Proxy 返回一个浮点数,并且您的本地 Python 解释器试图满足您将其拆分为两个不同变量的请求。 由于该函数只返回一个值,因此您在尝试拆分它时会遇到 Python 异常,而不是 SOAP 错误。

那谷歌的网络服务呢? 我遇到的最常见问题是我忘记正确设置应用程序密钥。

示例 12.18. 使用特定于应用程序的错误调用方法

>>> 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 你能发现错误吗? 调用语法、参数数量或数据类型都没有问题。 问题是特定于应用程序的:第一个参数应该是我的应用程序密钥,但 foo 不是有效的 Google 密钥。
2 Google 服务器响应了一个 SOAP 错误和一条非常长的错误消息,其中包括完整的 Java 堆栈跟踪。 请记住,所有 SOAP 错误都由 SOAP 错误表示:配置错误、函数参数错误以及像这样的应用程序特定错误。 其中隐藏着关键信息:无效的授权密钥:foo

有关 SOAP 故障排除的进一步阅读