☜ 제 11 장 HTTP 웹 서비스 """ Dive Into Python """
다이빙 파이썬
제 13 장 유닛 테스트 ☞

제 12 장 SOAP 웹 서비스

제 11 장에서는 HTTP를 통한 문서-지향적인 웹 서비스에 초점을 두었습니다. “입력 매개변수”는 URL이었고, “반환 값”은 실제 XML 문서였습니다. 문서를 해석하는 일은 여러분의 몫이었습니다.

이 장에서는 SOAP 웹 서비스에 초점을 두겠습니다. 이 서비스는 좀 더 구조적인 접근법을 취합니다. 직접적으로 HTTP 요청과 XML 문서를 다루는 대신에, SOAP를 사용하면 함수를 호출하는 것을 흉내낼 수 있습니다. 이 함수는 고유의 데이터 유형을 돌려줍니다. 앞으로 보시겠지만 상상의 나래는 거의 완벽합니다; SOAP 라이브러리를 통하여, 표준적인 파이썬 호출 구문으로 함수를 “호출할 수 있으며”, 호출된 함수는 파이썬 객체와 값을 돌려주는 듯 보입니다. 그러나 뚜껑 아래를 보면 SOAP 라이브러리가 실제로 여러 문서와 원격 서버 사이에 관련된 복잡한 거래를 수행하고 있습니다.

SOAP는 규격이 복잡합니다. 혹시라도 SOAP가 원격 함수를 호출하는 것에 관한 것일 뿐이라고 오해할 우려가 있습니다. 덧붙여서 어떤 사람들은 SOAP 덕분에 단-방향 비동기 메시지 건넴 그리고 문서-지향적 웹 서비스가 가능해질 것이라고 자랑합니다. 그리고 그 말이 맞을 것입니다; SOAP는 그렇게, 그리고 여러 방면으로 사용될 수 있습니다. 그러나 이 장에서는 이른바 “RPC-스타일의SOAP에 초점을 두겠습니다 -- 원격 함수를 호출하고 그의 결과를 다시 받는 것에 집중하겠습니다.

12.1. 뛰어 들기

구글을 사용하지요, 맞습니까? 구글은 인기있는 검색 엔진입니다. 프로그램적으로 구글 검색 결과에 접근해보고 싶은 적이 있습니까? 이제 가능합니다. 다음은 구글을 파이썬으로 검색하는 프로그램입니다.

예제 12.1. search.py

from SOAPpy import WSDL

# 다음 두 값을 환경구성해 줄 필요가 있다;
# http://www.google.com/apis/ 참조
WSDLFILE = '/path/to/copy/of/GoogleSearch.wsdl'
APIKEY = 'YOUR_GOOGLE_API_KEY'

_server = WSDL.Proxy(WSDLFILE)
def search(q):
    """구글을 검색해 {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

이를 모듈로 반입하여 더 큰 프로그램에 사용하거나, 스크립트를 명령어 줄에서 실행할 수 있습니다. 명령 줄에서 검색 질의를 명령-줄 인자로 주면 URL과 타이틀 그리고 상위 다섯 개의 구글 검색 결과의 설명을 인쇄합니다.

다음은 “python”이라는 단어를 검색해 본 샘플 출력입니다.

예제 12.2. 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>

SOAP에 관하여 더 읽어야 할 것

  • http://www.xmethods.net/에 공개적으로 SOAP 웹 서비스에 접근해 볼 수 있다.
  • SOAP 규격은 놀랍도록 읽기 쉽다. 그런 종류를 좋아한다면 말이다.

12.2. SOAP 라이브러리 설치하기

이 책의 다른 코드와는 다르게, 이 장은 파이썬과 함께 미리 설치된 라이브러리에 의존하지 않습니다.

SOAP 웹 서비스 안으로 들어가기 전에, 세 개의 라이브러리를 설치할 필요가 있습니다: PyXMLfpconst 그리고 SOAPpy가 그것입니다.

12.2.1. PyXML 설치하기

제일 먼저 PyXML 라이브러리가 필요합니다. 이 라이브러리는 고급 XML 라이브러리 집합으로서 제 9 장에서 공부한 내장 XML 라이브러리보다 기능을 더 많이 제공합니다.

절차 12.1. 

다음은 PyXML을 설치하는 절차입니다:

  1. http://pyxml.sourceforge.net/에 가셔서, 내려받기를 클릭하고, 운영체제에 맞게 최신 버전을 내려받으세요.

  2. 윈도우즈를 사용하신다면 여러 선택이 있습니다. 반드시 사용중인 파이썬 버전에 부합하는 PyXML 버전을 내려받으세요.

  3. 설치기를 더블-클릭하세요. 윈도우즈 용으로 PyXML 0.8.3 그리고 파이썬 2.3을 내려받는다면 설치기 프로그램은 PyXML-0.8.3.win32-py2.3.exe입니다.

  4. 설치 프로그램의 절차를 밟으세요.

  5. 설치가 끝나면 설치기를 닫으세요. 눈에 보이게 성공했다는 표시는 없습니다 (시작 메뉴에 프로그램이 설치되는 것도 아니고 데스크탑에도 단축 아이콘이 설치되지 않습니다). PyXML는 단순히 다른 프로그램에서 사용되는 XML 라이브리를 모아 놓은 것입니다.

PyXML이 제대로 설치되었는지 확인하려면 파이썬 IDE를 실행하고 다음에 보여주는 바와 같이 설치한 XML 라이브러리의 버전을 점검하면 됩니다.

예제 12.3. PyXML이 설치되었는지 확인하기

>>> import xml
>>> xml.__version__
'0.8.3'

이 버전 번호는 내려받아 실행한 PyXML 설치기 프로그램의 버전 번호와 일치합니다.

12.2.2. fpconst 설치하기

두 번째로 필요한 라이브러리는 fpconst입니다. 이는 IEEE754 배-정도 특수 값과 작동하는 상수와 함수의 세트입니다. 이는 특수 값으로 숫자-아님(Not-a-Number (NaN))과 무한 양수(Positive Infinity (Inf)) 그리고 무한 음수(Negative Infinity (-Inf))를 지원하는데, 이 값들은 SOAP 데이터유형 규격의 일부입니다.

절차 12.2. 

다음은 fpconst를 설치하는 절차입니다:

  1. 최신 버전의 fpconsthttp://www.analytics.washington.edu/statcomp/projects/rzope/fpconst/에서 내려받으세요.

  2. 내려받기는 두 가지 형태가 있는데, 하나는 .tar.gz 포맷이고,다른 하나는 .zip 포맷입니다. 윈도우즈를 사용하고 있다면 .zip 파일을 내려받으세요; 그렇지 않으면 .tar.gz 파일을 내려받으세요.

  3. 내려받은 파일의 압축을 푸세요. Windows XP라면 파일에다 오른쪽-클릭을 하고 Extract All을 선택하면 됩니다; 이전 윈도우즈 버전이라면 WinZip 같은 제-삼자 프로그램이 필요합니다. Mac OS X이라면 Stuffit Expander로 압축된 파일을 더블-클릭하면 압축을 풀 수 있습니다.

  4. 명령어 프롬프트를 열고 fpconst 파일이 풀려 있는 디렉토리로 찾아가세요.

  5. python setup.py install를 타자하면 설치 프로그램을 실행할 수 있습니다.

제대로 fpconst가 설치되었는지 확인하려면 파이썬 IDE를 실행하고 버전 번호를 점검하면 됩니다.

예제 12.4. fpconst가 설치되었는지 확인하기

>>> import fpconst
>>> fpconst.__version__
'0.6.0'

이 버전 번호는 내려받아 설치한 fpconst 압축파일의 버전 번호와 일치합니다.

12.2.3. SOAPpy 설치하기

세 번째 마지막 필수 라이브러리는 SOAP 라이브러리입니다: SOAPpy 그 자체로 라이브러리입니다.

절차 12.3. 

다음은 SOAPpy를 설치하기 위한 절차입니다:

  1. http://pywebsvcs.sourceforge.net/로 가서 SOAPpy 섹션에서 최신 공식 배포본을 선택하세요.

  2. 두 가지 내려 받기 형태가 가능합니다. 윈도우즈를 사용하고 있다면 .zip 파일을 내려받고; 그렇지 않으면 .tar.gz 파일을 내려받으세요.

  3. fpconst에서 했던 것과 똑 같이, 내려받은 파일의 압축을 푸세요.

  4. 명령어 프롬프트를 열고 SOAPpy 파일을 풀어놓은 디렉토리로 찾아 가세요.

  5. python setup.py install를 타자해서 설치 프로그램을 실행하면 됩니다.

제대로 SOAPpy가 설치되었는지 확인하려면 파이썬 IDE를 실행하고 버전 번호를 점검하세요.

예제 12.5. SOAPpy가 설치되었는지 확인하기

>>> import SOAPpy
>>> SOAPpy.__version__
'0.11.4'

이 버전 번호는 내려받아 설치한 SOAPpy 아카이브의 버전 번호와 일치합니다.

12.3. SOAP 처리 첫 단계

SOAP의 심장은 원격 함수를 호출하는 능력입니다. 데모의 목적으로 간단한 기능을 제공하는, 공개적으로 접근이 가능한 SOAP 서버가 수 없이 많습니다.

가장 인기 있는 공개 접근 SOAP 서버는 http://www.xmethods.net/입니다. 다음 예제는 미국 우편번호를 취해 그 지역의 현재 기온을 돌려주는 데모 함수를 사용합니다.

예제 12.6. 현재 기온 얻는 방법

>>> from SOAPpy import SOAPProxy            
>>> url = 'http://services.xmethods.net:80/soap/servlet/rpcrouter'
>>> namespace = 'urn:xmethods-Temperature'  
>>> server = SOAPProxy(url, namespace)      
>>> server.getTemp('27502')                 
80.0
SOAPProxy라는 프록시 클래스를 통하여 원격 SOAP 서버에 접근합니다. 이 프록시는 여러분을 대신하여 SOAP의 모든 내부 작동을 처리합니다. 여기에는 함수 이름과 인자 리스트로부터 XML 요청 문서를 생성하는 것, 그 요청을 HTTP를 타고 원격 SOAP 서버에 보내는 것, XML 응답 문서를 해석하는 것, 그리고 돌려줄 고유의 파이썬 값을 만드는 것이 포함됩니다. 다음 섹션에서 이런 XML 문서가 어떤 모습인지 보여드리겠습니다.
SOAP 서비스마다 모든 요청을 처리하는 URL이 있습니다. 같은 URL이 모든 함수 호출에 사용됩니다. 이 특별한 서비스는 오직 하나의 함수만 있지만 이 장의 후반부에서 함수가 여러 개 있는 구글 API를 보여드리겠습니다. 서비스 URL은 모든 함수가 공유합니다. SOAP 서비스마다 이름공간이 있습니다. 이 이름공간은 서버가 정의하며 완전히 서버의 마음입니다. 이름공간은 SOAP 메쏘드를 호출하려면 필수적인 환경구성의 일부입니다. 덕분에 서버는 하나의 서비스 URL을 공유하고 관련없는 여러 서비스 사이에 요청을 회람시킬 수 있습니다. 비유하자면 파이썬 모듈들을 패키지로 집어 넣는 것과 비슷합니다.
서비스 URL과 서비스 이름공간으로 SOAPProxy를 만들고 있습니다. 이렇게 하더라도 SOAP 서버에 접속되지는 않습니다; 단순히 지역 파이썬 객체를 만들 뿐입니다.
이제 모든 것이 제대로 설정되었으므로, 실제로 원격 SOAP 메쏘드를 마치 지역 함수처럼 호출할 수 있습니다. 보통의 함수처럼 인자를 건네고, 보통의 함수처럼 값을 돌려 받습니다. 그러나 뚜껑 아래에서는 엄청나게 많은 일이 진행됩니다.

뚜껑을 열고 들여다 보겠습니다.

12.4. SOAP 웹 서비스 디버깅 하기

SOAP 라이브러리는 배경 뒤에서 무슨 일이 일어나는지 쉽게 볼 수 있는 방법을 제공합니다.

디버깅을 켜려면 단순히 두 개의 플래그를 SOAPProxy에서 환경구성해 주면 됩니다.

예제 12.7. SOAP 웹 서비스 디버깅하는 방법

>>> from SOAPpy import SOAPProxy
>>> url = 'http://services.xmethods.net:80/soap/servlet/rpcrouter'
>>> n = 'urn:xmethods-Temperature'
>>> server = SOAPProxy(url, namespace=n)     
>>> server.config.dumpSOAPOut = 1            
>>> server.config.dumpSOAPIn = 1
>>> temperature = server.getTemp('27502')    
*** 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
먼저, 예와 같이 서비스 URL과 이름공간을 가지고 SOAPProxy를 만듭니다.
둘째, server.config.dumpSOAPInserver.config.dumpSOAPOut를 세팅해서 디버깅을 켭니다.
셋째, 예와 같이 SOAP 메쏘드를 호출합니다. SOAP 라이브러리는 출력 XML 요청 문서와 입력 XML 응답 문서를 모두 인쇄합니다. 이것이 바로 여러분을 대신하여 SOAPProxy가 해주는 고된 작업입니다. 부담스럽지요, 그렇지 않습니까? 이제 분해해 보겠습니다.

서버에게 전송되는 XML 요청 문서는 대부분 단지 보일러플레이트일 뿐입니다. 모든 이름공간 선언은 무시됩니다; 이름공간은 모두 모든 SOAP 호출에 대하여 (비슷하거나) 같습니다. “함수 호출의 심장”은 <Body> 원소 안에 있는 이 조각입니다:

<ns1:getTemp                                 
  xmlns:ns1="urn:xmethods-Temperature"       
  SOAP-ENC:root="1">
<v1 xsi:type="xsd:string">27502</v1>         
</ns1:getTemp>
원소 이름은 함수 이름 getTemp입니다. SOAPProxygetattr를 분배자로 사용합니다. 메쏘드 이름에 근거하여 따로 지역 메쏘드를 호출하는 대신에, 실제로는 메쏘드 이름을 사용하여 XML 요청 문서를 구성합니다.
함수의 XML 원소는 특정한 이름공간에 속합니다. 이 특정한 이름공간은 SOAPProxy 객체를 만들 때 지정한 이름공간입니다. SOAP-ENC:root에 관하여 걱정하지 마세요; 역시 보일러플레이트입니다.
함수의 인자도 XML로 번역됩니다. SOAPProxy는 각 인자를 조사하여 그의 데이터유형을 결정합니다 (이경우 문자열임). 인자의 데이터유형은 실제 문자열 값 다음에 따라오는 xsi:type 속성 안으로 들어갑니다.

무엇을 무시할지 알기만 하면 XML 반환 문서도 똑 같이 이해하기 쉽습니다. <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>
서버는 함수 반환 값을 <getTempResponse> 원소 안에 싸 넣습니다. 관례적으로, 이 포장자 원소의 이름은 그 함수의 이름에다 Response가 붙습니다. 그러나 실제로 거의 무엇이든 될 수 있습니다; SOAPProxy가 인지하는 중요한 것은 원소의 이름이 아니라, 이름공간입니다.
서버는 요청에서 사용한 이름공간에 응답을 돌려줍니다. 이 이름공간은 처음 SOAPProxy를 만들 때 지정한 이름공간입니다. 이 장의 후반부에서 SOAPProxy를 만들 때 이름공간을 잊고 지정하지 않으면 무슨 일이 일어나는지 알아보겠습니다.
반환 값이 그의 데이터 유형(부동소수점수)과 함께 지정됩니다. SOAPProxy는 이 명시적인 데이터유형을 사용하여 올바른 고유의 데이터유형의 파이썬 객체를 만들어 돌려줍니다.

12.5. WSDL 소개하기

SOAPProxy 클래스는 지역 메쏘드 호출을 대리하고, 투명하게 원격 SOAP 메쏘드를 요청합니다. 보시다시피, 일이 많습니다. SOAPProxy는 신속하고 투명하게 처리합니다. 해주지 않는 일이 있다면 메쏘드 내부검사의 수단을 제공하지 않는다는 것입니다.

다음을 생각해 보세요: 앞 두 섹션에서는 둘 다 간단한 데이터 유형인 하나의 인자와 하나의 반환 값을 가지고 간단한 원격 SOAP 메쏘드를 호출하는 법을 보여주었습니다. 이렇게 하려면 서비스 URL, 서비스 이름공간, 함수 이름, 인자 개수, 그리고 각 인자의 데이터유형을 이해하고 추적 유지해야 했습니다. 이 중에 하나가 빠지거나 잘못되면 모든 일이 어그러집니다.

별로 놀랄일은 아닙니다. 지역 함수를 호출하고 싶다면 그것이 (서비스 URL과 이름공간에 해당하는) 어느 모듈 또는 어느 패키지에 있는지 알 필요가 있습니다. 함수 이름과 인자의 개수를 올바르게 알 필요가 있습니다. 파이썬은 명시적으로 유형이 지정되지 않아도 솜씨좋게 데이터유형을 처리합니다. 그러나 여전히 얼마나 많은 인자를 건네야 하는지, 그리고 얼마나 많은 반환 값을 예상해야 하는지 알 필요가 있습니다.

큰 차이점은 내부검사입니다. 제 4 장에서 보셨듯이, 파이썬은 실행시간에 모듈과 함수에 관하여 상황을 탁월하게 알려줄 수 있습니다. 모듈 안에서 사용가능한 함수를 나열할 수 있고, 조금만 노력하면 함수 별로 선언과 인자에까지 깊이 들어갈 수 있습니다.

WSDL을 사용하면 SOAP 웹 서비스에 그렇게 할 수 있습니다. WSDL은 웹 서비스 기술 언어(“Web Services Description Language”)의 약자입니다. 다양한 웹 서비스 유형을 기술할 만큼 충분히 유연하게 설계되었지만 보통 SOAP 웹 서비스를 기술하는데 사용됩니다.

WSDL 파일은 말 그대로: 파일입니다. 더 구체적으로 말하면 XML 파일입니다. 보통 WSDL 파일에 기술된 SOAP 웹 서비스에 접근하기 위하여 여러분이 사용하는 서버와 같은 서버에 존재합니다. 물론, 그에 관하여 특별한 것은 전혀 없습니다. 이 장의 후반부에서 구글 API용으로 WSDL 파일을 내려받아 지역적으로 사용해 보겠습니다. 그렇다고 해서 지역적으로 구글(Google)을 호출하겠다는 뜻은 아닙니다; WSDL 파일은 여전히 구글 서버에 앉아있는 원격 함수를 기술합니다.

WSDL 파일에 SOAP 웹 서비스를 호출하는데 관련된 모든 것들이 기술됩니다:

  • 서비스 URL과 이름공간
  • 웹 서비스 유형 (SOAP를 사용한 함수 호출일 것이다. 물론, 언급했듯이, WSDL은 유연하게 다양한 웹 서비스를 기술할 수 있다.)
  • 가능한 함수 리스트
  • 각 함수에 대한 인자
  • 각 인자의 데이터 유형
  • 각 함수의 반환 값, 그리고 각 반환 값의 데이터유형

다른 말로 해서, WSDL 파일은 SOAP 웹 서비스를 호출하는데 필요한 모든 것을 알려줍니다.

12.6. WSDLSOAP 웹 서비스 내부검사하는 법

웹 서비스 분야가 보통 그런 것처럼, WSDL는 사연이 긴 역사가 있어서 정치적 분쟁과 다툼으로 가득합니다. 이 역사를 완전히 건너뛰려고 합니다. 왜냐하면 눈물이 앞을 가리기 때문입니다. 비슷한 일을 시도한 다른 표준이 있었지만 WSDL이 승리했습니다. 그래서 그 사용법을 배워보겠습니다.

WSDL로 할 수 있는 가장 근본적인 일은 SOAP 제공하는 메쏘드들을 찾아내는 것입니다.

예제 12.8. 사용가능한 메쏘드가 무엇인지 알아보는 방법

>>> from SOAPpy import WSDL          
>>> wsdlFile = 'http://www.xmethods.net/sd/2001/TemperatureService.wsdl')
>>> server = WSDL.Proxy(wsdlFile)    
>>> server.methods.keys()            
[u'getTemp']
SOAPpyWSDL 해석기가 포함되어 있습니다. 이 글을 쓰는 시점에서, 초기 개발 단계라는 라벨이 붙어 있었지만 시도해 본 WSDL 파일을 아무 문제 없이 해석해 주었습니다.
WSDL 파일을 사용하기 위해, 다시 프록시 클래스 WSDL.Proxy를 사용합니다. 이 프록시 클래스는 인자를 하나 받습니다: WSDL 파일이 바로 그것입니다. 이 경우 원격 서버에 저장된 WSDLURL을 건네고 있음에 주목하세요. 그러나 이 프록시 클래스는 WSDL 파일의 로컬 사본과 똑같이 작동합니다. WSDL 프록시를 만들면 WSDL 파일을 내려받아 해석합니다. 그래서 WSDL 파일에 에러가 있다면 (또는 네트워크 문제 때문에 가져올 수 없다면), 즉시 알 수 있습니다.
WSDL 프록시 클래스는 파이썬 사전인 server.methods와 같은 함수들을 보여줍니다. 그래서 가능한 메쏘드 리스트를 얻으려면 그냥 사전 메쏘드 keys()를 호출하기만 하면 됩니다.

좋습니다. 그래서 이 SOAP 서버는 getTemp 메쏘드 하나를 제공하는군요. 그러나 어떻게 호출할까요? WSDL 프록시 객체는 그 방법 역시 알려줄 수 있습니다.

예제 12.9. 메쏘드의 인자 알아 보는 법

>>> callInfo = server.methods['getTemp']  
>>> callInfo.inparams                     
[<SOAPpy.wstools.WSDLTools.ParameterInfo instance at 0x00CF3AD0>]
>>> callInfo.inparams[0].name             
u'zipcode'
>>> callInfo.inparams[0].type             
(u'http://www.w3.org/2001/XMLSchema', u'string')
server.methods 사전은 CallInfo라고 부르는 SOAPpy-종속적 구조로 채워집니다. CallInfo 객체에는 함수 인자를 포함하여, 한가지 특정한 함수에 관한 정보가 담깁니다.
함수 인자는 callInfo.inparams에 저장됩니다. 이는 ParameterInfo 객체들이 담긴 파이썬 리스트로서 각 매개변수에 대한 정보를 보유합니다.
ParameterInfo 객체에는 name 속성이 담겨 있는데, 이는 인자 이름입니다. SOAP를 통하여 함수를 호출하기 위해 인자 이름을 알아야 할 필요는 없지만 SOAP는 (파이썬과 똑같이) 이름붙은 인자가 있는 함수를 호출하는 것을 지원하며, 그를 사용하기로 결정했다면 WSDL.Proxy 는 이름붙은 인자들을 원격 함수에 올바르게 짝짓기합니다.
각 매개변수도 명시적으로 유형이 정의되며, XML 스키마에 정의된 데이터유형을 사용합니다. 앞 섹션에서 유선 추적을 할 때 이를 보셨습니다; XML 스키마 이름공간은 “보일러플레이트(boilerplate)”의 일부였습니다; 무시해도 좋다고 말씀드렸습니다. 여기에서도 우리의 목적을 위하여 무시해도 좋습니다. zipcode 매개변수는 문자열이며, 파이썬 문자열을 WSDL.Proxy 객체에 건네면 그것을 올바르게 짝지어서 서버에 보냅니다.

WSDL에서도 함수의 반환 값을 들여다 볼 수 있습니다.

예제 12.10. 메쏘드의 반환 값 알아보는 법

>>> callInfo.outparams            
[<SOAPpy.wstools.WSDLTools.ParameterInfo instance at 0x00CF3AF8>]
>>> callInfo.outparams[0].name    
u'return'
>>> callInfo.outparams[0].type
(u'http://www.w3.org/2001/XMLSchema', u'float')
함수 인자가 callInfo.inparams에 있듯이 반환 값은 callInfo.outparams에 있습니다. 이 역시 리스트입니다. 왜냐하면 SOAP를 통하여 호출된 함수는 파이썬 함수와 똑 같이 여러 값을 돌려줄 수 있기 때문입니다.
ParameterInfo 객체마다 nametype이 들어 있습니다. 이 함수는 return이라는 값을 하나 돌려줍니다. 이는 부동소수점수입니다.

모두 하나로 조립해서, WSDL 프록시를 통하여 SOAP 웹 서비스를 호출해 보겠습니다.

예제 12.11.  WSDL 프록시를 통하여 웹 서비스 호출하기

>>> from SOAPpy import WSDL
>>> wsdlFile = 'http://www.xmethods.net/sd/2001/TemperatureService.wsdl')
>>> server = WSDL.Proxy(wsdlFile)               
>>> server.getTemp('90210')                     
66.0
>>> server.soapproxy.config.dumpSOAPOut = 1     
>>> 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
SOAP 서비스를 직접 호출하는 것보다 환경구성하는 일이 더 간단합니다. 왜냐하면 WSDL 파일에 호출하는데 필요한 서비스 URL과 이름공간이 모두 포함되어 있기 때문입니다. WSDL.Proxy 객체를 만들면 WSDL 파일을 내려받아 해석하고, 실제 SOAP 웹 서비스를 호출하는데 사용하는 SOAPProxy 객체를 환경구성합니다.
WSDL.Proxy 객체가 생성되면 SOAPProxy 객체에서 그랬던 것처럼 쉽게 함수를 호출할 수 있습니다. 이는 별로 놀라운 일이 아닙니다; WSDL.Proxy는 그저 약간의 내부검사 메쏘드를 추가하고 SOAPProxy를 포장한 것에 불과하기 때문입니다. 그래서 함수를 호출하는 구문이 똑 같습니다.
server.soapproxyWSDL.ProxySOAPProxy에 접근할 수 있습니다. 이는 디버깅을 활성화할 때 유용한데, WSDL 프록시를 통하여 함수를 호출할 수 있으며, 그의 SOAPProxy는 전선을 타고 흘러 들어오고 나가는 XML 문서들을 뿌려줍니다.

12.7. 구글 검색하기

마지막으로 이 장의 초반부에서 본 샘플 코드에 주의를 돌려봅시다. 이 코드는 현재 온도를 얻는 것보다 무언가 더 유용하고 흥분되는 일을 합니다.

구글(Google)은 프로그램적으로 구글 검색 결과에 접근하는데 사용할 SOAP API를 제공합니다. 이를 사용하려면 구글 웹 서비스에 로그인할 필요가 있습니다.

절차 12.4. 구글 웹 서비스에 등록하기

  1. http://www.google.com/apis/에 가서 구글 계정을 만드세요. 이메일 주소만 필요합니다. 등록이 끝나면 이메일로 구글 API 라이센스를 받습니다. 구글의 검색 함수를 호출할 때마다 이 키를 매개변수로 건넬 필요가 있습니다.

  2. http://www.google.com/apis/에서, 구글 웹 API 개발자 키트를 내려받으세요. 여기에는 여러 언어로 된 샘플 코드가 들어 있으며 (파이썬은 아님), 더 중요한 것은 WSDL이 포함되어 있다는 것입니다.

  3. 개발자 키트 파일을 풀어서 GoogleSearch.wsdl을 찾으세요. 이 파일을 로컬 드라이브의 안전한 위치에 복사하세요. 나중에 이 장의 후반부에서 필요합니다.

개발자 키와 구글 WSDL 파일을 잘 아는 곳에 확보하면 구글 웹 서비스를 맛볼 수 있습니다.

예제 12.12. 구글 웹 서비스 들여다보기

>>> from SOAPpy import WSDL
>>> server = WSDL.Proxy('/path/to/your/GoogleSearch.wsdl') 
>>> server.methods.keys()                                  
[u'doGoogleSearch', u'doGetCachedPage', u'doSpellingSuggestion']
>>> callInfo = server.methods['doGoogleSearch']
>>> for arg in callInfo.inparams:                          
...     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')
구글 웹 서비스를 시작하는 일은 쉽습니다: 그냥 WSDL.Proxy 객체를 만들고 거기에다 구글 WSDL 파일의 사본이 로컬 드라이브 어디에 있는지 위치를 가르쳐 주면 됩니다.
WSDL 파일에 의하면 구글은 세가지 함수를 제공합니다: doGoogleSearchdoGetCachedPage 그리고 doSpellingSuggestion가 그것입니다. 정확하게 이름 그대로 행위합니다: 구글 검색을 수행하고 그 결과를 프로그램적으로 돌려주며, 구글에서 최근에 찾아 본 캐시 페이지에 접근해서, 자주 철자가 틀리는 검색어라면 올바른 철자를 제안합니다.
doGoogleSearch 함수는 다양한 유형의 매개변수들을 받습니다. 주의하세요. WSDL 파일을 보면 어떤 인자들이 있는지 그리고 그 인자들이 어떤 데이터유형인지 알 수 있지만 그것들이 무슨 뜻인지 어떻게 사용하는지는 알 수 없습니다. 이론적으로 오직 특정한 값만을 허용한다면 각 매개변수마다 용인할 만한 범위의 값을 알 수 있겠지만 구글의 WSDL 파일은 그 정도로 상세하지는 않습니다. WSDL.Proxy는 마법처럼 작동할 수 없습니다; 오직 WSDL 파일에 주어진 정보만 제공할 수 있을 뿐입니다.

다음에 doGoogleSearch에 건네지는 모든 매개변수들을 간략하게 요약해 놓았습니다:

  • key - 구글 API 키로서, 구글 웹 서비스에 등록할 때 부여 받았습니다.
  • q - 찾고자 하는 단어나 구절입니다. 구문은 정확하게 구글의 웹 폼과 동일합니다. 그래서 고급 검색 구문이나 트릭을 아신다면 여기에서도 모두 작동합니다.
  • start - 돌려주기 시작할 결과 인덱스. 상호대화적인 웹 버전의 구글처럼, 이 함수는 한번에 10개의 결과를 돌려줍니다. 두 번째 “검색 결과 페이지”를 얻고싶다면 start를 10으로 설정하면 됩니다.
  • maxResults - 돌려줄 결과 개수. 현재는 10으로 잡혀 있으며, 단 몇개의 결과에만 관심이 있고 대역폭을 절약하고 싶다면 더 작게 지정해도 됩니다.
  • filter - True라면 구글은 결과에서 중복되는 페이지들을 걸러냅니다.
  • restrict - 특정한 국가의 결과만 얻으려면 여기에다 country와 국가 코드를 설정하면 됩니다. 예제: countryUK로 설정하면 영국에 해당하는 페이지만 검색합니다. 또 linuxmac 또는 bsd로 설정하면 구글에서-정의한 기술 사이트 집합을 검색할 수 있으며, unclesam으로 설정하면 미합중국에 관련된 사이트를 검색할 수 있습니다.
  • safeSearch - True이면 구글은 포르노 사이트를 걸러냅니다.
  • lr (“language restrict”) - 여기에다 언어 코드를 설정하면 특정 언어로 된 결과만을 얻을 수 있습니다.
  • ieoe (“input encoding” 그리고 “output encoding”) - 비추천, 둘 다 utf-8이어야 합니다.

예제 12.13. 구글 검색하기

>>> 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")             
>>> len(results.resultElements)                  
10
>>> results.resultElements[0].URL                
'http://diveintomark.org/'
>>> results.resultElements[0].title
'dive into <b>mark</b>'
WSDL.Proxy 객체를 설정하고 나면 10개의 매개변수를 모두 사용하여 server.doGoogleSearch를 호출할 수 있습니다. 구글 웹 서비스에 등록할 때 받은 자신만의 구글 API 키를 잊지 말고 사용하세요.
많은 정보가 반환되지만 먼저 실제 검색 결과를 살펴보겠습니다. 결색 결과는 results.resultElements에 저장되며, 보통의 파이썬 리스트와 똑같이 접근할 수 있습니다.
resultElements를 구성하는 원소는 URLtitle 그리고 snippet과 기타 유용한 속성들을 가진 객체입니다. 이 시점에서 dir(results.resultElements[0])과 같이 보통의 파이썬 내부검사 테크닉을 사용하면 가능한 속성들을 볼 수 있습니다. 또는 WSDL 프록시 객체의 내부를 들여다보고 그 함수의 outparams를 둘러볼 수 있습니다. 각 테크닉 모두 같은 정보를 얻습니다.

results 객체는 실제 검색 결과 그 이상이 담겨 있습니다. 검색 자체에 대한 정보도 담겨 있는데, 예를 들면 얼마나 걸렸는가 그리고 결과는 얼마나 많은가 등의 정보가 있습니다 (물론 검색 결과는 10개가 반환되었을지라도 말입니다). 구글 웹 인터페이스는 이 정보를 보여주며, 역시 프로그램적으로 그에 접근할 수 있습니다.

예제 12.14. 구글에 보충 정보를 요구해 접근하는 방법

>>> results.searchTime                     
0.224919
>>> results.estimatedTotalResultsCount     
29800000
>>> results.directoryCategories            
[<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'
이 검색은 0.224919 초가 걸렸습니다. 거기에는 실제 SOAP XML 문서를 보내고 받는데 소비된 시간은 포함되지 않습니다. 구글이 요청을 받은 후 처리하는데 소비된 시간일 뿐입니다.
총, 대략 3000 만 건의 걸과가 있었습니다. start 매개변수를 바꾸고 다시 server.doGoogleSearch를 호출하면 한 번에 10개씩 접근할 수 있습니다.
어떤 질의에 대해서는, 구글이 구글 디렉토리에 있는 관련 카테고리 리스트도 돌려줍니다. 이 URL들을 http://directory.google.com/에 추가하면 그 디렉토리 카테고리 페이지에 링크를 구성할 수 있습니다.

12.8. SOAP 웹 서비스 문제해결법

물론, SOAP 웹 서비스의 세계가 언제나 행복하고 가벼운 것은 아닙니다. 어떤 경우는 문제가 있습니다.

이 장을 통하여 보셨듯이, SOAP는 여러 레이어가 관련됩니다. HTTP 레이어가 있는데, SOAP는 XML 문서를 HTTP 서버에 보내고 받기 때문입니다. 그래서 제 11 장, HTTP 웹 서비스 에서 배운 모든 디버깅 테크닉이 여기에서 제 역할을 합니다. import httplib를 한 다음 httplib.HTTPConnection.debuglevel = 1로 설정하면 아래에서 오고가는 HTTP 흐름을 볼 수 있습니다.

아래의 HTTP 레이어를 올라서면 수 많은 것들이 잘못될 수 있습니다. SOAPpySOAP 구문을 여러분으로부터 가려주는 놀라운 일을 하지만 그 의미는 일이 잘못되었을 때 문제가 어디에 있는지 찾아내기가 어려울 수 있다는 뜻이기도 합니다.

다음은 본인이 SOAP 웹 서비스를 사용하면서 흔히 저지른 실수와 그 때문에 발생하는 에러의 예들입니다.

예제 12.15. 잘못 구성된 프록시로 메쏘드 요청하기

>>> from SOAPpy import SOAPProxy
>>> url = 'http://services.xmethods.net:80/soap/servlet/rpcrouter'
>>> server = SOAPProxy(url)                                        
>>> server.getTemp('27502')                                        
<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?>
실수를 찾아내셨습니까? 손수 SOAPProxy를 만들고 있고, 올바르게 서비스 URL을 지정했지만 이름공간을 지정하지 않았습니다. 여러 서비스가 같은 서비스 URL을 경유할 수 있으므로, 어느 서비스에 대화를 시도하려고 하는지, 그러므로 어느 메쏘드를 실제로 호출하고 있는지 결정하려면 이름공간은 필수적입니다.
서버는 SOAP Fault를 전송하여 응답합니다. 이를 SOAPpySOAPpy.Types.faultType이라는 유형의 파이썬 예외로 변환합니다. SOAP 서버로부터 받은 예외는 모두 언제나 SOAP Fault가 됩니다. 그래서 쉽게 이 예외를 잡을 수 있습니다. 이 경우, 인간이-읽을 수 있는 SOAP Fault 부분에 문제에 대한 실마리가 주어집니다: 메쏘드 원소에 이름공간이 없습니다. 왜냐하면 원래 SOAPProxy 객체가 서버 이름공간으로 환경구성되지 않았기 때문입니다.

SOAP 서비스의 기본 원소들을 제대로 환경구성하지 못하는 것은 WSDL이 풀고자 하는 문제중의 하나입니다. WSDL 파일에는 서비스 URL과 이름공간이 포함되어 있으므로, 잘못될 수 없습니다. 물론, 여전히 잘못될 것이 있습니다.

예제 12.16. 엉터리 인자를 가지고 메쏘드 호출하기

>>> wsdlFile = 'http://www.xmethods.net/sd/2001/TemperatureService.wsdl'
>>> server = WSDL.Proxy(wsdlFile)
>>> temperature = server.getTemp(27502)                                
<Fault SOAP-ENV:Server: Exception while handling service request:
services.temperature.TempService.getTemp(int) -- no signature match>   
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>
문제를 찾아 내셨나요? 미묘한 문제입니다: 문자열 대신 정수를 가지고 server.getTemp를 호출하고 있습니다. WSDL 파일 내부검사에서 보셨듯이, getTemp() SOAP 함수는 zipcode라는 인자를 하나 받는데, 이는 문자열이어야 합니다. WSDL.Proxy는 여러분 대신 데이터유형을 강제로 변환하지 않습니다; 서버가 예상하는 데이터유형에 맞게 정확하게 건넬 필요가 있습니다.
또, 서버는 SOAP Fault를 돌려줍니다. 그리고 인간이-읽을 수 있는 에러 부분에 문제에 대한 실마리가 주어집니다: 정수 값을 가지고 getTemp 함수를 호출하고 있지만 그 이름으로 정수를 취하는 함수가 정의되어 있지 않습니다. 이론적으로, SOAP를 사용하면 함수를 오버로드 할 수 있습니다. 그래서 같은 이름 같은 개수의 인자를 가진 SOAP 서비스에 두 개의 함수를 가질 수 있습니다. 그래서 인자들의 데이터유형이 다릅니다. 이 때문에 데이터유형을 정확하게 일치시켜주는 것이 중요하며, WSDL.Proxy가 여러분 대신 데이터유형을 강제로 변환시켜 주지 않는 것입니다. 강제로 변환시켜주었다면 결국 완전히 다른 함수를 호출하게 될지도 모릅니다! 그것을 디버깅하려면 운이 따라야 합니다. 차라리 데이터유형을 꼼꼼히 따져보고 잘못 받으면 곧바로 빨리 실패하는 편이 훨씬 더 쉽습니다.

원격 함수가 실제로 돌려주는 것과 다르게 반환 값의 개수를 예상하는 파이썬 코드를 작성하는 것도 가능합니다.

예제 12.17. 메쏘드를 호출하고 반환 값의 개수를 다르게 예상하기

>>> wsdlFile = 'http://www.xmethods.net/sd/2001/TemperatureService.wsdl'
>>> server = WSDL.Proxy(wsdlFile)
>>> (city, temperature) = server.getTemp(27502)  
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: unpack non-sequence
실수를 찾아내셨나요? server.getTemp는 값 하나, 부동소수점수 하나만을 돌려주지만 여러분이 작성한 코드는 두 개의 값을 받아서 두 개의 다른 변수에다 할당하려고 시도합니다. 주의하세요. 이는 SOAP fault로 실패하지 않습니다. 원격 서버에 관한 한, 아무것도 잘못되지 않았습니다. SOAP트랙잭션이 완료된 후에 에러가 일어났고, WSDL.Proxy는 부동소수점수를 하나 돌려주었는데, 지역 파이썬 인터프리터는 여러분의 요청을 받아들여 반환 값을 두 개의 다른 변수에 갈라 넣으려고 시도했습니다. 함수는 오직 값을 한 개만 돌려주기 때문에, SOAP Fault를 얻는 것이 아니라 그 값을 가르려고 시도하는 파이썬 예외를 얻습니다.

구글 웹 서비스는 어떤가? 본인은 흔히 어플리케이션 키를 제대로 설정하는 일을 잊어버리는 실수를 저지릅니다.

예제 12.18. 어플리케이션-종속적인 에러를 가지고 메쏘드를 호출하기

>>> from SOAPpy import WSDL
>>> server = WSDL.Proxy(r'/path/to/local/GoogleSearch.wsdl')
>>> results = server.doGoogleSearch('foo', 'mark', 0, 10, False, "", 
...     False, "", "utf-8", "utf-8")
<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
'}>
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
'}>
실수를 찾아내실 수 있습니까? 호출 구문이나 인자 개수 또는 그 데이터유형에는 아무 문제가 없습니다. 문제는 어플리케이션에 관련되어 있습니다: 첫 인자는 어플리케이션 키로 예정되어 있지만 foo는 유효한 구글 키가 아닙니다.
구글 서버는 SOAP Fault와 놀라울 정도로 기다란 에러 메시지로 응답하는데, 여기에는 완전한 자바 스택 추적이 포함되어 있습니다. 모든 SOAP 에러는 SOAP Faults로 인지됨에 주의하세요: 환경구성 에러와 함수 인자에러 그리고 이와 같은 어플리케이션-종속적 에러가 모두 그렇습니다. 깊숙한 어느 곳엔가 중요한 정보가 파묻혀 있습니다: Invalid authorization key: foo가 바로 그 정보입니다.

SOAP 문제 해결에 관하여 더 읽어야 할 것

12.9. 요약

SOAP 웹 서비스는 아주 복잡합니다. 규격은 아주 야망에 차 있고 웹 서비스의 다양한 사례들을 망라하려고 시도합니다. 이 장에서는 간단한 사용 사례들을 몇 가지 다루어 보았습니다.

다음 장으로 들어가기 전에, 다음 사항들을 편안하게 할 수 있는지 확인하세요:

  • SOAP 서버에 접속해서 원격 메쏘드를 호출하는 법
  • WSDL 파일을 적재하고 원격 메쏘드들을 내부검사하는 법
  • 유선 추적으로 SOAP 호출 디버깅 하는 법
  • 흔히 일어나는 SOAP-관련 에러들을 해결하는 법
☜ 제 11 장 HTTP 웹 서비스 """ Dive Into Python """
다이빙 파이썬
제 13 장 유닛 테스트 ☞