I l@ve RuBoard Previous Section Next Section

13.5 Writing a Web Service That Supports Both XML-RPC and SOAP

Credit: Graham Dumpleton

13.5.1 Problem

You need to expose a service on the Web in a way that makes the service accessible to both XML-RPC and SOAP clients.

13.5.2 Solution

The OSE package offers a lot of extra flexibility for Python distributed processing, both server-side and client-side. Here is how we can code the actual web service:

# the actual web service, dbwebser.py
# needs the OSE package from http://ose.sourceforge.net

import netsvc
import netsvc.xmlrpc
import netsvc.soap
import signal
import dbm

class Database(netsvc.Service):

  def _ _init_ _(self, name):
      netsvc.Service._ _init_ _(self, name)
      self._db = dbm.open(name,'c')
      self.exportMethod(self.get)
      self.exportMethod(self.put)
      self.exportMethod(self.keys)
      self.joinGroup("web-services")

  def get(self, key):
      return self._db[key]

  def put(self, key, value):
      self._db[key] = value

  def keys(self):
      return self._db.keys(  )

dispatcher = netsvc.Dispatcher(  )
dispatcher.monitor(signal.SIGINT)
httpd = netsvc.HttpDaemon(8000)

database = Database("test")

rpcgw1 = netsvc.xmlrpc.RpcGateway("web-services")
httpd.attach("/xmlrpc/database", rpcgw1)

rpcgw2 = netsvc.soap.RpcGateway("web-services")
httpd.attach("/soap/database", rpcgw2)

httpd.start(  )
dispatcher.run(  )

Here's a client that accesses the service via XML-RPC:

# dbclient.py
# an XML-RPC client using the PythonWare xmlrpclib module (also
# included in the standard library with Python 2.2 and later)

import xmlrpclib

url = "http://localhost:8000/xmlrpc/database/test"
service = xmlrpclib.Server(url)

for i in range(10):
    service.put('X'+str(i), str(i*i))

for key in service.keys(  ):
    print key, service.get(key)

And here's a SOAP client that uses the pywebsvcs SOAP module:

import SOAP

url = "http://localhost:8000/soap/database/test"
service = SOAP.SOAPProxy(url)

for i in range(10):
    service.put('S'+str(i), str(i*i))

for key in service.keys(  ):
    print key, service.get(key)

13.5.3 Discussion

This recipe gives yet another example of an XML-RPC-capable web service. But this recipe is different in that the service can be accessed at the same time using the SOAP protocol. Confusion is avoided by having clients for each protocol use different URLs to access the service.

The ability to support both XML-RPC and SOAP at the same time avoids the question of which to use. Only a single implementation of the service needs to be written. If one protocol wins out over the other, you haven't wasted any time; you simply don't deploy the gateway for the protocol you don't want to support anymore. Deploying both also gives users a wider choice of client implementations.

Issues that arise in going down this road are that, since XML-RPC supports only positional parameters and not named parameters, you are reduced to using only positional parameters through the SOAP interface. There is also the problem that XML-RPC doesn't support the Python None type, nor various other scalar data types that can be used with SOAP (e.g., extended date and time values). XML-RPC restricts you to using strings as key values in dictionaries that you wish to pass around using the protocol. What's worse is that SOAP further constrains what those key values can be, and SOAP cannot handle an empty dictionary.

Thus, although it may be good to support both protocols, you are forced to use a set of data types and values that will work with both, which is a typical least-common-denominator syndrome similar to other cross-platform development efforts. In this case, the issue can be further complicated since some SOAP implementations may not preserve type information through to the server side, whereas in XML-RPC this is not a problem. Therefore, any server-side code may have to deal with values of specific types arriving in different forms. You need to run tests against a wide variety of clients to ensure that you've covered this ground.

The netsvc module used by this example comes with OSE, which can be found at http://ose.sourceforge.net. The recipe's server script instantiates a Dispatcher, an HttpDaemon serving on port 8000, and two RpcGateway instances, one from the soap and one from the xmlrpc module of OSE's netsvc package. Both gateways expose the services from a group named web-services, and we instantiate a single instance of our Database class, a subclass of netsvc's Service class, which joins that group. Thus, the Database instance implements all services that this server offers. Specifically, it does so by calling the exportMethod method (which it gets from its base class) on each of its own bound methods it wants to expose as part of its initialization. Both SOAP and XML-RPC servers expose the same Database instance via different URLs, and thus, both SOAP and XML-RPC clients end up accessing (and thus sharing) the same data structure.

Note that the OSE package provides a framework for building distributed applications, of which this recipe represents only a small portion. The OSE package comes with its own XML-RPC protocol implementation, but for SOAP, it currently relies upon the SOAP module from the pywebsvcs package, which can be found at http://sourceforge.net/projects/pywebsvcs, along with an alternate set of modules worth exploring called the Zolera SOAP Infrastructure (ZSI).

13.5.4 See Also

Recipe 13.8 and Recipe 13.9 for different uses of OSE; the OSE package (http://ose.sourceforge.net); the SOAP module from the pywebsvcs package (http://sourceforge.net/projects/pywebsvcs).

    I l@ve RuBoard Previous Section Next Section