ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


XML-RPC: It Works Both Ways

by Dave Warner
01/17/2001

In my previous XML-RPC article, I showed how to access the Meerkat XML-RPC server (written in PHP) from Python. This time around, we will go behind the scenes to view the structure of an XML-RPC method call and response. Then we will develop an XML-RPC server using Fredrick Lundh's xmlrpclib. Lastly, we'll touch on some real-world concerns about XML-RPC, SOAP, and look at the W3C's working group that is driving development of XML-based distributed application standardization.

A peek under the hood

So how does xmlrpclib work? Its approach to the construction of method calls and responses is quite simple. A method called dumps (dump string) turns native Python objects into XML. The process is called "marshalling". The reverse process, called "unmarshalling", is performed by loads (load string). Although transparent to the casual user, we can access these methods to peek under the hood. The dumps method takes three arguments: a tuple containing the parameters, the method name, and a method response. The last two are optional. Following good object-oriented design, xmlrpclib's dumps insists that the parameters are passed in the form of a tuple. This prevents modification of the parameters past this point.

Let's examine the structure of a method call used with Meerkat. At this point, no connection is needed. We are simply examining what will be sent, not actually communicating with a server.

Related Articles:

XML-RPC in Python

Web Client Programming in Python

Meerkat: The XML-RPC Interface

Here's the dictionary that I used in my last article:

>>>params = {
... 'category' : 7,
... 'time_period' : '1HOUR',
... 'descriptions' : 0,
... 'categories' : 0,
... 'channels' : 0,
... 'dates' : 0
    }

First, cast the parameters to a tuple:

>>>tuple_params = tuple([params])

Then, use dumps to marshall the tuple into an XML-RPC method call to meerkat.getItems:

>>>xmlrpccall = xmlrpclib.dumps(tuple_params, 
                                'meerkat.getItems')

Here's the result:

>>>print(xmlrpccall)
<?xml version=1.0?>
<methodCall>
<methodName>meerkat.getItems</methodName>
   <params>
   <param>
      <value><struct>
         <member>
            <name>categories</name>
            <value><int>0</int></value>
         </member>
         <member>
            <name>descriptions</name>
            <value><int>0</int></value>
         </member>
         <member>
            <name>channels</name>
            <value><int>0</int></value>
         </member>
         <member>
            <name>dates</name>
            <value><int>0</int></value>
         </member>
         <member>
            <name>time_period</name>
            <value><string>4HOUR</string></value>
         </member>
         <member>
            <name>category</name>
            <value><int>7</int></value>
         </member>
     </struct></value>
   </param>
   </params>
</methodCall>

(I've indented the result to show the structure of the method call.)

Performing the reverse is even easier. To load an incoming XML-RPC response into a native Python object, simply use the loads method (load string). Extracting the first element of the tuple returned by loads will give us our original dictionary:

>>>xmlrpclib.loads(xmlrpccall)[0]
({'categories': 0,
'category': 7,
'channels': 0,
'dates': 0,
'descriptions': 0,
'time_period': '1HOUR'},)

This process of dumping and then loading structures is useful to verify that your data is compatible with the present xmlrpclib.

Building an XML-RPC server

Now, let's turn to building an XML-RPC server to support a testing harness using dumps and loads. There are several good quality XML-RPC servers built in Python. Medusa comes with an implementation which Ng Pheng Siong has in turn included and enhanced in his excellent M2Crypto package for those who require a secure socket layer (SSL) transport mechanism. Zope, of course, comes with its own implementation of XML-RPC. For our purposes, we'll use Fredrick Lundh's example server as the base for our discussion. It is bundled with the client software we have been using, and is located in the file xmlrpcserver.py. This uses a method named call to hand off requests from clients to other methods or object instances.

First, we'll import the necessary packages:

import SocketServer
import xmlrpcserver
import xmlrpclib

Now, let's create a descendent of the RequestHandler class included in xmlrpcserver.py:

class TestRequestHandler(xmlrpcserver.RequestHandler):

Only the call method needs to be overridden for our purposes:

    def call(self, method, params):
	print "Dispatching: ", method, params 
	try: 
		server_method = getattr(self, method)
	except: 
		raise AttributeError, \
		    "Server does not have XML-RPC " \
		    "procedure %s" % method 
    return server_method(method, params)

The core functionality of the method lies in server_method = getattr(self, method), which obtains a reference to the requested method if it is in the class. If the method doesn't exist, an attribute exception is raised, causing an XML-RPC Fault message to be returned to the client.

We next need to establish a socket for the server to use:

if __name__ == '__main__':
	server = SocketServer.TCPServer(('', 8000), 
	    TestRequestHandler) server.serve_forever()

Adding a test harness

Now, let's add some methods to test the method call and response. Note that these methods are part of the TestRequestHandler class above.

First we'll get a dump to make sure the server is correctly marshalling the parameters in a method call:

    def dump_params(self, method, params):
	 return xmlrpclib.dumps(params)

If we want to see the entire method call, we can use:

    def dump_methodcall(self, method, params):
	return xmlrpclib.dumps(params[1:], params[0])

Now, let's add a dump_response() method that can marshall the response of a method call that doesn't already return marshalled data. First, we need a short method to test. The utility methods dump_method() and dump_params() are unsuitable since their responses are already marshalled. We need a test utility method that simply returns a Python object.

        def test(self, method, nr):
        return nr

Here's our dump_response() method:

    def dump_response(self, method, params):
        response = self.call(params[0], 
	                     tuple(params[1:]))
        return xmlrpclib.dumps(response)

Armed with the above, it is simple to test your own methods and add them to the server. The only requirement of the above structure is that your entry point be a method of the TestRequestHandler class. The completed file with the test harness is located here.

After starting the server up in another process, we can connect and test the results:

SERVER:

python xmlrpctestserver.py

CLIENT:

>>>import xmlrpclib
>>>testsvr = xmlrpclib.Server("http://localhost:8000")
>>>params = {
		"Name" : "Dave Warner",
		"Occupation" : "Programmer/Analyst"
		}
>>>testsvr.dump_params(params)
<params>
   <param>
   <value><struct>
      <member>
         <name>Occupation</name>
         <value><string>Programmer/Analyst</string></value>
      </member>
      <member>
         <name>Name</name>
         <value>
	    <string>Dave Warner</string>&
	 lt;/value>
      </member>
   </struct></value>
   </param>
</params>
>>> testsvr.dump_response('test', 32)
<params>
   <param>
      <value>
          <int>32</int>
      </value>
   </param>
</params>

The future of XML-RPC

For ease of use, xmlrpclib wins hands down for communicating with Meerkat. But does XML-RPC have a future? Yes! Athough the lure of wide-spread corporate backing is pushing many toward the Simple Object Access Protocol (SOAP), XML-RPC is in use today in some surprising places. Red Hat uses xmlrpclib.py in its new subscription service, and also internally. Their implementation of xmlrpclib ships with Red Hat 7 and includes enhancements by Cristian Gafton that include https support. Additionally, Userland's Frontier is fully XML-RPC aware, as is the Apache Java Project's Turbine framework and, by extension, Jetspeed.

So, where does that leave SOAP? Recently, the W3C established a working group on XML protocols. They have published a draft requirements document. Although they are using SOAP as their starting point, it is unlikely that SOAP will remain unchanged by this standardization process. I don't expect SOAP to go away any time soon, but I do expect it to be in flux for the next year or so.

Hopefully, the example above has shown that XML-RPC is not just a step on the way to SOAP and beyond, but a useful, simple tool in its own right. When XML-RPC was first introduced, Jon Udell asked the question: "Does distributed computing have to be any harder than this?" Much of the time, no.

Dave Warner is a senior programmer and DBA at Federal Data Corporation. He specializes in accessing relational databases with languages that begin with the letter P: Python, Perl, and PowerBuilder.


Discuss this article in the O'Reilly Network Python Forum.

Return to the Python DevCenter.

Copyright © 2009 O'Reilly Media, Inc.