This may be of some help for you. It´s unfinished business, though. So far, the ZSI stuff has worked quite well for me, but as mentioned I am also a web services beginner. I am writing this in lyx so I might be able to post in other formats. For the time being, this is the first part in pure text. Cheers, Holger
>>> Interoperable WSDL/SOAP web services introduction: Python ZSI , Excel XP & gSOAP C/C++ Holger Joukl LBBW Financial Markets Technologies Abstract Despite the hype & buzzword-storm, building web services servers and clients still is no piece of cake. This is partly due to the relative newness of technology. For the most part, though, this results from the actual complexness of the protocols/specs, the toolkit magic behind which this complexness is hidden, and the documentation gaps that exist for the toolkits. This document is intended to be an example-loaded simple tutorial, to ease the use for web services newcomers (like I am one). It features the Python ZSI module that is used to build the server side machinery and clients that access the exposed services from Python, MS Excel XP, and C/C++ using gSOAP. Copyright © 2005 Holger Joukl. All rights reserved. Redistribution and use in source (LyX, LaTeX) and 'compiled' forms (SGML, HTML, PDF, PostScript, RTF and so forth) with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code (LyX, LaTeX) must retain the above copyright notice, this list of conditions and the following disclaimer as the first lines of this file unmodified. 2. Redistributions in compiled form (transformed to other DTDs, converted to PDF, PostScript, RTF and other formats) must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS DOCUMENTATION IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD DOCUMENTATION PROJECT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 1 Introduction We assume the reader is familiar with Python, C/C++ and MS Excel/VisualBasic. While some basic concepts regarding WSDL, SOAP, HTTP servers etc. might be touched this document is NOT a tutorial on these. If you want to know more there´s plenty of stuff on the web. Please note that currently, throughout most examples, certain host names and ports are used - substitute them with the setup for your site.. Versions used: * Python 2.3 * PyXML 0.8.3 * ZSI 1.6.1 * ... To Describe: WS-I, rpc/literal 2 The SquareService This first example will implement a service that exposes a function which takes a double argument and returns the square of it ( x{}^{\textrm{2}} ) as a double. I.e. this examples uses simple scalar datatypes, one single argument and one single return value. 2.1 The SquareService WSDL This is the WSDL file that determines the contract for the SquareService, called SquareService.wsdl: <?xml version="1.0"?> <definitions name="SquareService" targetNamespace="http://dev-b.handel-dev.local:8080/SquareService" xmlns:tns="http://dev-b.handel-dev.local:8080/SquareService" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns="http://schemas.xmlsoap.org/wsdl/"> <message name="getSquareRequest"> <part name="x" type="xsd:double"/> </message> <message name="getSquareResponse"> <part name="return" type="xsd:double"/> </message> <portType name="SquarePortType"> <operation name="getSquare"> <documentation> the square method </documentation> <input message="tns:getSquareRequest"/> <output message="tns:getSquareResponse"/> </operation> </portType> <binding name="SquareBinding" type="tns:SquarePortType"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> <operation name="getSquare"> <soap:operation soapAction=""/> <input> <soap:body use="literal" namespace="http://dev-b.handel-dev.local:8080/SquareService"/> </input> <output> <soap:body use="literal" namespace="http://dev-b.handel-dev.local:8080/SquareService"/> </output> </operation> </binding> <service name="SquareService"> <documentation>Returns x^2 (x**2, square(x)) for a given float x</documentation> <port name="SquarePort" binding="tns:SquareBinding"> <soap:address location="http://dev-b.handel-dev.local:8080/SquareService"/> </port> </service> </definitions> Comments: * The style "rpc" and the use "literal" are used, to be WS-I-compliant. WS-I only supports rpc/literal and document/literal. 2.2 A Python ZSI server for the SquareService The Python ZSI package [key-1] is one of two pywebsvcs packages implementing web services for Python, namely SOAP messaging and WSDL capabilities. It is powerful and very easy to get started with, but lacks some documentation enhancements when it comes to WSDL-driven service generation. While the tools to do that are already there, documentation is sparse and examples are hard to find. All examples here are based on ZSI 1.6.1. 2.2.1 Generating stubs from WSDL ZSI comes with two python scripts to generate code from a WSDL file: * wsdl2py is used to generate python bindings for the service. * wsdl2dispatch generates a server frame for service dispatch where the actual worker functions will be hooked into. If you have installed ZSI on top of your python installation you can invoke the scripts like this (change your installation base path according to your setup):The installation base path for all examples here is /apps/pydev/gcc/3.4.3/. 1. wsdl2py: /apps/pydev/gcc/3.4.3/bin/wsdl2py -f SquareService.wsdl This will generate the file SquareService_services.py. 2. wsdl2dispatch: /apps/pydev/gcc/3.4.3/bin/wsdl2dispatch -f SquareService.wsdl This will generate the file SquareService_services_server.py. What do we have now? We have bindings to work with the services in python and a skeleton for dispatching to the actual worker methods. What we still need is * the main program that runs a (HTTP-) server with a request handler for the services and * the hooks to invoke the worker methods. Luckily, ZSI includes the ZSI.ServiceContainer module which implements the server for us. 2.2.2 Writing the web services server This is our main program mySquareServer.py: #! /apps/pydev/gcc/3.4.3/bin/python2.3 from ZSI.ServiceContainer import ServiceContainer, SOAPRequestHandler from SquareService_services_server import SquareService import os class MySOAPRequestHandler(SOAPRequestHandler): """Add a do_GET method to return the WSDL on HTTP GET requests. Please note that the path to the wsdl file is derived from what the HTTP invocation delivers (which is put into the self.path attribute), so you might want to change this addressing scheme. """ def do_GET(self): """Return the WSDL file. We expect to get the location from the invocation URL ("path"). """ wsdlfile = os.path.join('.', self.path.replace('/', "", 1) + ".wsdl") print ">>>>> using wsdlfile", wsdlfile wsdl = open(wsdlfile).read() self.send_xml(wsdl) # Copied from ZSI.ServiceContainer, extended to instantiate with a custom # request handler def AsServer(port=80, services=(), RequestHandlerClass=SOAPRequestHandler): '''port -- services -- list of service instances ''' address = ('', port) sc = ServiceContainer(address, RequestHandlerClass=RequestHandlerClass) for service in services: path = service.getPost() sc.setNode(service, path) sc.serve_forever() AsServer(port=8080, services=[SquareService()], RequestHandlerClass=MySOAPRequestHandler) We wouldn´t have needed to write the custom request handler MySOAPRequestHandler if not for the do_GET method. But both Python ZSI clients using the ServiceProxy class and MS VisualBasic SOAP clients expect to receive the WSDL when issueing HTTP GET, which is actually common behaviour to get the service description (apart from UDDI). Similarly, the AsServer(...) function had to be extended to make use of our custom request handler. 2.2.3 Hooking-in the service implementation The only thing left now is to hook in the implementation of the service. We need to * dispatch to the correct service method, * feed it the arguments received via a SOAP request and * set the return values for the SOAP response. This is the implementation for the SquareService getSquare method (or operation, in WSDL terms): from SquareService_services import * from ZSI.ServiceContainer import ServiceSOAPBinding class SquareService(ServiceSOAPBinding): # This dictionary is used to dispatch to the appropriate method. # Not that the dict key(s) are identical to the soapAction attributes of the # <soap:operation ...> field(s) in the WSDL: # ... # <soap:operation # soapAction="http://dev-b.handel-dev.local:8080/SquareService/getSquare"/> # ... # The value(s) for the key(s) are the generated soap_<...> method names. soapAction = { 'http://dev-b.handel-dev.local:8080/SquareService/getSquare': 'soap_getSquare', } def __init__(self, post='/SquareService', **kw): ServiceSOAPBinding.__init__(self, post) def soap_getSquare(self, ps): # input vals in request object # MANUALLY CORRECTED: # args = ps.Parse( getSquareRequestWrapper() ) # Use the class instead of an instance of the class. # Note: The erroneous code generation happens for rpc/literal, but not # for rpc/encoded, where using an instance works (?). args = ps.Parse( getSquareRequestWrapper ) # assign return values to response object response = getSquareResponseWrapper() # >>> ADDED MANUALLY # Here we hook in the actual worker method response._return = self.getSquare(args._x) # <<< return response # the (handwritten) worker code def getSquare(self, x): """Return square(x). """ return x**2 Note that ZSI does almost all the work for us, again. The only additions we had to do are: * Implementing the getSquare(...) worker method. We could also have invoked a function, used a lambda, put the worker code into soap_getSquare, etc. * Hooking getSquare in. This is done in the ... # >>> ADDED MANUALLY # Here we hook in the actual worker method response._return = self.getSquare(args._x) # <<< ... bits where * the x input argument is taken from the incoming SOAP request and handed to the getSquare method * the return field of the SOAP response message to be sent out is set with the getSquare result As you can see in the WSDL above the "getSquareRequest" message has a "part" with the name "x"; ZSI exposes this as attribute "_x" of the incoming parsed SOAP request message "args" instance. The same applies to the "return" part of the response message. ZSI exposes this to python as attribute "_return" of the getSquareResponseWrapper instance. * Correcting the line # args = ps.Parse( getSquareRequestWrapper() ) to args = ps.Parse( getSquareRequestWrapper ) This seems to be a bug in the code generation.When experimenting with rpc/encoded-style first, this line works "as is". 2.3 A Python ZSI client for the SquareService We implement a client that calls getSquare from the SquareService in myServiceProxyClient.py as follows: #!/apps/pydev/gcc/3.4.3/bin/python2.3 import sys import getopt from ZSI import ServiceProxy import ZSI.wstools.WSDLTools #------------------------------------------------------------------------------ # default configuration #------------------------------------------------------------------------------ port = 8080 host = 'dev-b' #------------------------------------------------------------------------------ # command line parsing #------------------------------------------------------------------------------ def usage(rcode=1): print "usage: myServiceProxyClient.py [--host=<hostname> --port=,-c<port> --help, -h]" sys.exit(rcode) try: optlist, args = getopt.getopt(sys.argv[1:], "hp:", ['help', 'port=']) except getopt.GetoptError: usage() for opt, arg in optlist: print opt, arg if opt in ["-h", "--help"]: usage(0) elif opt in ["--host"]: host = arg continue elif opt in ["-p", "--port"]: port = int(arg) continue url = 'http://' + host + ':' + str(port) + '/SquareService' # Hmm, if we want to send to the correct service location we # must set use_wsdl. service = ServiceProxy(url, use_wsdl=True, tracefile=sys.stdout, ns='http://dev-b.handel-dev.local:8080/SquareService') print 'service is', service print service.__dict__ print '\nAccessing service getSquare...' while 1: # Must use keyword arguments if use_wsdl was set x = float(raw_input("Enter number: ")) result = service.getSquare(x=x) print 'result:', result References Salz, Rich; Blunck Christopher: ZSI: The Zolera Soap Infrastructure. <http://pywebsvcs.sourceforge.net/zsi.html>, Release 1.6.1, December 08, 2004. Der Inhalt dieser E-Mail ist vertraulich. Falls Sie nicht der angegebene Empfänger sind oder falls diese E-Mail irrtümlich an Sie adressiert wurde, verständigen Sie bitte den Absender sofort und löschen Sie die E-Mail sodann. Das unerlaubte Kopieren sowie die unbefugte Übermittlung sind nicht gestattet. Die Sicherheit von Übermittlungen per E-Mail kann nicht garantiert werden. Falls Sie eine Bestätigung wünschen, fordern Sie bitte den Inhalt der E-Mail als Hardcopy an. The contents of this e-mail are confidential. If you are not the named addressee or if this transmission has been addressed to you in error, please notify the sender immediately and then delete this e-mail. Any unauthorized copying and transmission is forbidden. E-Mail transmission cannot be guaranteed to be secure. If verification is required, please request a hard copy version. -- http://mail.python.org/mailman/listinfo/python-list