Document/Literal SOAP Messages mit SOAPpy

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
Benutzeravatar
Richard_M
User
Beiträge: 11
Registriert: Samstag 13. Januar 2007, 19:22
Wohnort: Nähe Stuttgart
Kontaktdaten:

Hallo erstmal,
Ich habe folgendes Problem:
Ich versuche mit SOAPpy eine Nachricht an einen Document/Literal Webserver zu senden:

Code: Alles auswählen

import SOAPpy
import time
import datetime
proxy = SOAPpy.WSDL.Proxy("http://www.holidaywebservice.com/Holidays/GBNIR/GBNIRHolidayService.asmx?WSDL")
proxy.soapproxy.config.dumpSOAPOut = 1
proxy.soapproxy.config.dumpSOAPIn = 1

def exploreService():
    result=proxy.methods.keys()
    i=0
    while i< len(result):
        print result[i]
        printMethods(result[i])
        i=i+1
         
def printMethods(methodName):
    callinfo= proxy.methods[methodName]
    i=0
    while i< len(callinfo.inparams):
        print callinfo.inparams[i].name
        print callinfo.inparams[i].type
        i=i+1
exploreService()
def printResult(result):
    i=0
    while i< len(result):
        print result[0]

result = proxy.GetHolidaysForYear(year=2007)
printResult(result)
Dies führt zu der folgenden Ausgabe:

Code: Alles auswählen

GetHolidaysForDateRange
parameters
(u'http://www.27seconds.com/Holidays/GBNIR/', u'GetHolidaysForDateRange')
GetHolidaysAvailable
parameters
(u'http://www.27seconds.com/Holidays/GBNIR/', u'GetHolidaysAvailable')
GetHolidayDate
parameters
(u'http://www.27seconds.com/Holidays/GBNIR/', u'GetHolidayDate')
GetHolidaysForMonth
parameters
(u'http://www.27seconds.com/Holidays/GBNIR/', u'GetHolidaysForMonth')
GetHolidaysForYear
parameters
(u'http://www.27seconds.com/Holidays/GBNIR/', u'GetHolidaysForYear')
*** 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>
<GetHolidaysForYear SOAP-ENC:root="1">
<year xsi:type="xsd:int">2007</year>
</GetHolidaysForYear>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
************************************************************************
*** Incoming SOAP ******************************************************
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <soap:Fault>
      <faultcode>soap:Server</faultcode>
      <faultstring>Server was unable to process request. --> Specified argument was out of the range of valid values.
Parameter name: Year, Month, and Day parameters describe an unrepresentable DateTime.</faultstring>
      <detail />
    </soap:Fault>
  </soap:Body>
</soap:Envelope>
************************************************************************
Traceback (most recent call last):
  File "/home/Richard/workspace/WebServices/W7/myHolliday_client.py", line 29, in ?
    result = proxy.GetHolidaysForYear(year=2007)
  File "/usr/lib/python2.3/site-packages/SOAPpy/Client.py", line 470, in __call__
    return self.__r_call(*args, **kw)
  File "/usr/lib/python2.3/site-packages/SOAPpy/Client.py", line 492, in __r_call
    self.__hd, self.__ma)
  File "/usr/lib/python2.3/site-packages/SOAPpy/Client.py", line 406, in __call
    raise p
SOAPpy.Types.faultType: <Fault soap:Server: Server was unable to process request. --> Specified argument was out of the range of valid values.
Parameter name: Year, Month, and Day parameters describe an unrepresentable DateTime.: >
Also der Service kennt den Typ des Parameters Year anscheinend nicht. Wenn ich mir mit dem Tool SoapUI einen Call erstellen lasse funktioniert die anfrage und ich bekomme brav antwort:

Code: Alles auswählen

<!-- Anfrage durch SoapUI -->
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:gbs="http://www.27seconds.com/Holidays/GBSCT/">
   <soapenv:Body>
      <gbs:GetHolidaysForYear>
         <gbs:year>2007</gbs:year>
      </gbs:GetHolidaysForYear>
   </soapenv:Body>
</soapenv:Envelope>

Code: Alles auswählen

<!-- Antwort durch den WS-->
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <soap:Body>
      <GetHolidaysForYearResponse xmlns="http://www.27seconds.com/Holidays/GBSCT/">
         <GetHolidaysForYearResult>
            <xs:schema id="NewDataSet" xmlns="" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:xs="http://www.w3.org/2001/XMLSchema">
               <xs:element msdata:IsDataSet="true" name="NewDataSet">
                  <xs:complexType>
                     <xs:choice maxOccurs="unbounded">
                        <xs:element name="Holidays">
                           <xs:complexType>
                              <xs:sequence>
                                 <xs:element minOccurs="0" name="Name" type="xs:string"/>
                                 <xs:element minOccurs="0" name="Key" type="xs:string"/>
                                 <xs:element minOccurs="0" name="Date" type="xs:dateTime"/>
                                 <xs:element minOccurs="0" name="Type" type="xs:string"/>
                              </xs:sequence>
                           </xs:complexType>
                        </xs:element>
                     </xs:choice>
                  </xs:complexType>
               </xs:element>
            </xs:schema>
            <diffgr:diffgram xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
               <NewDataSet xmlns="">
                  <Holidays diffgr:hasChanges="inserted" diffgr:id="Holidays1" msdata:rowOrder="0">
                     <Name>New Year's Day</Name>
                     <Key>NEW_YEARS</Key>
                     <Date>2007-01-01T00:00:00.0000000-07:00</Date>
                     <Type>Bank Holiday</Type>
                  </Holidays>
                  <Holidays diffgr:hasChanges="inserted" diffgr:id="Holidays2" msdata:rowOrder="1">
                     <Name>2nd January</Name>
                     <Key>SEC_JAN</Key>
                     <Date>2007-01-02T00:00:00.0000000-07:00</Date>
                     <Type>Bank Holiday</Type>
                  </Holidays>
                  <Holidays diffgr:hasChanges="inserted" diffgr:id="Holidays3" msdata:rowOrder="2">
                     <Name>Good Friday</Name>
                     <Key>GOOD_FRIDAY</Key>
                     <Date>2007-04-06T00:00:00.0000000-07:00</Date>
                     <Type>Bank Holiday</Type>
                  </Holidays>
                  <!--
.
.
. -->
                  <Holidays diffgr:hasChanges="inserted" diffgr:id="Holidays34" msdata:rowOrder="33">
                     <Name>Hogmanay</Name>
                     <Key>HOGMANAY</Key>
                     <Date>2007-12-31T00:00:00.0000000-07:00</Date>
                     <Type>Notable Date</Type>
                  </Holidays>
               </NewDataSet>
            </diffgr:diffgram>
         </GetHolidaysForYearResult>
      </GetHolidaysForYearResponse>
   </soap:Body>
</soap:Envelope>
Und jetzt die Frage: Wie bekomme ich SOAPpy dazu statt dem Tag <year xsi:type="xsd:int">2007</year> den Tag <gbs:year>2007</gbs:year> in die Message zu schreiben ? Also, wie kann ich den Typ des Parameters angeben?
Vielen Dank schon mal für eure Hilfe.
cu Richard
BlackJack

Erstmal ein bisschen was zum Programmierstil. Ich weiss das sind nur kleine Spielereien und Tests, aber man sollte sich schlechten Stil möglichst schnell abgewöhnen.

Auf Modulebene sollte sich möglichst wenig Code ausserhalb von Funktionsdefinitionen befinden. Das verringert die Gefahr, dass man in Funktionen Objekte benutzt, die nicht als Argumente in die Funktion hineinkommen. Wie zum Beispiel `result` in `exploreService()`.

Die Zeilenlänge sollte auf 80 Zeichen beschränkt bleiben. Bei Zeichenketten wie der URL der WSDL-Datei kann man einfach mehrere Zeichenketten hinter- bzw. untereinander schreiben die nur durch "whitespace"-Zeichen getrennt sind. Beim Übersetzen werden diese zu einer zusammenhängenden Zeichenkette übersetzt.

Namenskonvention in Python ist kleinbuchstaben_mit_unterstrichen für alle Namen ausser Klassen (MixedCase) und Konstanten (ALLES_GROSS). Und vor und nach binären Operatoren wird üblicherweise ein Leerzeichen gesetzt.

Die ``while``-Schleifen sind sehr umständlich. Man kann in Python mit ``for`` ohne einen Index direkt über die Elemente von "Container"-Objekten iterieren. Das ist ein Name weniger bzw. ein aussagekräftigerer Name für die Elemente und man kann auch nicht vergessen den Index hochzuzählen, wie das in `printResult()` passiert ist.

Der Quelltext könnte so aussehen:

Code: Alles auswählen

import SOAPpy


def explore_service(proxy):
    for method_name, callinfo in proxy.methods.iteritems():
        print '-' * 40
        print 'Method:', method_name
        print 'Parameter(s):'
        for parameter in callinfo.inparams:
            print '   ', parameter.name, ':', parameter.type



def print_result(result):
    for item in result:
        print item


def main():
    proxy = SOAPpy.WSDL.Proxy('http://www.holidaywebservice.com/Holidays/'
                              'GBNIR/GBNIRHolidayService.asmx?WSDL')
    proxy.soapproxy.config.dumpSOAPOut = 1
    proxy.soapproxy.config.dumpSOAPIn = 1
    
    explore_service(proxy)
    
#     result = proxy.GetHolidaysForYear(2007)
#     print_result(result)


if __name__ == '__main__':
    main()
Zum Problem: Da kann ich auch nicht weiterhelfen. Laut WSDL sieht es so aus, als wenn dort ein `int` übergeben werden muss, also sollte das auch nicht weiter stören:

Code: Alles auswählen

      <s:element name="GetHolidaysForYear">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="1" maxOccurs="1" name="year" type="s:int" />
          </s:sequence>
        </s:complexType>
SOAP ist im grossen und ganzen aber sowieso ziemlich schrecklich. Es funktioniert am besten wenn man nur mit der Sprache und den Werkzeugen arbeitet, mit denen der Service erstellt wurde. Das untergräbt irgendwie den ursprünglichen Sinn von SOAP, aber WSDL ist ein so flexibles, sprich komplexes Monster, dass man ohne Werkzeuge kaum durchsteigt. Und ob das automatische Abbilden von Typen der Sprachen auf SOAP-Typen und umgekehrt klappt, ist ab einer gewissen Komplexität Glückssache.
Benutzeravatar
Richard_M
User
Beiträge: 11
Registriert: Samstag 13. Januar 2007, 19:22
Wohnort: Nähe Stuttgart
Kontaktdaten:

Hallo zusammen, danke Black Jack,
mit dem Programmierstiel hast du natürlich völlig recht, Asche auf mein Haupt...
Ich bin bei dem Problem zu einer zumindest teilweise befriedigenden Lösung gekommen, mit den ZSI - Tools. Download unter: http://sourceforge.net/project/showfile ... e_id=30660
Doku unter: http://pywebsvcs.sourceforge.net/guide.html
Das Tool generiert module zum für die angegebene WSDL Datei durch: # wsdl2py --complexType -url=http://www.neurofuzz.com/modules/crypto ... P.php?wsdl
Hierdurch erhält man die module neuroSOAPwsdl_services.py und neuroSOAPwsdl_services_types.py.
neuroSOAPwsdl_services.py:

Code: Alles auswählen

################################################## 
# neuroSOAPwsdl_services.py 
# generated by ZSI.generate.wsdl2python
##################################################


from neuroSOAPwsdl_services_types import *
import urlparse, types
from ZSI.TCcompound import ComplexType, Struct
from ZSI import client
import ZSI
from ZSI.generate.pyclass import pyclass_type

# Locator
class neuroSOAPwsdlLocator:
    neuroSOAPwsdlPortType_address = "http://neurofuzz.com/modules/cryptoService/cryptoSOAP.php"
    def getneuroSOAPwsdlPortTypeAddress(self):
        return neuroSOAPwsdlLocator.neuroSOAPwsdlPortType_address
    def getneuroSOAPwsdlPortType(self, url=None, **kw):
        return neuroSOAPwsdlBindingSOAP(url or neuroSOAPwsdlLocator.neuroSOAPwsdlPortType_address, **kw)

# Methods
class neuroSOAPwsdlBindingSOAP:
    def __init__(self, url, **kw):
        kw.setdefault("readerclass", None)
        kw.setdefault("writerclass", None)
        # no resource properties
        self.binding = client.Binding(url=url, **kw)
        # no ws-addressing

    # op: encrypt
    def encrypt(self, request):
        if isinstance(request, encryptRequest) is False:
            raise TypeError, "%s incorrect request type" % (request.__class__)
        kw = {}
        # no input wsaction
        self.binding.Send(None, None, request, soapaction="urn:encryptwsdl#encrypt", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
        # no output wsaction
        typecode = Struct(pname=None, ofwhat=encryptResponse.typecode.ofwhat, pyclass=encryptResponse.typecode.pyclass)
        response = self.binding.Receive(typecode)
        return response

    # op: decrypt
    def decrypt(self, request):
        if isinstance(request, decryptRequest) is False:
            raise TypeError, "%s incorrect request type" % (request.__class__)
        kw = {}
        # no input wsaction
        self.binding.Send(None, None, request, soapaction="urn:decryptwsdl#decrypt", encodingStyle="http://schemas.xmlsoap.org/soap/encoding/", **kw)
        # no output wsaction
        typecode = Struct(pname=None, ofwhat=decryptResponse.typecode.ofwhat, pyclass=decryptResponse.typecode.pyclass)
        response = self.binding.Receive(typecode)
        return response

_encryptRequestTypecode = Struct(pname=("urn:encryptwsdl","encrypt"), ofwhat=[ZSI.TC.String(pname="string", aname="_string", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="key", aname="_key", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:encryptwsdl")
class encryptRequest:
    typecode = _encryptRequestTypecode
    __metaclass__ = pyclass_type
    def __init__(self):
        self._string = None
        self._key = None
        return
encryptRequest.typecode.pyclass = encryptRequest

_encryptResponseTypecode = Struct(pname=("urn:encryptwsdl","encryptResponse"), ofwhat=[ZSI.TC.String(pname="return", aname="_return", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:encryptwsdl")
class encryptResponse:
    typecode = _encryptResponseTypecode
    __metaclass__ = pyclass_type
    def __init__(self):
        self._return = None
        return
encryptResponse.typecode.pyclass = encryptResponse

_decryptRequestTypecode = Struct(pname=("urn:decryptwsdl","decrypt"), ofwhat=[ZSI.TC.String(pname="string", aname="_string", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True), ZSI.TC.String(pname="key", aname="_key", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:decryptwsdl")
class decryptRequest:
    typecode = _decryptRequestTypecode
    __metaclass__ = pyclass_type
    def __init__(self):
        self._string = None
        self._key = None
        return
decryptRequest.typecode.pyclass = decryptRequest

_decryptResponseTypecode = Struct(pname=("urn:decryptwsdl","decryptResponse"), ofwhat=[ZSI.TC.String(pname="return", aname="_return", typed=False, encoded=None, minOccurs=1, maxOccurs=1, nillable=True)], pyclass=None, encoded="urn:decryptwsdl")
class decryptResponse:
    typecode = _decryptResponseTypecode
    __metaclass__ = pyclass_type
    def __init__(self):
        self._return = None
        return
decryptResponse.typecode.pyclass = decryptResponse
Die Types Datei:

Code: Alles auswählen

################################################## 
# neuroSOAPwsdl_services_types.py 
# generated by ZSI.generate.wsdl2python
##################################################


import ZSI
import ZSI.TCcompound
from ZSI.schema import LocalElementDeclaration, ElementDeclaration, TypeDefinition, GTD, GED
from ZSI.generate.pyclass import pyclass_type

##############################
# targetNamespace
# urn:neuroSOAPwsdl
##############################

class ns0:
    targetNamespace = "urn:neuroSOAPwsdl"

# end class ns0 (tns: urn:neuroSOAPwsdl)
und meine Clientimplementierung...

Code: Alles auswählen

# import the generated class stubs
from neuroSOAPwsdl_services import *

# get a port proxy instance
loc = neuroSOAPwsdlLocator()
port = loc.getneuroSOAPwsdlPortType()

req = encryptRequest()
req._string = 'Hallo'
req._key = 'Welt'

# call the remote method
resp = port.encrypt(req)

print 'Verschlüsselt:', resp._return

req = decryptRequest()
req._string = resp._return
req._key = 'Welt'

resp = port.decrypt(req)

print 'Entschlüsselt:', resp._return
viel Spass und nochmal Danke, Richard
Antworten