Binary Data to Go: Using XML-RPC to Serve Up
Charts on the Fly
by Joe Johnston
07/09/2001
Warning: Acronyms Ahead
XML-RPC is a protocol that allows programs of different languages on
different machines to easily talk to each other. By sending a well-defined
XML document over unadorned HTTP, a client program can make a remote
procedure call to a server. The server processes the request and wraps its
response in another well-defined XML document that is sent back to the
client over that same HTTP connection.
Because procedure requests and
responses are all in XML, each end of the RPC connection need not be
written in the same language or even for the same platforms. Clients code
to an agreed-upon API that a Web service listener implements. Either side
of the API fence can change without affecting the other side. In this way,
Web services in general, and XML-RPC in particular, can help break down
the Berlin Wall of incompatible OS platforms and make language-agnostic
software network components.
Although less famous than its younger sibling SOAP, XML-RPC is a simple
and
easy tool that can help you integrate even the most uncommunicative of
systems. Where SOAP is a generalized, object-oriented, messaging protocol
that is designed to carry arbitrary XML payloads across any network
protocol, XML-RPC is a simple procedural protocol designed only to make
remote function calls. Lest you get the impression that XML-RPC is inferior
to SOAP, there are a good number of everyday problems that XML-RPC can
solve adroitly. The upcoming O'Reilly book, Programming Web Services
with XML-RPC, provides several examples of XML-RPC's no-nonsense
approach to solving application-integration and data-sharing challenges.
For instance, XML-RPC can transport binary data as base64-encoded
messages, much the way email clients send attachments. Because this feature
is somewhat rarely used in XML-RPC applications, it's worth taking a look
at a Web service that creates simple charts for client programs using this
technique.
And Then There Were Three
As of this article's publication date, three Perl implementations of
XML-RPC exist, all available at your local CPAN Web site. The oldest and
best known of these is Frontier::RPC2, written by Ken MacLeod.
Unfortunately, the current stable release, 0.06, has a bug that hampers
transmission of base64 objects. The newest module, RPC::XML was
written by Randy Ray, and it provides the really useful feature of
introspection, which is a method that lets remote clients ask the server
for the remote procedures it provides. It also provides type checking
to ensure that clients are providing the kinds and numbers of arguments
that the implementing Perl procedure expects. The module used in this
article is SOAP::Lite. Recently, Paul Kulchenko added XML-RPC
support to his existing SOAP package. The result is a very solid and
surprisingly flexible XML-RPC library.
One of the advantages of using the XMLRPC::Lite classes that
come
bundled with SOAP::Lite is that you can create XML-RPC servers
that look like CGI scripts. This means that the
XMLRPC::Transport::HTTP::CGI class lets your system's Web server
worry about mundane HTTP issues (like authentication and logging) and lets
you concentrate on implementing your Web service API.
The Chart Server
I'm not into graphics, but Perl's Chart::Bars module makes
short
work of creating simple bar charts. But what about the other programmers
in your department who aren't using Perl? Traditionally, they'd have
to find a graphics library written in their language just to keep up
with you and Perl. With XML-RPC, it's simple to create an interface that
lets your local Python programmer in on your Perl fun.
Example 1 below shows how this chart server is implemented. Line 6
brings
in the XMLRPC::Transport::HTTP module, which contains the class
XMLRPC::Transport::HTTP::CGI. This class describes how this server is
going to talk to clients.
In order to get parameters from and pass function values to clients, you
will need to add the class XMLRPC::Server::Parameters to the
current package's @ISA array.
Those unfamiliar with Perl's inheritance model should have a look at
Tom Christiansen's perltoot, which is bundled with Perl.
Populating the @ISA array tells Perl that if a method is called,
the interpreter should look first in the current package for a subroutine
of the same name. Failing that, Perl should look in the packages listed in
the @ISA array for the desired method. In this case, the server
tries to invoke the method methodName to see how to handle RPC
requests. The code shown starting on line 13 is what's in the
XMLRPC::Lite man page, and it simply strips off anything before the
left-most period and uses what's left as a method call.
Example 1. Perl XML-RPC chart server
1 #!/usr/bin/perl -w --
2 # Serve up charts hot and fresh
3
4 use strict;
5 use Chart::Bars;
6 use XMLRPC::Transport::HTTP;
7
8 @::ISA = qw(XMLRPC::Server::Parameters);
9
10 my $resp = XMLRPC::Transport::HTTP::CGI->dispatch_to('methodName');
11 $resp->handle;
12
13 sub methodName {
14 my $self = shift;
15 my $method = $_[-1]->method;
16
17 $method =~ s/\w+\.//; # ignore method prefixes
18 return $self->$method(@_);
19 }
20
21 # Implement the Chart Server API
22 sub make_chart {
23 my ($xmlrpc_obj, $x_values, $y_values) = @_;
24
25 unless( ref $x_values && ref $y_values ){
26 die "ERROR: Please send two array references";
27 }
28
29 my $chart;
30 unless( $chart = Chart::Bars->new() ){
31 die "ERROR: Can't make Chart::Bars object";
32 }
33
34 my $png = get_png($chart, [$x_values, $y_values] );
35
36 return XMLRPC::Data->type(base64 => $png);
37 }
38
39 #--helper function
40 # Given a populate Chart object,
41 # simply return the png as a scalar
42 # This is a gut of Chart::Base::png
43 sub get_png {
44 my ($chart_obj, $data) = @_;
45
46 # allocate the background color
47 $chart_obj->_set_colors();
48
49 # make sure the object has its copy of the data
50 $chart_obj->_copy_data($data);
51
52 # do a sanity check on the data, and collect some basic facts
53 # about the data
54 $chart_obj->_check_data;
55
56 # pass off the real work to the appropriate subs
57 $chart_obj->_draw();
58
59 return $chart_obj->{'gd_obj'}->png();
60 }
This server only defines one remote procedure, make_chart. It
expects to be passed an array reference of values for the x values
and another array reference for the y values. After a bit of error
checking, the graphing function, get_png is called. The Chart
module does not provide ready access to the string containing the chart
graphic, which here is in Portable Network Graphics (PNG) format.
Fortunately, it was easy enough to cut and paste parts needed from the
Chart.pm code. This explains why, on line 59 in Example 1, the
gd_obj attribute is directly accessed instead of going through a
method call, as all hygienic object-oriented code should do.
The make_chart function simply passes the PNG string that
get_png generates to the XMLRPC::Data method
type to encode into a base64 object. When XML-RPC attempts to
encode the client's response, it sees that it has data marked as base64 and
encodes the data appropriately for XML transport. The XML-RPC client
is expected to decode the base64 string.
The Python Client
The Python client is straightforward. Recall that the XML-RPC server is
a
CGI program. Therefore, the URL will depend on where your Web server wants
to execute CGI programs from. I've configured my local Apache server to execute
CGI scripts from user directories, so the URL for my chart server is
http://127.0.0.1/~jjohn/chart_server.pl.
Python's XML-RPC implementation is from Fredrik Lundh and Secret Labs.
You can fetch it from the PythonWare Web site. In line 8 of Example 2, a new XML-RPC
client object is created, which is instantiated through a method
deceptively called Server. It expects to receive the server's URL
as input. In order to invoke the remote procedure call,
simply call it as if it were a method of the XML-RPC object. The call
returns another object, which contains the response from the server. The
twist comes when the script needs to write out the data. Simple objects
like strings and integers can be used immediately, but base64 data requires
the data method to return the original binary data.
Example 2. Python Chart Client
1 #!/usr/bin/python
2 # Have a chart made for us
3
4 import sys, xmlrpclib
5 URL = "http://127.0.0.1/~jjohn/chart_server.pl"
6
7 # Create a new connection
8 conn = xmlrpclib.Server(URL)
9
10 # Call the RPC
11 png_string = conn.make_chart([1,3,4], [11,3,5])
12
13 # Write out the file
14 png = open("chart.png", "w")
15 png.write(png_string.data)
16 png.close();
17
18 print("done\n")
The results of this call are shown in Figure 1. (Yes, the graphic
here is a GIF. Not all browsers support PNG.) While this example is
less than earth-shattering, it provides a provocative indication of
where Web services can go.
Figure 1. The Returned Chart
Whither Web Services
Web services are important because they define a standard way for
programs
of different platforms to not only exchange data, but to use each other's
functionality. The software industry is still trying to figure out how to
best leverage this truly potent idea.
In No Silver Bullet, Fred Brooks posits that creating
software is fundamentally hard and that he was unlikely to see any
significant breakthrough in the abatement of that primary complexity. While
Web services alone isn't Brooks' silver bullet, the spirit of freedom and
cooperation that they engender could go a long way toward creating a world
in which all software on all platforms can play together
nicely.
Joe Johnston is a software engineer at O'Reilly & Associates.
A graduate of the University of Massachusetts in Boston with a B.A. in
computer science, he is a teacher, a Web designer, and an author of
articles for Perl Journal and for the Web. Joe coauthored
O'Reilly's Programming
Web Services with XML_RPC. He can be emailed at
jjohn@cs.umb.edu.