- Implementing Web Services with Quixote
- ======================================
- This document will show you how to implement Web services using
- Quixote.
- An XML-RPC Service
- ------------------
- 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
- as arrays and structs. The xmlrpclib module, part of the Python 2.2
- standard library and available separately from
- http://www.pythonware.com/products/xmlrpc/, converts between Python's
- 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
- <boolean> xmlrpclib.Boolean
- <base64> xmlrpclib.Binary
- <dateTime> xmlrpclib.DateTime
- ============== =====================
- Making XML-RPC Calls
- --------------------
- Making an XML-RPC call using xmlrpclib is easy. An XML-RPC server
- lives at a particular URL, so the first step is to create an
- xmlrpclib.ServerProxy object pointing at that URL. ::
- >>> import xmlrpclib
- >>> s = xmlrpclib.ServerProxy(
- '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::
- <?xml version='1.0'?>
- <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,
- ``xmlrpc(request, func)``, that processes the body of an XML-RPC
- 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
- you supply and it raises an exception, the ``xmlrpc()`` function will
- 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. ::
- from quixote.directory import Directory
- from quixote.util import xmlrpc
- from quixote import get_request
- class RPCDirectory(Directory):
- _q_exports = ['rpc']
- def rpc (self):
- return xmlrpc(get_request(), rpc_process)
- 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())
- return xmlrpclib.DateTime(now)
- else:
- 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
- RuntimeError exception. Quixote's ``xmlrpc()`` will catch this
- 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())
- return xmlrpclib.DateTime(now)
- 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())
- return xmlrpclib.DateTime(now)
- else:
- 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.