Zanurkuj w Pythonie/Rozwiązywanie problemów

Z Wikibooks, biblioteki wolnych podręczników.

Rozwiązywanie problemów[edytuj]

Oczywiście świat serwisów SOAP to nie jest tylko kraina mlekiem i miodem płynąca. Czasami coś idzie nie tak.

Jak już widziałeś w tym rozdziale na SOAP składa się kilka warstw. Jest tam warstwa HTTP, ponieważ SOAP wysyła dokumenty XML do i odbiera te dokumenty od serwera HTTP. A więc wszystkie techniki dotyczące debugowania, których nauczyłeś się w rozdziale 11 HTTP, mają zastosowanie także tutaj. Możesz zaimportować httplib i ustawić httplib.HTTPConnection.debuglevel = 1, aby zobaczyć cały ruch odbywający się poprzez HTTP.

Poza tą warstwą HTTP jest wiele rzeczy, którą mogą sie nie powieść. SOAPpy wykonuje godną podziwu robotę ukrywając przed Tobą składnię SOAP, ale to oznacza także, że może być trudne zdiagnozowanie problemu, gdy takowy się pojawi.

Oto kilka przykładów pomyłek, które robiłem używając serwisów SOAP i komunikaty błędów jakie one spowodowały.

Przykład 12.15. Wywoływanie metod z niewłaściwie skonfigurowanym Proxy

>>> 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. Zauważyłeś pomyłkę? Tworzymy ręcznie SOAPProxy i prawidłowo podajemy URL serwisu, ale nie podaliśmy przestrzeni nazw. Ponieważ wiele serwisów może działaś na tym samym URL-u, przestrzeń nazw jest bardzo istotna, aby ustalić do którego serwisu próbujemy się odwołać, a następnie jaką metodę właściwie wywołujemy.
  2. Serwer odpowiada poprzez wysłanie SOAP Fault, który SOAPpy zamienia na pythonowy wyjątek typu SOAPpy.Types.faultType. Wszystkie błędy zwracane przez serwer SOAP zawsze będą obiektami SOAP Fault, a więc łatwo możemy te wyjątki przechwycić. W tym przypadku, ta czytelna dla człowieka część SOAP Fault daje wskazówkę do tego jaki jest problem: element metoda nie jest zawarty w przestrzeni nazw, ponieważ oryginalny obiekt SOAPProxy nie został skonfigurowany z przestrzenią nazw serwisu.

Błędna konfiguracja podstawowych elementów serwisu SOAP jest jednym z problemów, które ma za zadanie rozwiązać WSDL. Plik WSDL zawiera URL serwisu i przestrzeń nazw, a więc nie można ich podać błędnie. Oczywiście nadal są inne rzeczy, które mogą zostać podane błędnie.

Przykład 12.16. Wywołanie metody z nieprawidłowymi argumentami

>>> 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. Zauważyłeś pomyłkę? To jest subtelna pomyłka: wywołujemy server.getTemp z liczbą całkowitą (ang. integer) zamiast z łańcuchem znaków (ang. string). Jak już widziałeś w pliku WSDL, funkcja getTemp() SOAP przyjmuje pojedynczy argument, kod pocztowy, który musi być łańcuchem znaków. WSDL.Proxy nie będzie konwertował typów danych; musimy podać dokładnie te typy danych jakich serwer oczekuje.
  2. I znowu, serwer zwraca SOAP Fault i czytelna dla człowieka część komunikatu błędu daje wskazówkę do tego, gdzie leży problem: wywołujemy funkcję getTemp z liczbą całkowitą, ale nie ma zdefiniowanej funkcji o tej nazwie, która przyjmowałaby liczbę całkowitą. W teorii SOAP pozwala na przeciążanie funkcji, a więc jeden serwis SOAP mógłby posiadać dwie funkcje o tej samej nazwie i z taką samą liczbą argumentów, ale z argumentami o różnych typach. O to dlaczego tak ważne jest podawanie właściwych typów. i dlaczego WSDL.Proxy nie konwertuje typów danych. Gdyby to robił, to mogłoby się zdarzyć, że wywołalibyśmy zupełnie inną funkcję! Powodzenia w debugowaniu takiego błędu. Dużo łatwiej jest być krytycznym wobec typów danych i zgłaszać błędy tak szybko jak to tylko możliwe.

Jest także możliwe napisanie kodu Pythona, który oczekuje innej liczby zwracanych wartości, niż zdalna funkcja właściwie zwraca.

Przykład 12.17. Wywołanie metody i oczekiwanie niewłaściwej liczby zwracanych wartości

>>> 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. Zauważyłeś pomyłkę? server.getTemp zwraca tylko jedną wartość, liczbę zmiennoprzecinkową (ang. float), ale my napisaliśmy kod, który zakłada, że otrzymamy dwie wartości i próbuje je przypisać do dwóch oddzielnych zmiennych. Zauważ, że tutaj nie pojawił się wyjątek SOAP fault. Co do zdalnego serwera, to wszystko odbyło się jak należy. Błąd pojawił się dopiero po zakończeniu transakcji SOAP, WSDL.Proxy zwrócił liczbę zmiennoprzecinkową a nasz lokalny interpreter Pythona próbował zgodnie z naszym zaleceniem podzielić ją pomiędzy dwie zmienne. Ponieważ funkcja zwróciła tylko jedną wartość, został zgłoszony wyjątek Pythona, a nie SOAP Fault.

A co z serwisem Google? Najczęstszym problemem jaki z nim miałem było to, że zapominałem właściwie ustawić klucz aplikacji.

Przykład 12.18. wywołanie metody z błędem specyficznym dla aplikacji

>>> 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. Zauważyłeś pomyłkę? Nie ma błędów w samej składni wywołania lub w liczbie argumentów lub w typach danych. Problem jest specyficzny dla tej konkretnej aplikacji: pierwszym argumentem powinien być nasz klucz aplikacji, ale foo nie jest prawidłowym kluczem dla Google.
  2. Serwer Google odpowiada poprzez SOAP Fault i niesamowicie długi komunikat błędu, który zawiera kompletny zrzut stosu Javy. Zapamiętaj, że wszystkie błędy SOAP są oznaczane poprzez SOAP Faults: błędy w konfiguracjach, błędy w argumentach funkcji i błędy specyficzne dla aplikacji jak ten. Zakopana gdzieś tam jest kluczowa informacja: Invalid authorization key: foo (niewłaściwy klucz autoryzacji).