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