The last of the XML-RPC toolkits discussed here is the RPC::XML package, developed by one of the authors of this book, Randy J. Ray. This package isn't quite as independent as the XMLRPC::Lite implementation; it requires an external XML parser (currently the XML::Parser package from CPAN). This package gives you a lot of flexibility in creating server applications. The main server class, RPC::XML::Server , can function as a standalone server using either the HTTP::Daemon class from the LWP package or the Net::Server package from CPAN. This latter package supports several different multiprocess models and provides all the background operation for whichever model the application chooses to use. Applications can even choose a model on-the-fly , rather than being locked into a specific one. The RPC::XML package also provides a server class designed especially to act as a content handler for Apache and mod_perl . In addition to these choices in server management, the package comes with a set of server-side methods that implement the introspection interface pioneered by the PHP XML-RPC suite that was used in building the Meerkat API. This introspection interface was described in Chapter 3. 4.4.1 Client Example: meer2html.plThe RPC::XML version of the Meerkat example isn't significantly different from the other toolkits' versions. Example 4-5 shows the relevant parts of this version of the script. Appendix C lists the full program. Example 4-5. The meer2html-RPC::XML.pl scriptuse RPC::XML::Client; $client = RPC::XML::Client # Remember that MEERKAT was declared with "use constant" ->new(MEERKAT, error_handler => sub { die "Transport error: $_[0]" }); 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->simple_request($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->simple_request('meerkat.getItems', { $key => $val, time_period => '7DAY', num_items => $num, descriptions => 200 }); } As with the previous toolkits, only one library is used to get the functionality needed for a client. The constructor for the class is slightly different from both previous toolkits. The first argument must be the URL of the server the client will be conversing with. The constructor can accept some other arguments as well, but in this case, all that gets passed is the error_handler key followed by a closure that is used as a callback when errors occur. The way RPC::XML handles errors is to return an error message as a simple string, whereas most methods return references normally. Setting the callback in this case causes an error to immediately call die , without the code having to check every return value later on. For the sake of completeness, the show_data routine is also included here, despite being completely identical to the version used for RPC::XMLSimple and XMLRPC::Lite . This just underscores the simplicity of the way in which all three toolkits make data available to the programmer. The resolve_name and get_data routines are slightly different than either of the two previous client examples. First, the method the RPC::XML::Client class uses is called simple_request . In fact, the client class defines two methods for making remote requests , the other one being send_request . The one used here returns native Perl data, while the other form returns a data object, similar to the behavior of XMLRPC::Lite . You can choose whether you wish to get the returned data as an object that can be tested for failure and treated as a fault or if you want to let callbacks handle that and just get the Perl data directly. 4.4.2 The RPC::XML::Client Class in DetailInstances of the RPC::XML::Client class are containers for other class instances, specifically LWP::UserAgent and HTTP::Request . The user -agent object manages the actual communication to and from the server. The request object is created ahead of time so that common elements, such as the majority of the HTTP headers, can be precomputed, rather than newly evaluated on every outgoing message. The RPC::XML::Client class has a number of methods available to application developers, some of which are summarized here. Appendix A contains a full reference to the classes for all the XML-RPC toolkits covered in this chapter.
These methods are useful for setting callbacks that need to have access to the object itself. They can be given closures as new callbacks that contain lexically scoped references to the object. The RPC::XML::Client class doesn't auto-dispatch methods the way XMLRPC::Lite does. It does offer helper functions for creating data objects in cases where the Perl interpreter might not be able to correctly guess the type. Table 4-4 lists the data classes and the related helper functions. Table 4-4. The RPC::XML data classes and functions
All the helper functions take a single value as an argument and return an object reference from the appropriate class. The classes themselves share a number of common methods:
These methods apply to the RPC::XML::array and RPC::XML::struct classes, as well. Helper functions aren't available for them, but RPC::XML can map them from list and hash references, respectively. Faults are managed in a class called RPC::XML::fault , which is a subclass of the struct class (and it doesn't have a helper function either). Faults must be created using the class constructor, but the constructor has a few shortcut approaches available to the writer:
As mentioned earlier, the fault class overrides the is_fault method to return true . 4.4.3 The Fortune Server Using RPC::XML::ServerAs with previous implementations of the fortune example, the code in Example 4-6 is very simple. The RPC::XML::Server class version is longer than either of the previous two, however. This is because this server class doesn't provide a way to add local procedures by class or namespace as previous implementations do. One thing that sets this server class apart from the previous two is that it manages and monitors the signatures associated with server-side routines. These are apparent in Example 4-6, and a client trying to connect to this server would have to form a valid request based on these specifications. Example 4-6. The RPC::XML fortune serverRPC::XML::Server->new(port => 9000) ->add_proc({ name => 'books', signature => [ 'array', 'array string', 'array array' ], code => \&XRFortune::books }) ->add_proc({ name => 'fortune', signature => [ 'array', 'array string', 'array array' ], code => \&XRFortune::fortune }) ->add_proc({ name => 'weighted_fortune', signature => [ 'array', 'array string', 'array array' ], code => \&XRFortune::weighted_fortune }) ->server_loop; In this example, only the toolkit-specific lines are shown (the full code is given in Appendix C). The class constructor isn't that different in syntax or function from the previous examples. Like XMLRPC::Lite , most of the methods return the object reference upon success, allowing for methods to be chained together. Here, the add_proc method is called three times, adding one procedure each time. After the third call, the server_loop method handles the socket-accept logic until a signal of some kind causes it to exit, at which point the script also exits. The design philosophy behind this server implementation is to exert more hands-on control over the methods and procedures that are published. It does this by allowing them to be added manually, loaded from a XML file (a format called "XPL," detailed later), or by loading entire directories of such files at a time. The XML file format will be discussed later, after we cover the basics of the server class itself. 4.4.4 The RPC::XML::Server Class in DetailThe RPC::XML::Server class is a container much like the server classes from RPC::XMLSimple and XMLRPC::Lite . Like those two, the basic functionality is found in the HTTP::Daemon class from the LWP package. But this server class can also use the Net::Server package from CPAN, if it is available. Net::Server provides a server framework with several different styles for the connection/service model. Besides the standard single-process and fork-per-request models, the package can also emulate Apache's preforking, multiple-child architecture. The package is worth evaluation independent of XML-RPC itself. Server-side code is associated with a server object in one of three forms: a method, a procedure, or a function. The names distinguish the way the server interacts with the code when managing a request, as shown in Table 4-5. Table 4-5. Types of server-side code
A method ( RPC::XML::Method ) checks signatures but doesn't count the server object (which is passed as the first parameter when invoked) in the argument list. This allows the method itself to access the server object and its relevant data. A procedure ( RPC::XML::Procedure ) is almost the same as a method, but it doesn't get the server object. It does check signatures. Finally, the function ( RPC::XML::Function ) does no checking of signatures at all, and doesn't receive the server object. [2]
The constructor for the server class has a complex array of options available. These are summarized in Table 4-6. At present, much of the server functionality can be controlled only through these options. Not all have corresponding accessor methods for later modification. Table 4-6. The options available to the RPC::XML::Server constructor
Compared to the constructor, the remaining class methods are pretty simple and clear. Some of the more commonly used ones are:
As of Version 0.43 of the toolkit, RPC::XML::Server and the Apache subclass derived from it support compression using the Compress::Zlib module from CPAN, if available. The mechanics of expressing compression support in the message headers tries to be compatible with XMLRPC::Lite . In general, if the compression module is available and both ends of the conversation support compression, it should happen behind the scenes without any direct intervention on the part of the application. 4.4.4.1 Managing server-side code with XPL filesThere have been numerous references to XPL files up to now. These files are a format originally created for expressing methods for the server. Since then, the syntax has been extended to distinguish methods, procedures, and functions. The actual code for the method may also be provided in multiple languages (though obviously only the Perl code matters to this server). The goal of the format was to provide a way to exchange "bundles" of XML-RPC functionality with all the meta-data (signatures, help text, etc.) packaged as well. XPL originally referred to the file being a .pl wrapped in XML. Example 4-7 shows a sample XPL file for one of the introspection routines the RPC::XML package includes. This example has the help text removed, as well as comment blocks, for brevity. The full example is listed in Appendix C. Example 4-7. A sample XPL file, listMethods.xpl<?xml version="1.0"?> <!DOCTYPE methoddef SYSTEM "rpc-method.dtd"> <methoddef> <name>system.listMethods</name> <version>1.1</version> <signature>array</signature> <signature>array string</signature> <help> ... </help> <code language="perl"> <![CDATA[ #!/usr/bin/perl sub listMethods { use strict; my $srv = shift; my $pat = shift; my @list = sort $srv->list_methods; # Exclude any that are hidden from introspection APIs @list = grep(! $srv->get_method($_)->hidden, @list); @list = grep(index($_, $pat) != -1, @list) if ($pat); \@list; } _ _END_ _ ]]></code> </methoddef> The layout of the file is very basic. The outer tag is one of methoddef , proceduredef , or functiondef . The container tag therefore declares the type of procedure being defined (in terms of the three types explained earlier). Within this container must be a name tag that provides the published name of the routine, at least one signature tag with a space-separated signature (except in the case of RPC::XML::Function declarations), and the code container with the actual Perl code for the routine. The source code can be encoded one of two ways: using an XML CDATA section, as done earlier, or by ensuring that any < or & characters are entity-encoded. By using the CDATA approach and including both the #!perl start-up line and _ _END_ _ token at the end, the whole XPL file can be syntax-checked with perl -cx . (This doesn't validate the XML, it checks only the Perl syntax.) In addition to the previous tags, the file can also define version (the version number/symbol of the code), hidden (an empty tag whose presence means the function should not be listed by the introspection API), and help (the minidocumentation text the introspection API uses when describing the routine to a client). These tags are all optional, and the version tag information isn't generally used anywhere except in the Apache status module, described later. The application developer isn't left with the task of creating these files manually. The distribution of RPC::XML includes a utility script that can create the XPL files from files containing the Perl code and meta-data either in files or passed on the command line. The script, make_method , is documented and the distribution's Makefile.PL contains an example of integrating it into a build process. The distribution uses the XPL format to manage the introspection API routines that are provided. The make_method tool can build an XPL file from command-line data or from a meta-configuration file: make_method --name=reboot --helpfile=reboot.help --code=reboot.pl \ --signature=int --output=reboot.xpl make_method --base=reboot 4.4.5 The Introspection Interface for ServersTable 4-7 lists the server introspection routines, along with their calling syntax (signatures) and basic functionality. Table 4-7. The server introspection routines
Both the primary server class and the Apache-specific class (described next ) default to loading these routines. Loading of the routines can be disabled by passing no_default => 1 in the constructor. Likewise, they can be added at a later time by calling a method on the server object called add_default_methods . The Apache server class has a slightly different version of system.status that reports additional information pertaining to the Apache environment. 4.4.6 Writing for Apache with Apache::RPC::ServerOne distinct difference between the RPC::XML toolkit versus the others is the native support for running under Apache and mod_perl . The basic server class is subclassed in Apache::RPC::Server , which provides handlers for the PerlHandler and PerlChildInitHandler phases of the request/response lifecycle. The handler and init_handler methods of the class are used as Apache location handlers, not as components of a script that would run under the Apache::Registry system. Instead, the Apache configuration maps a URI location (or more than one) to the XML-RPC subsystem. The objects of this class don't allocate HTTP listeners, operating instead on the request objects that Apache creates for handlers to access. 4.4.7 Configuring Server ObjectsRPC server objects for the Apache environment are usually configured in a different manner than the other servers. In many cases, the configuration of the XML-RPC handler in Apache will be a simple PerlHandler directive: <Location /rpc> SetHandler perl-script PerlHandler Apache::RPC::Server </Location> This configuration doesn't install any server-side code except for the introspection routines. To give the administrator the ability to control the Apache RPC environment at this level, the Apache::RPC::Server class allows for most of the configurable parameters to be set using location-based directives. The following fragment shows many of these in use, with in-line comments explaining them: <Location /rpc> SetHandler perl-script # handler( ) doesn't have to be specified, but it can be PerlHandler Apache::RPC::Server->handler # define a name for the server, in case more than one # will be configured for this Apache host RpcServer rpc # add a directory to the list of dirs that methods are # kept in; may appear multiple times RpcServerDir /opt/rpc/methods # Add on specific method by file name; may also appear # multiple times RpcServerMethod /opt/rpc/extra/method.xpl # suppress loading the introspection API methods RpcDefMethods no # disable auto-loading of methods... RpcAutoMethods no # ...but allow existing methods to auto-update: RpcAutoUpdates yes </Location> There are more options detailed in the manpages for the class. When configured using Location directives, the server objects pick up their methods from a three-step sequence:
Any methods can be overridden in this process by a adding a new method under the name of an existing one. Thus, a server can add all the routines in a given directory, then use one or more RpcServerMethod directives to selectively replace some of them. For more direct control over the RPC server objects, Apache can be configured using <Perl> sections in the configuration file. Using this method, the server-side code can be controlled in finer detail. The configuration also has full access to the server operations, and multiple server objects can be configured to share common methods, rather than having multiple copies of the same code in memory simultaneously . This fragment shows a sample configuration with two servers, one of which is access-controlled; the other isn't: <Perl> # First, create and configure some Apache::RPC::Server # objects # One regular one, with the standard settings: $main::defobj = Apache::RPC::Server->new(path => '/RPC', auto_methods => 1, auto_updates => 1); # One version without the default methods, and no # auto-actions $main::secobj = Apache::RPC::Server->new(no_default => 1, path => '/rpc-secured'); # Give the secured one access to server.status, for report # gathering: $main::secobj->copy_methods($main::defobj, 'system.status'); # Imagine that add_method and/or add_methods_in_dir has been # used to add to the methods tables for those objects. Now # assign them to locations managed by Apache: $Location{'/RPC'} = { SetHandler => 'perl-script', PerlHandler => '$main::defobj' }; $Location{'/rpc-secure'} = { SetHandler => 'perl-script', PerlHandler => '$main::secobj', AuthUserFile => '/etc/some_file', AuthType => 'Basic', AuthName => 'SecuredRPC', 'require' => 'valid-user' }; </Perl> In the example, the password-protected RPC server chose not to load the default methods. However, there was a need for the system.status method to track the statistics of the server. This can be explicitly added with add_method , but by using share_methods , the actual internal Perl code already present on the " open " server is used. This approach has another advantage over the Location configuration method. When servers are configured using Perl sections, they are configured in the parent before the child processes are created. As a result, the children are created with the servers already in place and ready to operate . This is because the code sets the location handler to be an actual object reference, rather than a class name. When the handler is called for a request, it has the object there and ready, and doesn't have to first check for and retrieve a (possibly new) class instance to handle the request. With the Location configuration, the objects aren't created until the first request is received, which keeps them from being shared between children. 4.4.8 The Apache::RPC::Status MonitorThe last topic for this chapter isn't an XML-RPC processor, but it works together with the Apache server class. Apache::RPC::Status is a status monitor modeled after the Apache::Status package that reports the statistics of configured XML-RPC servers controlled by the Apache server. The Apache::RPC::Status package lets a server administrator check the status of the RPC servers configured on a given host. As Figures Figure 4-1 through Figure 4-3 illustrate , the monitor allows the view to go from a high-level view of the server (or servers) down to the numbers for a given procedure. Figure 4-1. The main monitor pageFigure 4-2. The server-level page, showing known methodsFigure 4-3. The method-level page, showing documentationFortunately, configuring and enabling the status monitor is much less intensive than are the RPC servers themselves: </Location /rpc-status> SetHandler perl-script PerlHandler Apache::RPC::Status </Location> |