Credit: Graham Dumpleton
You need to expose a service on the Web in a way that makes the service accessible to both XML-RPC and SOAP clients.
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)
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).
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).
Get Python Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.