4.2 RPC::XMLSimple


The RPC::XMLSimple module provides support code for the client and server classes, RPC::XMLSimple::Client and RPC::XMLSimple::Daemon . An application will include only the server or client code, as needed. Both of those modules already include the core elements.

Installation of the module is very simple because it is available through CPAN and has only a few simple dependencies. It does require the XML::Parser module to handle the XML data, and the LWP module for both client communications and server functionality.

4.2.1 Client Example: meer2html.pl

Let's reimplement the meer2html.pl tool from Chapter 3 using the toolkit instead of building and parsing XML-RPC requests and responses manually. Comparing just the length in lines of the two versions of the utility (with comments and blank lines excluded), the Frontier version is less than half the length of the manual version.

Example 4-1 shows the relevant parts of the meer2html-Frontier.pl code. The sections shown are those that differ significantly from the original version.

Example 4-1. The meer2html-Frontier.pl script
 use RPC::XMLSimple::Client;     $client = RPC::XMLSimple::Client->new(url => MEERKAT);     sub show_data {     my $data = shift;         print STDOUT qq(<span class="meerkat">\n<dl>\n);     for (@$data) {         print STDOUT <<"END_HTML"; <dt class="title"><a href="$_->{link}">$_->{title}</a></dt> <dd class="description">$_->{description}</dd> END_HTML     }     print STDOUT qq(</dl>\n</span>\n); }     sub resolve_name {     my ($str, $name) = @_;         $name = "meerkat.get${name}BySubstring";     my $resp = $client->call($name, $str);     die "resolve_name: $str returned more than 1 match"         if (@$resp > 1);         $resp->[0]{id}; }     sub get_data {     my ($key, $val, $num) = @_;         $client->call('meerkat.getItems',                   { $key         => $val,                     time_period  => '7DAY',                     num_items    => $num,                     descriptions => 200 }); } 

The first difference is that rather than " use "ing three modules for XML management and transport, only one is needed ( RPC::XMLSimple::Client ). As a result, the only object created for communication with Meerkat is $client , an object of the RPC::XMLSimple::Client class.

The example then skips forward to the show_data routine. In the previous version, this routine extracted data from the XML that Meerkat returns. That isn't necessary with RPC::XMLSimple , because the toolkit has already converted the return message from the server into a native Perl data structure. Because the data returned from Meerkat is in an array of structures, the Perl data received here is an array reference, each element of which is a hash reference.

The resolve_name routine is also considerably simpler. The most significant difference is the absence of hardcoded XML. The call method of the RPC::XMLSimple::Client class handles the creation of the XML block behind the scenes. Both remote routines return an array of structures, even if the number of structures is just one. So the return value of the call is made by dereferencing the first element and then pulling the id key from it.

Finally, the get_data is also significantly shorter and simpler. It, too, benefits from not needing to explicitly construct the XML for the main data-retrieval request. The space saved is even more notable because the original sported a fairly lengthy and involved XML block. The other benefit is that the return value comes back as an array reference containing hash references. There is no need to preprocess this data at all before it gets handed to the next part of the pipeline, so the invocation of the call method acts as the return value as well.

When errors occur, this package throws exceptions in the form of die statements. In this script, the only thing that would be done with an error would be to die as well, so no effort is made to trap these.

4.2.2 The RPC::XMLSimple::Client Class in Detail

The RPC::XMLSimple::Client class is designed to abstract from the application all the overhead and work of maintaining the user agent and other objects from the LWP class that are used in managing the actual communications to and from the remote server. In addition to that, it provides some utility functionality to ease the expression of data in a format that the toolkit can correctly turn into an XML-RPC message.

Client creation is with a new method, which may take any of five parameters as input (or a hash reference using these keys and their values):

url

The value for this key is the URL of the server to which the client is to connect.

proxy

If necessary, a proxy URL may be passed with this parameter, which will be used to route all communications to the server URL.

encoding

If the outgoing messages are to be encoded in a character set other than UTF-8 (the default for XML), this parameter allows for specifying the desired encoding.

use_objects

If this parameter has a value that evaluates to true , calls to the server return data as object instances rather than native Perl values. Useful when a string value might look like an integer or some other type.

debug

If set to a true value, this parameter dumps the request and response messages to the controlling terminal.

The client object actually makes the remote call to the server using the call method. It takes the remote procedure name as a first argument, and any parameters to the call as subsequent arguments. Like all the toolkits here, the client is able to do a good job of converting Perl scalars to the appropriate XML-RPC datatypes. There are data-conversion routines available for those cases where a value could fit more than one type.

The class methods boolean , date_time , base64 , int , double , and string all take their single argument and return an object. The objects may be used to fetch or set the underlying values using a method called value . If called with no argument, the method returns the object's current value. If an argument is passed, it is set as the new value. When one object is passed to call , it is guaranteed to be properly encoded in the outgoing message. When use_objects is set on the client, these are the types of objects that will be returned from calls to the server. Each object class has a name, as shown in Table 4-1.

Table 4-1. The data classes in RPC::XMLSimple

XML-RPC datatype

RPC::XMLSimple data class

 Int 
 RPC::XMLSimple::Integer 
 Double 
 RPC::XMLSimple::Double 
 String 
 RPC::XMLSimple::String 
 Boolean 
 RPC::XMLSimple::Boolean 
 dateTime.iso8601 
 RPC::XMLSimple::DateTime::ISO8601 
 base64 
 RPC::XMLSimple::Base64 

The client class correctly handles XML-RPC arrays and structures when passed in a Perl array reference or hash reference. When the return value from a server call is an array or structure, the client object returns an array or hash reference. If use_objects is set, the low-level simple data values will be objects.

When any type of error occurs, whether a localized runtime error (such as transport or socket errors) or a fault returned from the server, the client object throws an exception in the form of a die call. The text of the error message will contain some detail about what caused the exception, including the fault code and fault string, when the cause is a <fault> from the server. Unfortunately, there is no finer-grained method of catching or handling faults.

4.2.3 A Server Example: Providing "Fortunes"

Let's build a simple service that wraps the fortune program. To make it more interesting and challenging, rather than just simply running the program on the server and sending the text back to the client, the service lets the client take advantage of the functionality present in the modern implementations of fortune . Clients can choose from various books of quotes, as well as the ability to manipulate the statistical probability of any given book being chosen as the source. Current versions of the program can do even more than this, but these features will be enough for the example.

This example uses a simple package of code called XRFortune.pm, for "XML-RPC Fortune," to handle the interaction with the fortune program. The XRFortune module is described in the sidebar The XRFortune.pm Module, and its source is given in Appendix C.

The XRFortune.pm Module

This module wraps the well-known fortune application. The syntax of the commands is based on the GNU version of the program, as distributed by Red Hat Linux. You can download the GNU fortune program from ftp://ftp.gnu.org if you don't have it.

There are three routines in the XRFortune package: books , fortune , and weighted_fortune .

  • The books routine can be called in one of three ways: with no arguments, with a single string, or with an array reference of strings. With no arguments, it returns a list of the books fortune has access to, as an array reference. With one string or a list of strings, it validates the strings as known books, returning an array reference of the ones that are valid. The return value is always an array reference.

  • The fortune routine takes an optional argument, either a single book or a list of books as an array reference. If any arguments are passed, they are given to the invocation of fortune to restrict the selection to just those books. Otherwise, it is called with no arguments, allowing the choice to come from any of the known books.

  • The weighted_fortune routine allows for control over the likelihood that a fortune will come from a given book. There are two ways this is done: "pass an array reference" means to equally weight all the (valid) books in the array (the default within fortune is to weigh them by their size relative to the whole). The second way is to pass a hash reference whose keys are book names and whose values are the integer weights the books should have. The list has invalid books pruned out, then the remaining weights are totaled up, and must equal 100 exactly, or a fault is returned. The books and their weights are then converted to the appropriate command-line sequence for the call to fortune .

Both fortune and weighted_fortune return the lines of text as an array reference, with newline characters chopped off. This avoids confusion on systems with different interpretations of newlines.

The example server script is short enough to show in its entirety and is given in Example 4-2.

Example 4-2. The fortune-Frontier.pl server
 #!/usr/bin/perl -w     use strict;     use XRFortune; use RPC::XMLSimple::Daemon;     RPC::XMLSimple:Daemon->new(          LocalPort => 9000,          ReuseAddr => 1,          methods => {              books             => \&XRFortune::books,              fortune           => \&XRFortune::fortune,              weighted_fortune =>  \&XRFortune::weighted_fortune          });     exit; 

The RPC::XMLSimple::Daemon instance is created using the commonly named new constructor. As will be explained later, the only one of the arguments it actually cares about is the methods key and the hash reference passed as its value. The hash reference is expected to provide a mapping of internal subroutines to names by which they may be remotely called. The keys of the hash table are the external names, and the values are subroutine references. Thus, they may also be anonymous subroutines or closures.

The LocalPort and ReuseAddr parameters are passed to the parent class of RPC::XMLSimple::Daemon and are used to bind the server to port 9000, as well as to set a parameter on the underlying socket structure that enables binding even if there are left-over sockets from a previous execution.

4.2.4 The RPC::XMLSimple::Daemon Class in Detail

The RPC::XMLSimple::Daemon class is a subclass of HTTP::Daemon from the LWP module, which in turn is a subclass of IO::Socket::INET . The initial two parameters in Example 4-2 are actually parameters to the constructor for the socket class. Both the RPC and HTTP subclasses create their objects by first calling the super-class constructor, then adding extra material to the newly created object.

This package provides one of the lightest-weight daemon implementations of the XML-RPC choices. As can be seen in Example 4-2, the constructor goes straight into the typical socket-based connection loop. The call to new doesn't actually return unless there is an error in object creation (in which case it simply returns undef ).

The daemon doesn't make any effort to track or enforce method signatures for the methods it publishes. Incoming calls are sent to the matching subroutine reference with the list of parameters that accompanied the request. The return value of the subroutine call is serialized into the response. If a routine returns a list of values, only the first one is sent back to the client, since the protocol expects a return value to be a scalar (which covers list references and structure/hash references).

As it is currently implemented (based on Frontier::RPC Version 0.06 and RPC::XMLSimple Version 1.0), the server object not only goes directly into the accept loop without offering the application any direct control, but it uses a hardcoded path for the requests it is willing to accept. The server expects all incoming RPC requests to be on the URL path /RPC2 , a value that is coded directly into the server class itself. This is likely to change in a future version.



Programming Web Services with Perl
Programming Web Services with Perl
ISBN: 0596002068
EAN: 2147483647
Year: 2000
Pages: 123

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net