191 lines
8.3 KiB
HTML
191 lines
8.3 KiB
HTML
<?xml version="1.0" encoding="us-ascii" ?>
|
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
|
|
<meta name="generator" content="Docutils 0.3.0: http://docutils.sourceforge.net/" />
|
|
<title>Implementing Web Services with Quixote</title>
|
|
<link rel="stylesheet" href="default.css" type="text/css" />
|
|
</head>
|
|
<body>
|
|
<div class="document" id="implementing-web-services-with-quixote">
|
|
<h1 class="title">Implementing Web Services with Quixote</h1>
|
|
<p>This document will show you how to implement Web services using
|
|
Quixote.</p>
|
|
<div class="section" id="an-xml-rpc-service">
|
|
<h1><a name="an-xml-rpc-service">An XML-RPC Service</a></h1>
|
|
<p>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
|
|
<a class="reference" href="http://www.pythonware.com/products/xmlrpc/">http://www.pythonware.com/products/xmlrpc/</a>, converts between Python's
|
|
standard data types and the XML-RPC data types.</p>
|
|
<table class="table" frame="border" rules="all">
|
|
<colgroup>
|
|
<col width="40%" />
|
|
<col width="60%" />
|
|
</colgroup>
|
|
<tbody valign="top">
|
|
<tr><td>XML-RPC Type</td>
|
|
<td>Python Type or Class</td>
|
|
</tr>
|
|
<tr><td><int></td>
|
|
<td>int</td>
|
|
</tr>
|
|
<tr><td><double></td>
|
|
<td>float</td>
|
|
</tr>
|
|
<tr><td><string></td>
|
|
<td>string</td>
|
|
</tr>
|
|
<tr><td><array></td>
|
|
<td>list</td>
|
|
</tr>
|
|
<tr><td><struct></td>
|
|
<td>dict</td>
|
|
</tr>
|
|
<tr><td><boolean></td>
|
|
<td>xmlrpclib.Boolean</td>
|
|
</tr>
|
|
<tr><td><base64></td>
|
|
<td>xmlrpclib.Binary</td>
|
|
</tr>
|
|
<tr><td><dateTime></td>
|
|
<td>xmlrpclib.DateTime</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="section" id="making-xml-rpc-calls">
|
|
<h1><a name="making-xml-rpc-calls">Making XML-RPC Calls</a></h1>
|
|
<p>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.</p>
|
|
<pre class="literal-block">
|
|
>>> import xmlrpclib
|
|
>>> s = xmlrpclib.ServerProxy(
|
|
'http://www.stuffeddog.com/speller/speller-rpc.cgi')
|
|
</pre>
|
|
<p>Now you can simply make a call to the spell-checking service offered
|
|
by this server:</p>
|
|
<pre class="literal-block">
|
|
>>> 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}]
|
|
>>>
|
|
</pre>
|
|
<p>This call results in the following XML being sent:</p>
|
|
<pre class="literal-block">
|
|
<?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>
|
|
</pre>
|
|
</div>
|
|
<div class="section" id="writing-a-quixote-service">
|
|
<h1><a name="writing-a-quixote-service">Writing a Quixote Service</a></h1>
|
|
<p>In the quixote.util module, Quixote provides a function,
|
|
<tt class="literal"><span class="pre">xmlrpc(request,</span> <span class="pre">func)</span></tt>, that processes the body of an XML-RPC
|
|
request. <tt class="literal"><span class="pre">request</span></tt> is the HTTPRequest object that Quixote passes to
|
|
every function it invokes. <tt class="literal"><span class="pre">func</span></tt> 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 <tt class="literal"><span class="pre">xmlrpc()</span></tt> function will
|
|
catch the exception and return a <tt class="literal"><span class="pre">Fault</span></tt> to the remote caller.</p>
|
|
<p>Here's an example of implementing a simple XML-RPC handler with a
|
|
single method, <tt class="literal"><span class="pre">get_time()</span></tt>, that simply returns the current
|
|
time. The first task is to expose a URL for accessing the service.</p>
|
|
<pre class="literal-block">
|
|
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):
|
|
...
|
|
</pre>
|
|
<p>When the above code is placed in the __init__.py file for the Python
|
|
package corresponding to your Quixote application, it exposes the URL
|
|
<tt class="literal"><span class="pre">http://<hostname>/rpc</span></tt> as the access point for the XML-RPC service.</p>
|
|
<p>Next, we need to fill in the contents of the <tt class="literal"><span class="pre">rpc_process()</span></tt>
|
|
function:</p>
|
|
<pre class="literal-block">
|
|
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
|
|
</pre>
|
|
<p><tt class="literal"><span class="pre">rpc_process()</span></tt> 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 <tt class="literal"><span class="pre">rpc_process()</span></tt> will
|
|
therefore usually be an <tt class="literal"><span class="pre">if</span></tt> statement that checks the name of the
|
|
method, and calls another function to do the actual work. In this case,
|
|
<tt class="literal"><span class="pre">get_time()</span></tt> is very simple so the two lines of code it requires are
|
|
simply included in the body of <tt class="literal"><span class="pre">rpc_process()</span></tt>.</p>
|
|
<p>If the method name doesn't belong to a supported method, execution
|
|
will fall through to the <tt class="literal"><span class="pre">else</span></tt> clause, which will raise a
|
|
RuntimeError exception. Quixote's <tt class="literal"><span class="pre">xmlrpc()</span></tt> will catch this
|
|
exception and report it to the caller as an XML-RPC fault, with the
|
|
error code set to 1.</p>
|
|
<p>As you add additional XML-RPC services, the <tt class="literal"><span class="pre">if</span></tt> statement in
|
|
<tt class="literal"><span class="pre">rpc_process()</span></tt> will grow more branches. You might be tempted to pass
|
|
the method name to <tt class="literal"><span class="pre">getattr()</span></tt> 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 <tt class="literal"><span class="pre">if...</span> <span class="pre">elif...</span> <span class="pre">elif...</span> <span class="pre">else</span></tt> 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.</p>
|
|
<p>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:</p>
|
|
<pre class="literal-block">
|
|
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
|
|
</pre>
|
|
<p>This approach works nicely when there are many methods and the
|
|
<tt class="literal"><span class="pre">if...elif...else</span></tt> statement would be unworkably long.</p>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|