xmlrpclib.ServerProxy object pointing at that URL.


Implementing Web Services with Quixote

This document will show you how to implement Web services using Quixote.

XML-RPC is the simplest protocol commonly used to expose a Web service. In XML-RPC, there are a few basic data types such as integers, floats, strings, and dates, and a few aggregate types such standard library and available separately from standard data types and the XML-RPC data types.

XML-RPC Type Python Type or Class
<int> int
<double> float
<string> string
<array> list
<struct> dict
lives at a particular URL, so the first step is to create an 'http://www.stuffeddog.com/speller/speller-rpc.cgi')

Now you can simply make a call to the spell-checking service offered by this server:

>>> s.speller.spellCheck('my speling isnt gud', {})
[{'word': 'speling', 'suggestions': ['apeling', 'spelding',
  'spelling', 'sperling', 'spewing', 'spiling'], 'location': 4},
{'word': 'isnt', 'suggestions': [``isn't'', 'ist'], 'location': 12}]

This call results in the following XML being sent:

<methodCall> <methodName>speller.spellCheck</methodName> <params> <param> <value><string>my speling isnt gud</string></value> </param> <param> <value><struct></struct></value> </param> </params> </methodCall>

Writing a Quixote Service

In the quixote.util module, Quixote provides a function, request. request is the HTTPRequest object that Quixote passes to every function it invokes. func is a user-supplied function that receives the name of the XML-RPC method being called and a tuple containing the method's parameters. If there's a bug in the function catch the exception and return a Fault to the remote caller.

Here's an example of implementing a simple XML-RPC handler with a single method, get_time(), that simply returns the current time. The first task is to expose a URL for accessing the service.

_q_exports = ['rpc'] def rpc (request): def rpc_process (meth, params): ...

When the above code is placed in the __init__.py file for the Python package corresponding to your Quixote application, it exposes the URL http://<hostname>/rpc as the access point for the XML-RPC service.

Next, we need to fill in the contents of the rpc_process() function:

import time

def rpc_process (meth, params):
    if meth == 'get_time':
        # params is ignored
        now = time.gmtime(time.time())
        raise RuntimeError, "Unknown XML-RPC method: %r" % meth

rpc_process() receives the method name and the parameters, and its job is to run the right code for the method, returning a result that will be marshalled into XML-RPC. The body of rpc_process() will therefore usually be an if statement that checks the name of the method, and calls another function to do the actual work. In this case, get_time() is very simple so the two lines of code it requires are simply included in the body of rpc_process().

If the method name doesn't belong to a supported method, execution will fall through to the else clause, which will raise a exception and report it to the caller as an XML-RPC fault, with the error code set to 1.

As you add additional XML-RPC services, the if` statement in ``rpc_process() will grow more branches. You might be tempted to pass the method name to getattr() to select a method from a module or class. That would work, too, and avoids having a continually growing set of branches, but you should be careful with this and be sure that there are no private methods that a remote caller could access. I generally prefer to have the if... elif... elif... else blocks, for three reasons: 1) adding another branch isn't much work, 2) it's explicit about the supported method names, and 3) there won't be any security holes in doing so.

An alternative approach is to have a dictionary mapping method names to the corresponding functions and restrict the legal method names to the keys of this dictionary:

def echo (*params):
    # Just returns the parameters it's passed
    return params

def get_time ():
    now = time.gmtime(time.time())

methods = {'echo' : echo, 
           'get_time' : get_time}

def rpc_process (meth, params):
    func = methods.get[meth]
    if methods.has_key(meth):
        # params is ignored
        now = time.gmtime(time.time())
        raise RuntimeError, "Unknown XML-RPC method: %r" % meth

This approach works nicely when there are many methods and the if...elif...else statement would be unworkably long.

$Id: web-services.txt,v 1.3 2002/10/02 17:08:47 gward Exp $