This site is now 100% read-only, and retired.

Cross-platform cross-environment RPC server creation

Posted by Steve on Thu 20 Jul 2006 at 09:03

Using XML-RPC it is possible to write software that can be accessed remotely by multiple means, from Ruby and Python to Perl and Ajax. Using a couple of simple libraries it is possible to setup a simple server in only a few minutes, with no need to worry about argument parsing, anything complex.

Here we'll give a quick introduction to writing a simple XML-RPC server in Perl and show how clients written in different languages and potentially running on different hosts can communicate with it.

Whilst we don't really need to know the details of the communication each request and response will be an XML string. For example a call to the function "hello" which we will implement will look like this:

 <?xml version="1.0" encoding="us-ascii"?>
  <methodCall>
   <methodName>hello</methodName>
   <params></params>
  </methodCall>

I'm not going to dwell on the encoding, or the decoding, of the request and response. If you're curious you can install tcpdump, tcpflow, or a similar tool to sniff the request and response as it passes across your interface.

To get started writing a simple server we'll need the relevant library package installed:

root@lappy:~# apt-get install librpc-xml-perl
Reading package lists... Done
Building dependency tree... Done
The following extra packages will be installed:
  libxml-parser-perl
Suggested packages:
  libapache-mod-perl
The following NEW packages will be installed
  librpc-xml-perl libxml-parser-perl
0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded.
Need to get 481kB of archives.
After unpacking 1499kB of additional disk space will be used.
Do you want to continue [Y/n]? 

Now we can get started. If you're new to perl, or this library, it is worth installing the perl-doc package. This allows you to read the documentation contained in almost all perl libraries via:

perldoc RPC::XML::Server
The Server

Once we've got the Perl XML-RPC libraries installed we can create a simple server. We'll create something which has two methods which can be invoked remotely:

hello

This will return a single static string.

hellofoo

This method will take a single string argument and return "hello + argument". (For example "helloFoo( 'me' );" should return "Hello me".

The server we're writing is very simple, but it should be stable and reliable. It will listen upon localhost:8888.

Here is the code:

#!/usr/bin/perl -w
#
# Simple RPC server with two methods.
#

use RPC::XML::Server;

# Create the server
my $daemon = RPC::XML::Server->new( host => 'localhost',
                                    port => 8888 );

# Add a simple method
$daemon->add_method({ name => 'hello',
                      signature => ['string'],
                      code => sub {return "Hello world!";} });

# Add another method
$daemon->add_method({ name => 'hellofoo',
                      signature => ['string string'],
                      code => \&helloFoo });

# Start the server up.
$daemon->server_loop();

# The function called when a request for 'hellofoo' is made.
sub helloFoo
{
    my( $srv, $name ) = ( @_ );

    return( "Hello $name" );
}

The functions we've added are described by a "signature". A signature is comprised of the argument(s) the function accepts and the return type of the function. The first method "hello" returns a string and requires no arguments.

The second function has the signature of "string string" - this means it returns a string, and requires a string.

Start the server up by running:

skx@lappy:~/RPC$ perl server.pl 

Since we're listening upon port 8888 we don't need to be root - indeed that would be a bad idea.

Perl Client

Since using XML-RPC is cross-platform and cross-language we're not restricted to making our client run on the same host, or be written in the same language as our server.

Still before we proceed to using something different we'll look at a simple perl client. Here is some sample code:

#!/usr/bin/perl -w

use strict;
require RPC::XML;
require RPC::XML::Client;

my $client = RPC::XML::Client->new('http://localhost:8888');

#
#  Call our hello function with no arguments.
#
#  Print the response
#
my $res = $client->send_request('hello');
print "(hello) - Response: " . $res->value . "\n";

#
#  Now call our "Hello Foo"
#
my $req = RPC::XML::request->new('hellofoo',
                                 RPC::XML::string->new('Steve'));
$res = $client->send_request($req);
print "(hellofoo 'Steve') - Response value = " . $res->value . "\n";

This can be executed and will display the output we'd expect:

skx@lappy:~/RPC$ perl ./client-perl.pl 
(hello) - Response: Hello world!
(hellofoo 'Steve') - Response value = Hello Steve
Ruby Client

Ruby is a fast-growing scripting language. When you install the language you'll get several libraries included with it, including an RPC extension.

If you don't have ruby installed already you can install it by running:

root@ lappy:~# apt-get install ruby1.8
Reading package lists... Done
Building dependency tree... Done
The following extra packages will be installed:
  libruby1.8
Suggested packages:
  ruby1.8-examples rdoc1.8 ri1.8
The following NEW packages will be installed
  libruby1.8 ruby1.8
0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded.
Need to get 1647kB of archives.
After unpacking 6005kB of additional disk space will be used.
Do you want to continue [Y/n]? 

Here is an extremely basic Ruby client for our server:

#!/usr/bin/ruby1.8

# Use the RPC client
require "xmlrpc/client"

# Make an object to represent the XML-RPC server.
server = XMLRPC::Client.new( "localhost", "/", 8888 )

# Call the remote server and get our result
result = server.call("hello")
print "Calling (hello) - Result: #{result}\n"

# Now call the 'hellofoo' method
result = server.call( "hellofoo", "Debian" )
print "Calling (hellofoo 'Debian') - Result: #{result}\n"

This should be fairly readable even if you've not seen ruby before. Essentially we create a client object then use it to call the two methods in our server.

Here is what it looks like:

skx@lappy:~/RPC$ ruby1.8 ruby-client 
Calling (hello) - Result: Hello world!
Calling (hellofoo 'Debian') - Result: Hello Debian

As you can see the two methods were made, and the results returned as expected.

Further Work

Once you've coded your RPC server, one that is capable of doing something useful, the next logical step is to connect to it remotely.

In our sample server you cannot do that since it is only listening upon the "loopback" interface however it is very simple to make it globally available.

Change the following section of the server:

# Create the server
my $daemon = RPC::XML::Server->new( host => 'localhost',
                                    port => 8888 );

Remove the host line to leave this:

# Create the server
my $daemon = RPC::XML::Server->new( port => 8888 );

Now if you update one of the clients to specify the hostname of your server instead of localhost you'll be able call procedures remotely!

For example here is a simple RPC server which reports uptime:

#!/usr/bin/perl -w
#
# Simple RPC server to show "uptime"
#

use RPC::XML::Server;

my $daemon = RPC::XML::Server->new( port => 8888 );
$daemon->add_method({ name => 'uptime',
                      signature => ['string'],
                      code => sub {return `uptime` ;} });
$daemon->server_loop();

With this process running you can query the uptime from any host on your LAN with code such as this:

#!/usr/bin/perl -w
#
# Query uptime via the RPC server.  Call with the hostname of your server.
#

use strict;
require RPC::XML;
require RPC::XML::Client;

# Use the hostname specified as a command line argument.
# Revert to 'localhost' if one isn't given.
my $host = shift || "localhost";

my $client = RPC::XML::Client->new("http://$host:8888" );
my $res    = $client->send_request('uptime');
print "$host - uptime: " . $res->value . "\n";

Here is an example of it running locally:

skx@lappy:~/RPC$ 
skx@lappy:~/RPC$ perl uptime-client 
localhost - uptime:  18:59:31 up  1:17,  5 users,  load average: 0.00, 0.02, 0.08

Now we can see it called from another host on the LAN:

skx@desktop:~/RPC$ perl ./uptime-client lappy
lappy - uptime:  18:59:00 up  1:17,  5 users,  load average: 0.00, 0.02, 0.09

Using a server as simple as this you can make lots of interesting and useful applications, especially you combine an RPC server and a web application. RPC clients are available for PHP, for example, and can be used to update webpages with information dynamically.

 

 


Re: Cross-platform cross-environment RPC server creation
Posted by stevenothing (87.194.xx.xx) on Thu 27 Jul 2006 at 15:30
If anyone uses nagios for monitoring, and wants a rough-but-works xmlrpc method to query it (works with nagios 1.3 using the text file backend), there's some stuff at http://www.nihilistic.org.uk/files/nagios-xmlrpc.tar.gz

It requires python, but no additional modules other than those in the python2.3 package.

[ Parent ]

Re: Cross-platform cross-environment RPC server creation
Posted by Anonymous (67.161.xx.xx) on Tue 26 Jun 2007 at 16:34
Sweet, just what i was looking for. I wanted the uptime of a server without the overhead and clutter of a terminal. Maybe i'll try to put it into a Gnome applet... Thanks.

[ Parent ]