XML + SOAP + Webservices
Holger Joukl
Holger.Joukl at LBBW.de
Thu Jun 9 11:41:09 EDT 2005
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.
More information about the Python-list
mailing list