perfectxml.com
 Basic Search  Advanced Search   
Topics Resources Free Library Software XML News About Us
  You are here: home ╗╗ Free Library ╗╗ O'Reilly Books » Programming Web Services with XML-RPC Sunday, 11 March 2007
 
Programming Web Services with XML-RPC

ISBN: 0596001193
Author(s): Simon St. Laurent, Joe Johnston, Edd Dumbill (Foreword by Dave Winer)
June 2001

XML-RPC, a simple yet powerful system built on XML and HTTP, lets developers connect programs running on different computers with a minimum of fuss. Java programs can talk to Perl scripts, which can talk to ASP applications, and so on. With XML-RPC, developers can provide access to functionality without having to worry about the system on the other end, so it's easy to create web services.

Buy this book!

Copyright O'Reilly & Associates, Inc.. Used with permission.

Chapter 3

Client-Server Communication: XML-RPC in Java

Java was built from the ground up as a network-centric development environment. As a Java developer, XML-RPC offers you an opportunity to extend that foundation in a structured way. Adding XML-RPC to your toolkit makes it easier to integrate a Java application with an application built using another environment or simply to establish lightweight point-to-point communication between Java programs running on different computers. Although XML-RPC goes against the grain of much Java network programming (and even against some of the fundamental principles of object-oriented development), its alternative approach can be useful in many relatively common scenarios.

You already have a wide variety of Java-based XML and HTTP tools to choose from, but you can also take advantage of a prepackaged set of XML-RPC tools. Although understanding the foundations of XML-RPC is very useful for debugging and for establishing connections between systems in different environments, you can treat XML-RPC much like you do any other Java feature. There's some setup work to do, especially for XML-RPC servers, but most of this work is simple and needs to be done only once in the course of a program.

This chapter looks at how XML-RPC fits into Java's many network options. It demonstrates how to build a variety of different XML-RPC clients, servers, and handlers. Some of these examples take advantage of built-in functionality for setting up simple XML-RPC servers and handlers; others explore the possibilities opened up by handling more of the processing directly. The examples cover different styles of XML-RPC programming, from simple library function calls to more complex calls that manipulate information on the server.

Why XML-RPC for Java?

Java is already an extremely network-aware environment, complete with its own mechanisms for remote communication and coordination of objects on multiple systems. Remote Method Invocation (RMI) and RMI's support for the broader world of CORBA-based systems provide built-in support for distributing applications across multiple systems.[1] In many ways, Java is well ahead of its competitors, and its network support extends far beyond the simple request-response cycle of XML-RPC.

Despite Java's built-in network computing prowess, XML-RPC offers a few kinds of functionality that Java can't match. XML-RPC is far more lightweight than Java's built in RMI support, passing only parameters rather than objects. Java programs can use XML-RPC to connect directly to any other system supporting XML-RPC, rather than limiting connections to fellow RMI systems or having to use complex (and expensive) CORBA object request brokers.

As illustrated in Figure 3-1, XML-RPC can bring the direct connections RMI makes possible for strictly Java applications to applications that integrate multiple environmentsXML-RPC's use of HTTP as a transport substrate makes it relatively simple to integrate XML-RPC with the web-enabled applications that are already spreading across the computing landscape. At the same time, XML-RPC uses such a tiny subset of HTTP that Java applications can easily avoid the overhead of full-scale HTTP processing, working with a more minimal--and more efficient--driver that takes advantage of Java's built-in understanding of TCP/IP.

XML-RPC also offers you a shorter compilation and testing cycle. Unlike RMI, which requires recompilation of interfaces to register method signatures, XML-RPC allows the client to specify which method it wants to use and then looks for a handler. Because the reference is done by name, there aren't any stubs to manage or include, and changes can be made much more easily at runtime.

On the other hand, XML-RPC is definitely not appropriate in plenty of Java application situations. Much of the Enterprise JavaBeans (EJB) work already relies on RMI, and rewriting it to use XML-RPC would be a remarkable waste of time. Although a snippet of XML-RPC code might be useful as a simple bridge between an EJB-based application and code written for other environments, XML-RPC isn't designed explicitly to support the many demands of complex enterprise-scale design. Similarly, if you need to pass objects, rather than parameters, betweensystems, you should look into a more sophisticated set of tools than XML-RPC. XML-RPC lets you pass sets of parameters, not complex nested structures with associated method information, back and forth. As explained later in this chapter, XML-RPC's approach doesn't match cleanly with JavaBeans, either.

Figure 3-1.XML-RPC makes it possible to connect a wide array of programming environments

 

Although most Java programs aren't designed for use in the procedural framework that XML-RPC uses, an enormous amount of code in the typical Java program could conceivably be exposed as an XML-RPC procedure, with or without some degree of modification. Although Java is very object-focused, it retains enough links with procedural environments for developers to take advantage of "traditional" features, such as function calls, in the midst of complex object interactions. Although some of XML-RPC's rules, like its lack of support for void return values, make integrating XML-RPC with Java difficult, most of the integration process is pretty simple, limited only by the narrow range of data types and structures XML-RPC supports.

The XML-RPC library for Java does not require the methods it uses be static, but in some ways static methods fit the basic architecture of XML-RPC very well. Static methods are the closest things Java offers to procedural function calls, commonly used for utility functions (such as those in the Math class) for which the function itself is important, but there may not be any surrounding context. If you've built libraries of these kinds of methods, implementing key algorithms for processing arguments, you may find it especially easy to convert your old work to XML-RPC handlers.

You can also use XML-RPC servers in a larger Java framework to answer client requests while using those requests to modify their own information set. Rather than thinking about XML-RPC handlers as "mere" procedures, you can treat XML-RPC handlers as regular Java methods, limited only by difficulties in transferring objects between the routine making the method call and the routine performing processing. In every other way, XML-RPC can become a natural part of Java programming, providing yet another way to get information into and out of Java environments. Using XML-RPC can make connecting Java programs to programs written in other environments much simpler, and may be appropriate for some simple Java-to-Java cases, as well.

The XML-RPC Java Library

Although you could use the wide variety of XML resources available in Java to create your own XML-RPC package, Hannes Walln÷fer has already built a set of classes that provide XML-RPC capabilities for both clients and servers, including a small XML-RPC server that lets you work without the overhead of a full-scale Web server. Most examples in this chapter rely on his package.

As of this writing, the XML-RPC library for Java in still in beta, at Version 1.0 beta 4. Although it is unlikely that there will be major changes to the API, you should check the index.html file and documentation that come with the library if you encounter problems.

The XML-RPC Library for Java web site is http://classic.helma.at/hannes/xmlrpc/, and additional resources (including a mailing list with archives) are also available there. The examples in the current chapter use the helma.xmlrpc library, which is available at that site.

In addition to core XML-RPC functionality, the helma.xmlrpc package includes:

  • Classes for quick XML-RPC client and server creation

  • A micro-web server useful for setting up XML-RPC on systems that don't already have a web server running or don't want to use the existing server

  • A sample of Java servlet integration

  • A standalone set of classes used for building lightweight XML-RPC applets

The components included in the XML-RPC library include client- and server-specific classes used for creating requests and responses, as well as a more generic core that controls how the library handles HTTP processing and XML parsing.

Installing the helma.xmlrpc Library

The helma.xmlrpc library is available for free download as a zip archive at http://classic.helma.at/hannes/xmlrpc/. You'll need an unzipping utility to open the archive, which contains documentation, examples, source code, and three Java archive (jar) files. The files provide the executables you'll need to put XML-RPC into your Java environment.

The most critical of the jar files (all of which are stored in the lib directory) is xmlrpc.jar, which contains the core logic for implementing XML-RPC. The library also includes a jar file for the OpenXML parser, which is supported by default. You don't have to use the OpenXML parser, but it's very helpful if you install XML-RPC on a system without its own XML parser already installed. The last jar file, xmlrpc-applet.jar, includes code that lets you build applets that handle XML-RPC client transactions inside a browser and that can be controlled by JavaScript.

If you already have an XML parser installed, you only need to add xmlrpc.jar to your Java CLASSPATH environment variable, though you'll need to specify which parser you want to use in your XML-RPC client and server initialization code. If you don't have an XML parser already installed, or you just want to rely on the choice of helma.xmlrpc's creator, add the openxml-1.2.jar file to your CLASSPATH in addition to xmlrpc.jar. Although you may want to add xmlrpc-applet.jar to your CLASSPATH for development convenience, it's designed to be used in a browser and doesn't have to be installed on client computers.

You can distribute the helma.xmlrpc package with your own code, though the author requests that the license be distributed with the package and that any modifications be clearly documented.

General XML-RPC Library Configuration

The helma.xmlrpc class includes a set of static methods used to configure your XML-RPC processing. Because they are static methods, they affect all XML-RPC processing. You can't specify that some groups of XML-RPC methods should use a different parser from others, nor can you specify that debugging information should only be reported for certain groups of methods. This isn't normally a liability, however--it's very difficult to imagine a situation in which using different parsers for different methods might be justified, for example.

The setDriver( ) method lets you choose an XML parser for processing XML-RPC requests as they arrive. By default, the XML-RPC library uses the OpenXML parser, but developers can change that to any SAX-compliant parser. If your application uses a different XML parser for some other aspect of processing, it probably makes sense to use a single parser--it's easier to manage and cuts down on the size of the distribution.

The setDriver( ) method takes a single argument, the name of the parser to be used. It's probably best to enclose this method in a try/catch exception handler to handle the ClassNotFoundException the method will throw if the Java environment can't find the class:

try {
   // Use the Microstar AElfred parser for XML-RPC processing
 
   XmlRpc.setDriver("com.microstar.xml.SAXDriver");
} catch (ClassNotFoundException e) {
   // If no AElfred, provide an intelligible error message
 
   System.out.println("Could not locate AElfred.  Please check your 
classpath for com.microstar.xml.SAXDriver.");
}

The XML-RPC package also provides shortcut names for some commonly used parsers. For the most current list of shortcuts, see "Choosing the XML parser" in the documentation that comes with the distribution. In this case, we could have used aelfred instead of com.microstar.xml.SAXDriver as the argument to XmlRpc.setDriver( ).

By default, all XML-RPC messages are sent using the ISO-8859-1 character encoding, but the setEncoding( ) method allows you to choose alternate encodings. Encodings must be specified from the list of available Java encodings, available at http://java.sun.com/j2se/1.3/docs/guide/intl/encoding.doc.html.

To send out requests using the UTF-8 character set, you can write:

XmlRpc.setEncoding("UTF8");  //Java identifies UTF-8 as UTF8 without dash

The setDebug( ) method lets you watch your XML-RPC method registrations and request processing much more closely, providing information to the system console about the structure of the document received, the parameters extracted, and the result returned. To start debugging output, you'll need to pass a value of true to the setDebug( ) method:

XmlRpc.setDebug(true);  //turn on verbose debugging output

When you no longer need debugging information (which can pile up very quickly), pass a value of false to setDebug( ):

XmlRpc.setDebug(false);  //turn off verbose debugging output

The helma.xmlrpc package provides a lot of information about what happens inside a transaction. The following output, for example, describes a server-side transaction involving the anyArea handler that is created later in this chapter. Content marked in bold was generated by the code used to build the XML-RPC server, but all the rest was generated by the helma.xmlrpc package itself:

Attempting to start XML-RPC Server...
Started successfully.
Target object is class AreaHandler
Registered AreaHandler class to area.
Now accepting requests. (Halt program to stop.)
POST / HTTP/1.0
User-Agent: helma XML-RPC 1.0
Host: localhost:8899
Content-Type: text/xml
Content-Length: 112248
 
startElement: methodCall
startElement: methodName
endElement: methodName
startElement: params
startElement: param
startElement: value
startElement: struct
startElement: member
startElement: name
endElement: name
startElement: value
startElement: double
endElement: double
endElement: value
endElement: member
startElement: member
startElement: name
endElement: name
startElement: value
endElement: value
endElement: member
endElement: struct
endElement: value
endElement: param
endElement: params
endElement: methodCall
Spent 211 millis parsing
method name is area.anyArea
inparams = [{radius=3.0, type=circle}]
Searching for method: anyArea
Parameter 0: class java.util.Hashtable = {radius=3.0, type=circle}
outparam = 28.274333882308138
Spent 231 millis in request

In this case, the client requested a calculation of the area of a circle with a radius of 3, and received a response of 28.274.... after 231 milliseconds of processing on my 233MHz system. (This was an initial request, adding about 200 milliseconds while the classes loaded. Caching reduces the time per request significantly.)

The version field of the XmlRpc class may be useful for developers writing code that depends on version-specific features. At this point, the interface of the class appears to be stable, and developers should have control over the code they deploy, but this might be worth checking in situations when CLASSPATH conflicts and other hazards of shared systems could come into play.

Data Types and Java XML-RPC

The helma.xmlrpc package supports all XML-RPC data types (plus an extra, nil), representing them as built-in Java types. Because Java supports, and sometimes requires, object wrappers around its primitive types, the XML-RPC package can be flexible with XML-RPC clients and sometime with XML-RPC servers.

The helma.xmlrpc package can automatically map XML-RPC types to Java types, as shown in Table 3-1.

Table 3-1:XML-RPC versus Java data types

XML-RPC type

Simplest Java type

More complex Java type

i4

int

java.lang.Integer

int

int

java.lang.Integer

boolean

boolean

java.lang.Boolean

string

java.lang.String

java.lang.String

double

double

java.lang.Double

dateTime.iso8601

java.util.Date

java.util.Date

struct

java.util.Hashtable

java.util.Hashtable

array

java.util.Vector

java.util.Vector

base64

byte[]

byte[]

nil (extension)

null

null

XML-RPC clients may pass arguments to the helma.xmlrpc package using either of the choices above, when there is a choice. (Because Java won't accept primitives inside Vectors and Hashtables, the wrapper classes are sometimes necessary.) Similarly, XML-RPC handlers may use either choice for their return values. Because i4 and int are considered identical by the XML-RPC specification, the helma.xmlrpc package accepts either of them in incoming requests. The helma.xmlrpc package handles all encoding and decoding needed by the dateTime.iso8601 and base64 types.

On the other hand, XML-RPC handlers that use the automatic registration feature of helma.xmlrpc must use the simplest Java type available to describe the parameters they accept. The examples in the next few sections detail how this works and show some of the occasional extra work required to map complex types to simpler types.

If you're reusing existing code that takes the wrapper class, rather than the primitive, as an argument, it is possible to create an XML-RPC processor that supports the wrapper argument. However, you either have to write extra code that manages the conversion of the primitives to the wrappers before calling the existing code or build your own set of tools for handling XML-RPC requests. Writing a middleware handler might seem ungainly, but it's probably the easier route and isn't that difficult with the helma.xmlrpc.XmlRpcHandler interface.

Building XML-RPC Clients

Building XML-RPC clients with the helma.xmlrpc package is a relatively simple operation, involving the creation of an XmlRpcClient object, assigning parameters, and making a call to the XmlRpcClient's execute method. There are a number of exceptions that can be thrown, and there may be delays in getting a response, but for the most part, calling XML-RPC routines requires only a small amount of extra coding, much of which actually deals with exception handling.

The constructor and the execute( ) methods are the core of the XmlRpcClient class. The easiest way to handle them is in a try/catch structure, though you can encapsulate them in methods that throw the exceptions to a higher-level handler. The constructor may throw a MalformedURLException, a subclass of IOException; the execute( ) method may throw either IOException (when connections are refused, impossible, etc.) or XmlRpcException, which is issued when the XML-RPC server reports an error. The constructor accepts a String object representing a URL, a URL object, or the combination of a String for the hostname and an int for the port.

XmlRpcClient objects are reusable, though they only connect to the server originally specified when they were constructed. Applications that establish repeated connections to the same server may want to reuse the objects, but many applications just create the client, call a method, and disappear. In these cases, the constructor and execute( ) method may appear inside a single try/catch statement. When the constructor and execute( ) method appear together, the MalformedURLException may be treated as just another IOException, making it one fewer exception to catch.

The following example creates a client that can connect to port 8899 on the computer with the IP address 192.168.126.42. It sends a double (retrieved from user input through the args[] array) to a method identified as area.circleArea, expecting to get back the area of a circle whose radius is the double sent as the parameter. This code doesn't do anything with the result; it just sends the request and handles any exceptions that might be thrown.

try {
    // Create the client, identifying the server
    XmlRpcClient client =
        new XmlRpcClient("http://192.168.126.42:8899/");
 
    // Create the request parameters using user input
    Vector params = new Vector(  );
    params.addElement(new Double(args[0]));
 
    // Issue a request
    Object result =
       client.execute("area.circleArea", params);
 
} catch (IOException e) {
    System.out.println("IO Exception: " + e.getMessage(  ));
} catch (XmlRpcException e) {
    System.out.println("Exception within XML-RPC: " + e.getMessage(  ));
}
//Continue processing using result object...
    //result object will contain one of the XML-RPC data types

Depending on the response sent by the XML-RPC server, the result object may be any of the types described previously in the section "Data Types." In many cases, it is just a single typed value, but the helma.xmlrpc classes return Vector or Hashtable objects for XML-RPC responses that return arrays or structs.

As of Version 1.0 Beta 4, the XmlRpcClient class provides support for basic HTTP authentication. To use basic authentication, developers need to add only one method call to their client setup. The setBasicAuthentication( ) method takes two strings as arguments. The first is a username, the second is the password.

To add a username and password to the previous request, you simply need to add the code shown in bold in the following example:

try {
    // Create the client, identifying the server
    XmlRpcClient client =
        new XmlRpcClient("http://192.168.126.42:8899/");
 
             XmlRpcClient.setBasicAuthentication("myUsername", "myPassword");
    // Create the request parameters using user input
    Vector params = new Vector(  );
    params.addElement(new Double(args[0]));
 
    // Issue a request
    Object result =
        client.execute("area.circleArea", params);
 
} catch (IOException e) {
    System.out.println("IO Exception: " + e.getMessage(  ));
} catch (XmlRpcException e) {
    System.out.println("Exception within XML-RPC: " + e.getMessage(  ));
}
//Continue processing using result object...
    //result object will contain one of the XML-RPC data types

Although HTTP basic authentication isn't especially secure, code containing usernames and passwords in plain text is also a serious security risk. If you use this feature, you should consider combining it with other security tools (like the "paranoid" mode described for the XML-RPC server, later in this chapter) and protecting your code from distribution beyond the group of people authorized to work with those passwords.

The XmlRpcClient class uses Java's built-in support (java.net.URLConnection) for HTTP requests, giving it the ability to handle proxies automatically and to deal with a variety of server situations. Developers who need a lighter-weight XML-RPC client can use the XmlRpcClientLite class, which implements more minimal HTTP support. The two classes are identical, except in XmlRpcClient's support for more advanced HTTP functionality, including basic HTTP authentication.

Building XML-RPC Servers

Building XML-RPC servers is a bit more complex than building clients. In addition to building the core logic of your application, you need to publish your services to the XML-RPC library so it can manage requests. Your application may do this in the context of a servlet running within a larger Web server, or it may use the library's built-in WebServer class to create a minimal server handling only XML-RPC requests.

The simplest way to build an XML-RPC server relies on the WebServer class and uses the XmlRpcServer class's built-in ability to recognize Java classes and methods during the registration process. If you just need to publish methods whose parameters conform to the XML-RPC data types described earlier in this chapter, this is usually straightforward.

Using the WebServer Class

The helma.xmlrpc package includes a simple WebServer class that makes it easy to set up XML-RPC on systems that don't have a web server installed previously or to add an extra server listening on a nonstandard port. The WebServer class provides only the core of HTTP functionality used by XML-RPC, not the full set of functionality used to distribute web pages. This limitation should reduce the fears of network administrators who don't want to install internal web sites, while giving Java developers a small-footprint approach to adding XML-RPC to computers that aren't intended to be web servers.

Creating a new web server that uses the built-in WebServer class requires calling its constructor with a port number. For example, to create a web server that listens for XML-RPC requests on port 9876, you can call its constructor as follows:

WebServer server = new WebServer(9876);

If the WebServer can't start on the specified port, it throws a java.io.IOException. Depending on how you structure the program, you may want to enclose the constructor (and subsequent registrations) in a try/catch statement, or you may want the method managing the XML-RPC interface to pass the exception on to another handler.

Once you've set up the WebServer object, you can add and remove XML-RPC handlers to it using the functionality described in the section "Creating Handlers," later in this chapter. WebServer itself is a fairly small wrapper of functionality around the XmlRpcServer class, so you can use it the same way. The WebServer class does support HTTP Basic Authentication, but you'll need to create and register classes that implement the AuthenticatedXmlRpcHandler interface.

In addition to registering and processing XML-RPC handlers, the WebServer class provides a basic security model in the form of a "paranoid" mode. By default, WebServer accepts requests from all IP addresses. In paranoid mode, the server only accepts requests from specified IP addresses. It provides two methods, acceptClient( ) and denyClient( ), for building lists of approved and rejected clients. By default, no client connections are accepted when setParanoid( ) has been called with an argument of true. You'll need to use the acceptClient( ) method to add approved IP addresses, and can use denyClient( ) to trim that list. Once an IP address has been put on the denied list, acceptClient( ) can't bring it back--denials are more permanent than acceptances. (You'll have to restart the XML-RPC sever to reopen it.) Both methods accept the asterisk (*) for wildcarding, a feature convenient for dealing with groups of addresses without resorting to loops.

If your Java XML-RPC server is only communicating with clients on the same computer--to bridge Java and another environment, most likely--you may want to shut down all IP addresses except localhost, 127.0.0.1. The server won't even consider requests made from other systems if you take the following approach:

WebServer server = new WebServer(9876);
server.setParanoid(true);
server.addClient("127.0.0.1");

Using this setup, only requests directed to http://127.0.0.1:9876 will be considered, and because of the unique nature of the localhost address, they'll have to originate from the same machine as the XML-RPC server.

Another common setup permits an entire IP subnet to access the XML-RPC server, but exclude a few systems, perhaps gateways to other networks that might be hijacked. If an XML-RPC server was in the 192.168.137.x private network and the gateway router was 192.168.137.55, that server could be made available to all hosts on the local network except the gateway using the following code snippet:

WebServer server = new WebServer(9876);
server.setParanoid(true);
server.addClient("192.168.137.*");
server.denyClient("192.168.137.55");

Although filtering messages based on IP addresses isn't a complete security model by any means, it may be enough to make XML-RPC usable in a wide variety of contexts. If you need more security than this, you should consider using the XmlRpcServer class in a richer web server context, using the HTTPS and certificates facilities available on larger-scale web servers.

Using XmlRpcServer Without WebServer

If you integrate XML-RPC with existing web servers or you need more security than the IP filtering model of the WebServer class, you should use the XmlRpcServer class. Although the WebServer class accepts HTTP POST requests and feeds them directly to handlers, XmlRpcServer acts as an intermediary, accepting request information from servlets or other sources. Although XmlRpcServer doesn't handle the connection management end of HTTP, it does process all textual information sent over HTTP.

XmlRpcServer is simpler than WebServer because it leaves network details and security to its surrounding environment. The two classes use identical approaches for registering and unregistering classes containing XML-RPC methods, but XmlRpcServer requires more assistance than WebServer. The WebServer class listens for requests and processes them automatically; XmlRpcServer needs another class to listen for requests.

The XmlRpcServer class uses an XML-in, XML-out model for handling requests, leaving the rest to the supporting environment. Servlets can process the header information of XML-RPC requests, but they pass those requests' XML content to the XmlRpcServer's execute( ) method as InputStreams. (Optionally, they can include a username and password derived from basic authentication.) The execute( ) method returns a String containing the response, which the supporting environment then wraps with appropriate HTTP header information.

For example, the WebServer class wraps its calls to the XmlRpcServer's execute method as follows:

ServerInputStream sin = new ServerInputStream (input, contentLength);
byte result[] = xmlrpc.execute (sin, user, password);
output.write (httpversion.getBytes(  ));
output.write (ok);
output.write (server);
output.write (conclose);
output.write (ctype);
output.write (clength);
output.write (Integer.toString (result.length).getBytes(  ));
output.write (doubleNewline);
output.write (result);
output.flush (  );

WebServer takes a very basic approach, reading and writing the HTTP requests as streams of textual bytes. Within a servlet, developers can take advantage of a slightly higher level of abstraction, as shown in the Servlet that comes in the XML-RPC package:

public void doPost(HttpServletRequest req, HttpServletResponse res)
                throws ServletException, IOException  {
        byte[] result = xmlrpc.execute (req.getInputStream (  ));
        res.setContentType("text/xml");
        res.setContentLength (result.length);
        OutputStream output = res.getOutputStream(  );
        output.write (result);
        output.flush (  );
}

In either case, XmlRpcServer accepts an XML-RPC request as an XML document and returns an XML-RPC response document. The supporting environment has to handle the rest of the transaction.

The XmlRpcServer class also provides support for the AuthenticatedXmlRpcHandler interface. You can also pass information using a three-argument version of execute, which allows the XmlRpcServer class to send authentication information to the handler. In addition to the InputStream, you'll need to add the username and password strings. Because the authentication header, which includes both the username and password, is sent using base64 encoding and the servlet package doesn't provide a simple means of reaching the password, extracting the username and password requires a few extra steps. These steps are highlighted in bold in the following example:

public void doPost(HttpServletRequest req, HttpServletResponse res)
                throws ServletException, IOException  {
 
    //get authorization header from request
    String auth=req.getHeader("Authorization");
    if (!auth.toUpperCase(  ).startsWith("BASIC")) {
        throw ServletException("wrong kind of authorization!");
    }
 
    //get encoded username and password    
    String userPassEncoded=auth.substring(6);
 
    //create a base64 decoder using Sun utility class
    sun.misc.BASE64Decoder dec=new sun.misc.BASE64Decoder(  );
    String userPassDecoded=new String(dec.decodeBuffer(userPassEncoded));
 
    //split decoded username and password
    StringTokenizer userAndPass=new StringTokenizer(userPassDecoded,":");
    String username=userAndPass.nextToken(  );
    String password=userAndPass.nextToken(  );
 
    //send input stream, username, and password to xmlrpc.execute
        String result = xmlrpc.execute (req.getInputStream (  ), username, password);
        res.setContentType("text/xml");
        res.setContentLength (result.length (  ));
        PrintWriter writer = res.getWriter(  );
        writer.write (result);
        writer.flush (  );
}

The WebServer class already includes this decoding, providing a more transparent means of handling authenticated transactions.

Creating XML-RPC Handlers

Both WebServer and XmlRpcServer employ the same set of methods for registering and unregistering classes whose methods can be used through XML-RPC. Java classes whose methods accept the standard data types (described earlier in this chapter) can use the WebServer and XmlRpcServer classes' built-in logic for mapping XML-RPC calls to Java methods; other classes can be extended with a single execute( ) method for mapping XML-RPC requests to Java methods. In both cases, the addHandler( ) and removeHandler( ) methods are used the same way to add and remove classes that can handle XML-RPC methods.

Creating Handlers Using Automatic Registration

If the methods you want to use for processing XML-RPC requests are written so that they only accept and return the data types described, you can use the automatic registration process. The following code sample demonstrates these methods using a simple testing procedure that accepts two strings and returns a new string that concatenates the strings in the reverse order of how they were received:

public class testHandler {
 
        public String nameTester(String first, String last) {
                return "Reversed: " + last + ", " + first;
        }
}

Making the nameTester( ) method available as an XML-RPC procedure requires two steps. First, set up a web server to handle the HTTP transactions. Second, register this method with that server to make it available via XML-RPC. Once the server object has been created, the testHandler can be registered with the server using the addHandler( ) method. The server then accepts and processes requests for the method:

WebServer server = new WebServer(9876);
server.addHandler("test", new testHandler(  ));

The server examines the testHandler class and extracts its method signatures for mapping to XML-RPC requests. XML-RPC clients may now send requests for the test.nameTester method, sending two strings as parameters. They'll receive a single string back, which begins with the text "Reversed:" and then concatenates the strings in reverse order.

If that method needs to be disengaged at some later point, the removeHandler( ) method can be called:

server.removeHandler("test");

Now the test.nameTester( ) method is no longer available. Although turning XML-RPC methods on and off dynamically might create serious chaos for a lot of stable service-oriented applications, it can be very useful for managing XML-RPC methods in conjunction with some kind of control panel. Among other things, it lets you update the classes used to handle an XML-RPC request without having to stop and restart the XML-RPC server or servlet.

Creating Handlers Using Explicit Registration

If you prefer to manage the mappings between classes and methods more directly, you can register classes that implement the helma.xmlrpc.XmlRpcHandler interface. When the server encounters these classes, it defers to their mapping from XML-RPC method names and parameters to Java methods. Instead of trying to pass arguments directly to Java methods inside a class, the XML-RPC server passes the method name and parameters (as a Vector) to the execute method, the single method required by the XmlRpcHandler interface.

The default behavior of helma.xmlrpc handles most situations, but mapping methods directly is appropriate in a number of cases. You may have a set of classes you're retrofitting to XML-RPC that expect their arguments to arrive as objects rather than primitives. You would prefer to handle that packaging in a single method rather than by creating method-by-method front ends. You may want to hide the internal structures of your processing, a reasonable strategy when exchanging information with potential competitors. Finally, you may be creating methods that are minor variations on a theme, where the method name differentiates only a small change, such as the expected return value type.

Implementing the XmlRpcHandler interface requires only one method, execute( ). If a class implements the XmlRpcHandler interface, all XML-RPC calls are directed to the execute method, short-circuiting the automatic method mapping of the XmlRpcServer class. The execute( ) method takes a String and a Vector as arguments and returns an Object. The String is the method named by the XML-RPC request; the Vector contains all parameters that were sent with that request.

The execute( ) method can be used in several different ways. The logic inside the execute( ) method may just ship the Vector containing the parameters to other methods, leaving them to unpackage and process the parameters. Some execute( ) methods may emulate the helma.xmlrpc.XmlRpc package's own processing, mapping the method name and parameter set to appropriate handlers. More sophisticated execute( ) methods might read the parameters inside the Vector and create objects based on those parameters, which are then shipped to the appropriate target method. In any of these cases, the execute( ) method acts as a gateway.

Example 3-1 uses the execute( ) method to pass information to the nameTester( ) method used in the automatic registration example. Note that the nameTester( ) method is now private, accessible only through the execute( ) method. This isn't required, but it illustrates that the execute( ) method has taken over from the XML-RPC package's native registration.

Example 3-1: Creating an XML-RPC handler

import java.util.Vector;
import helma.xmlrpc.XmlRpcHandler;
 
public class testHandler implements XmlRpcHandler {
 
        public Object execute(String methodName, Vector parameters) throws java.lang.Exception {
                if (methodName=="nameTester") {
                        String first=(String) parameters.elementAt(0);
                        String last=(String) parameters.elementAt(1);
                        return nameTester(first, last);
                } else {
                        throw new Exception("No such method!");
                }
        }
 
        private String nameTester(String first, String last) {
                return "Reversed: " + last + ", " + first;
        }
 
}

Classes that implement the AuthenticatedXmlRpcHandler interface instead of XmlRpcHandler can process the username and password pairs from basic authentication as well as the method names and parameters, giving them additional gateway functionality. You'll want to implement a more sophisticated password checking mechanism--and perhaps move it to a separate class to be shared among various handlers. The testHandler class in Example 3-2, however, demonstrates how a handler supporting authentication might work.

Example 3-2: Creating an XML-RPC handler with authentication

import java.util.Vector;
import helma.xmlrpc.*;
 
public class testHandler implements AuthenticatedXmlRpcHandler {
 
//authenticated execute
        public Object execute(String methodName, Vector parameters, 
                      String username, String password) throws Exception {
                if (checkPassword(username, password)) {
                        return execute(methodName, parameters);
                } else {
                        throw new Exception("Unauthorized user");
                }
        }
//unauthenticated execute - called by authenticated
        protected Object execute(String methodName, Vector parameters) 
                         throws Exception {
                if (methodName=="nameTester") {
                        String first=(String) parameters.elementAt(0);
                        String last=(String) parameters.elementAt(1);
                        return nameTester(first, last);
                } else {
                        throw new Exception("No such method!");
                }
        }
 
e boolean checkPassword(String username, String password) {
        //password checking logic should be more sophisticated!
                if (username.equals(password)) {
                        return true;
                } else {
                        return false;
                }
        }
 
        private String nameTester(String first, String last) {
                return "Reversed: " + last + ", " + first;
        }
}

The XmlRpcServer class checks the type of class it works with and passes the correct set of parameters to the execute( ) method. Classes that implement XmlRpcHandler receive two parameters--method name and the Vector containing the parameters--while classes that implement AuthenticatedXmlRpcHandler receive four parameters: method name, the Vector containing parameters, username, and password.

Three Practical Examples

Although XML-RPC itself is very simple, it can be applied to a number of programming styles. The approach that most directly fits "remote procedure calls" is one that calls procedures across a network, as shown in the library function example in the next section. Procedure calls can also be used easily for client-to-server reporting, shown in an example that logs error reports from clients. Finally, just as Java itself uses get and set methods to manipulate the properties of objects, XML-RPC can extend that functionality to expose those methods to other systems on the network.

Library Functions

This example creates a simple Java library that performs mathematical calculations without any side effects on the server. The calculations performed--determining the areas of circles and squares--aren't very complex, but this same approach could be used for much more intensive algorithms. Although the average applet is certainly capable of calculating the area of a square on its own, many fields of computing rely on mathematical tools that demand extraordinary amounts of processing power.

As shown in Example 3-3, the AreaHandler class has two methods and no properties. The first method takes two arguments, length and width, and returns the area of a rectangle; the second method takes a single argument, radius, and returns the area of a circle.

Example 3-3: Library function implemented as XML-RPC handler
public class AreaHandler {
 
        public Double rectArea(double length, double width) {
        return new Double(length*width);
    }
 
    public Double circleArea(double radius) {
        double value=(radius*radius*Math.PI);
        return new Double (value);
    }
 
}

This code is run as an XML-RPC client on the server, with a simple client to pass it values. The code for the server builds on the generic code created in the previous section, using the helma.xmlrpc library's built-in WebServer class, as shown in Example 3-4.

Example 3-4: Hosting the area function

import java.io.IOException;
import helma.xmlrpc.WebServer;
import helma.xmlrpc.XmlRpc;
 
public class AreaServer {
 
    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java AreaServer [port]");
            System.exit(-1);
        }
 
        try {
 
            // Start the server, using built-in version
            System.out.println("Attempting to start XML-RPC Server...");
            WebServer server = new WebServer(Integer.parseInt(args[0]));
 
            System.out.println("Started successfully.");
 
            // Register our handler class as area
            server.addHandler("area", new AreaHandler(  ));
            System.out.println(
                "Registered AreaHandler class to area.");
 
            System.out.println("Now accepting requests. (Halt program to stop.)");
 
        } catch (IOException e) {
            System.out.println("Could not start server: " +
                e.getMessage(  ));
        }
    }
 
}

To start this server and the servers in the rest of the examples, call the Java runtime with the name of the class and an argument of 8899, in this case:

D:\xmlrpc\example1a>java AreaServer 8899

A simple client might call one of these methods and return its result. Most of the code in Example 3-5 provides an interface between the command line and the XML-RPC request itself, but it works well as test code.

Example 3-5: An XML-RPC client that calls the area function

import java.io.IOException;
import java.util.Vector;
import helma.xmlrpc.XmlRpc;
import helma.xmlrpc.XmlRpcClient;
import helma.xmlrpc.XmlRpcException;
 
public class AreaClient {
 
    public static void main(String args[]) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java AreaClient [radius]");
            System.exit(-1);
        }
 
        try {
            // Create the client, identifying the server
            XmlRpcClient client =
                new XmlRpcClient("http://localhost:8899/");
 
            // Create the request parameters using user input
            Vector params = new Vector(  );
            params.addElement(new Double(args[0]));
 
            // Issue a request
            Object result =
               client.execute("area.circleArea", params);
 
            // Report the results
            System.out.println("The area of the circle would be: " + result.toString(  ));
 
        } catch (IOException e) {
            System.out.println("IO Exception: " + e.getMessage(  ));
        } catch (XmlRpcException e) {
            System.out.println("Exception within XML-RPC: " + e.getMessage(  ));
        }
    }
 
}

When this XML-RPC client is run from the command line, it's possible to send a radius and receive back an area:

D:\xmlrpc\example1a>java AreaClient 3
The area of the circle would be: 28.274333882308138

Although this library works well in its current form, it's possible to add more flexibility to the way in which the arguments are sent by using a struct and named parameters instead of the direct approach. To make this work, the AreaHandler class needs a new method for processing the Hashtable used when structs are sent, and then needs to route requests to the appropriate method based on a type value in the struct. The new method is shown in Example 3-6.

Example 3-6: A method for routing XML-RPC requests

    public Double anyArea(Hashtable arguments) {
        Double value;
        value=new Double(0);
        String requestType=(String) arguments.get("type");
        if (requestType.equals("circle")) {
            Double radius=(Double) (arguments.get("radius"));
            value=circleArea(radius.doubleValue(  ));
        }
        if (requestType.equals("rectangle")) {
            Double length=(Double) (arguments.get("length"));
            Double width=(Double) (arguments.get("width"));
            value=rectArea(length.doubleValue(), width.doubleValue(  ));
        }
 
        return value;
    }

Most of the code handles conversions from the generic Object types stored in the Hashtable to the primitive types needed by the actual circleArea( ) and rectArea( ) methods. Although this code still returns the same results as the simpler methods it calls and adds an extra layer of processing overhead, you may find this approach useful if you need to create libraries that produce different results based on different types of named inputs.

Now the client code looks a little different because it has to assemble a Hashtable, not just simple parameters. Differences are highlighted in bold in Example 3-7.

Example 3-7: A client that calls the router

import java.io.IOException;
import java.util.Vector;
import java.util.Hashtable;
import helma.xmlrpc.XmlRpc;
import helma.xmlrpc.XmlRpcClient;
import helma.xmlrpc.XmlRpcException;
 
public class AreaClient {
 
    public static void main(String args[]) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java AreaClient [radius]");
            System.exit(-1);
        }
 
        try {
            // Create the client, identifying the server
            XmlRpcClient client =
                new XmlRpcClient("http://localhost:8899/");
 
            // Create a double from the user argument
            Double radius=new Double(args[0]);
 
            // Create a hashtable and add a circle request
            Hashtable requestHash = new Hashtable(  );
            requestHash.put("type", "circle");
            requestHash.put("radius", radius);
 
            // Create the request parameters using user input
            Vector params = new Vector(  );
            params.addElement(requestHash);
 
            // Issue a request
            Object result =
               client.execute("area.anyArea", params);
 
            // Report the results
            System.out.println("The area of the circle would be: " + result.toString(  ));
 
        } catch (IOException e) {
            System.out.println("IO Exception: " + e.getMessage(  ));
        } catch (XmlRpcException e) {
            System.out.println("Exception within XML-RPC: " + e.getMessage(  ));
        }
    }
 
}

The call and the results will look the same--users do not need to be aware of the extra flexibility they have available:

D:\xmlrpc\example1b>java AreaClient 5.6
The area of the circle would be: 98.5203456165759

Also, unless the circleArea( ) and rectArea( ) are changed to become private methods, direct XML-RPC requests for those methods will continue to work.

Although this struct approach is somewhat like implementing the XmlRpcHandler interface and directing XML-RPC requests yourself, it isn't nearly as demanding because you control how much you use this approach. You can redirect some methods, but let XmlRpcServer figure out simpler methods. You could also implement something very similar using a Java Vector/XML-RPC array to send information between client and server, relying on order rather than labeling.

Reporting

The procedures called by XML-RPC requests don't have to be library routines used to retrieve information. Clients may just send servers information that they record--perhaps in a database, just in a file, or even on the system console. The example in this section builds a simple logging application that collects client exceptions and records them to the system console. This kind of tiny application can be very useful for debugging distributed applications, giving you an easy way to centralize information from multiple systems running concurrently.

This application first requires the design of a static method that captures exceptions and reports them via XML-RPC to a central server. As shown in Example 3-8, the first parameter is the IP address of the client sending the request (that information isn't passed to the XML-RPC handler), and the second is a String containing the message from the exception.

Note that methods that process Java exceptions have to be static methods. In addition, because the report( ) method itself has some possible exceptions due to the XML-RPC call, the entire body of the method is contained inside a try/catch statement.

Example 3-8: Exception reporting XML-RPC client

import java.io.IOException;
import java.net.*;
import java.util.Vector;
import helma.xmlrpc.XmlRpc;
import helma.xmlrpc.XmlRpcClient;
import helma.xmlrpc.XmlRpcException;
 
public class XLogClient {
 
    public static void main(String args[]) {
        try {
            throw new Exception("help");
        } catch (Exception e) {
            report (e);
        }
    }
 
    public static void report(Exception eReport) {
        try {
            // Create the client, identifying the server
            XmlRpcClient client =
                new XmlRpcClient("http://192.168.124.14:8899/");
 
            //get local hostname and IP address
            InetAddress address=InetAddress.getLocalHost(  );
            String ipAddress=address.toString(  );
 
            // Create the request parameters using user input
            Vector params = new Vector(  );
            params.addElement(ipAddress);
            params.addElement(eReport.getMessage(  ));
            // Issue a request
            Object result =
                client.execute("XLog.XLogReport", params);
 
            // Report the results - this is just for the example
            // In production, the 'ack' will be thrown away.
            // Alternatively, the log system could be more interactive
            // and the result might have meaning.
            System.out.println("Reponse was: " + result.toString(  ));
 
            //If we can't report to server, report locally
        } catch (IOException e) {
            System.out.println("IO Exception: " + e.getMessage(  ));
        } catch (XmlRpcException e) {
            System.out.println("Exception within XML-RPC: " + e.getMessage(  ));
        }
    }
}

The main( ) method in Example 3-8 is very short--it throws an exception with a message of "help" and then catches it, sending it to our report( ) method. The report( ) method sends the address of this system along with the exception message to the XML-RPC server. If something goes wrong with that transmission, the report( ) method prints its error messages to standard output.

The server that hosts this reporting system only needs to set up a web server and register one class for monitoring incoming exception reports. It builds on the same basic framework used for previous examples, as shown in Example 3-9.

Example 3-9: A host for the logging handler

import java.io.IOException;
import helma.xmlrpc.WebServer;
import helma.xmlrpc.XmlRpc;
 
public class XLogServer {
 
    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java AreaServer [port]");
            System.exit(-1);
        }
 
        try {
            // Start the server, using built-in version
            System.out.println("Attempting to start XML-RPC Server...");
            WebServer server = new WebServer(Integer.parseInt(args[0]));
 
            System.out.println("Started successfully.");
 
            // Register our handler class as area
            server.addHandler("XLog", new XLogHandler(  ));
            System.out.println(
                "Registered XLogHandler class to XLog.");
 
            System.out.println("Now accepting requests. (Halt program to stop.)");
 
        } catch (IOException e) {
            System.out.println("Could not start server: " +
                e.getMessage(  ));
        }
    }
}

For this demonstration, and often in practice, only a basic exception log handler is necessary. The handler defined in Example 3-10 just takes the addresses and messages and reports them to standard output.

Example 3-10: A simple XML-RPC handler for reporting messages

public class XLogHandler {
    public String XLogReport(String address, String message) {
        System.out.println("From: " + address);
        System.out.println("Message: " + message);
        return "ack";
    }
}

The results of the test are simple, but could be effective if they represented real crises in need of attention:

D:\xmlrpc\example2>java XLogServer 8899
Attempting to start XML-RPC Server...
Started successfully.
Registered XLogHandler class to XLog.
Now accepting requests. (Halt program to stop.)
From: javalab1/192.168.124.12
Message: help
From: javalab5/192.168.124.17
Message: help
From: javalab6/192.168.124.19
Message: help
From: javalab27/192.168.124.141
Message: help

More sophisticated handling might filter through the messages to flag especially important alerts or save the messages to a file, but the basic report( ) method and the server already provide a strong foundation for future development. Building a more sophisticated log tracking facility would involve sending the information to something more permanent (and searchable) than screen output, like a database or even a file.

A get and set Approach

Although the previous example featured a relatively active client and a passive server, XML-RPC can be used to create more controlling clients, as well. XML-RPC's procedural approach fits fairly well with the common JavaBeans approach of getProperty( ) and setProperty( ) methods, though simple JavaBeans doesn't work in XML-RPC. Why? The set methods return void, and all XML-RPC methods have to return a value of some kind. On a relatively fundamental level, XML-RPC and JavaBeans are mismatched.

It isn't that difficult to write an execute( ) method that does the mapping, or even to modify helma.xmlrpc to return an empty value on methods that return void. However, the current section illustrates a simpler approach, building a controlling client and a server that maintains state between requests. Although this pattern isn't very sophisticated, it can be combined with other patterns to build more sophisticated applications. XML-RPC could be used throughout those applications, or it could just be one part of many.

To satisfy XML-RPC's need for return values, this example returns the current value of the property from the server to the client. This is duplicate information to some extent, but at least that information might be useful to verify that the change was made. Empty strings would be slightly more efficient, but would still incur overhead to no benefit.

The current example uses XML-RPC client requests to get and set a value on a Java object. Although the example used here is simple, it isn't difficult to extend it into more complex terrain using the same basic framework.

The key to this example lies in the handler class, shown in Example 3-11, which supports the get and set of its own value property.

Example 3-11: An XML-RPC handler that manipulates a variable

public class GetSetHandler {
    protected int value;
 
    public GetSetHandler(int initialValue) {
        value=initialValue;
    }
 
    public int getValue(String requester) {
        return value;
    }
 
    public int setValue(String requester, int newValue) {
        value=newValue;
        return value;
    }
}

The value property here is an integer, and the framework looks much like a JavaBeans component, but with the added return values and arguments noted earlier.

As shown in Example 3-12, the server code is much like that used by earlier examples--this is just another handler, and state management is up to the handler, not the server code wrapping it. You don't need to create a static variable to host the handler object because the GetSetHandler object is bound to the XML-RPC handling code.

Example 3-12: XML-RPC host for GetSetHandler

import java.io.IOException;
import helma.xmlrpc.WebServer;
import helma.xmlrpc.XmlRpc;
 
public class GetSetServer {
 
    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java GetSetServer [port]");
            System.exit(-1);
        }
        try {
            // Start the server, using built-in version
            System.out.println("Attempting to start XML-RPC Server...");
            WebServer server = new WebServer(Integer.parseInt(args[0]));
 
            System.out.println("Started successfully.");
 
            // Register our handler class as area
            server.addHandler("getSet", new GetSetHandler(20));
            System.out.println(
                "Registered GetSetHandler class to getSet.");
 
            System.out.println("Now accepting requests. (Halt program to stop.)");
 
        } catch (IOException e) {
            System.out.println("Could not start server: " +
                e.getMessage(  ));
        }
    }
}

Example 3-13 shows the client used to test this code. It is more complex, largely because it needs to manage both get and set possibilities. In production code, most of this complexity can be ignored because programs calling functions are generally more predictable than human input. On the other hand, this interface can be very useful during the debugging cycle.

Example 3-13: Client for manipulating values on the server

import java.io.IOException;
import java.net.*;
import java.util.Vector;
import helma.xmlrpc.XmlRpc;
import helma.xmlrpc.XmlRpcClient;
import helma.xmlrpc.XmlRpcException;
 
public class GetSetClient {
 
    public static void main(String args[]) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java GetSetClient [get | set] [value]");
            System.exit(-1);
        }
 
      String getOrSet=new String(args[0]);
 
      if (!((getOrSet.equals("get")) || (getOrSet.equals("set")))) {
            System.out.println(
                "First argument must be get or set");
            System.exit(-1);
        }
 
        try {
            // Create the client, identifying the server
            XmlRpcClient client =
                new XmlRpcClient("http://localhost:8899/");
 
            //get local host IP address
        InetAddress address=InetAddress.getLocalHost(  );
        String ipAddress=address.toString(  );
 
            // Create the request parameters using user input
            Vector params = new Vector(  );
            params.addElement(ipAddress);
            if (getOrSet.equals("set")) {
            Integer newValue=new Integer(args[1]);
            params.addElement(newValue);
        }
 
            // Issue a request
        Object result=null;
            if (getOrSet.equals("set")) {
                result = client.execute("getSet.setValue", params);
        } else {
               result =  client.execute("getSet.getValue", params);
        }
 
            // Report the results
            System.out.println("The response was: " + result.toString(  ));
 
        } catch (IOException e) {
            System.out.println("IO Exception: " + e.getMessage(  ));
        } catch (XmlRpcException e) {
            System.out.println("Exception within XML-RPC: " + e.getMessage(  ));
        }
    }
}

After you start the server, you can test the implementation from the command line:

D:\xmlrpc\example3>java GetSetClient get
The response was: 20
D:\xmlrpc\example3>java GetSetClient set 21
The response was: 21
D:\xmlrpc\example3>java GetSetClient get
The response was: 21
D:\xmlrpc\example3>java GetSetClient set 200
The response was: 200
D:\xmlrpc\example3>java GetSetClient get
The response was: 200
D:\xmlrpc\example3>java GetSetClient set 750
The response was: 750
D:\xmlrpc\example3>java GetSetClient get
The response was: 750

The GetSetHandler object retains the last value it was set for, starting with the initial value of 20 it received when first initialized. Although the command line here is testing the setting on a single system, multiple systems have access to both setting and retrieving that value, which could make such a drop-box useful as a central point for information distribution, provided that security isn't that important an issue.

This kind of get-set mechanism can be used for all kinds of programming tasks, allowing clients to control server properties and behavior. It's a common feature in interfaces used to administer a wide variety of systems and can be used both to tweak values occasionally and to send long lists of orders that must be carried out over time. This can be an easy way to control Java systems from programs running in other environments because XML-RPC provides the glue and the get-set mechanism is a common pattern in Java programming.

Moving Toward Cross-Platform Peer-to-Peer

The three preceding examples have moved from clients using servers as remote processors to clients reporting information to servers to clients actually controlling servers. XML-RPC's basic client-server foundation makes exchanging information in a wide variety of different ways possible, and its variable structures are flexible enough that single methods can have different behaviors, depending on the kinds of parameters they are sent.

The flexibility you need to move from client-server XML-RPC to a more peer-to-peer model is already there, thanks to arrays and structs. Clients can send any quantity of information that the application can then pick through, and servers can return any quantity of information for the client. Some clients may be capable of sending more information than others; servers can send extra information to clients, understanding that only some of their targets will use the entire set of information. There's no need for clients to play a purely client role or for servers to play purely server roles. Every program can be both a client and a server, if and when that seems appropriate.

As long as that flexibility is sufficient for your needs, the helma.xmlrpc package can provide you with a foundation for communication with non-Java systems with which you can define the roles of client and server as you find appropriate.


1. The Common Object Request Broker Architecture (CORBA), is designed to facilitate large-scale exchanges of object information and processing between systems that may or may not use similar environments or languages.

  Contact Us | E-mail Us | Site Guide | About PerfectXML | Advertise ©2004 perfectxml.com. All rights reserved. | Privacy