Book HomeJava and XML, 2nd Edition

11.2. Saying Hello

You are probably interested in seeing if XML-RPC might be the right solution for some of your development problems. To elaborate on XML-RPC, we'll now look at building some actual working Java code using XML-RPC. In the great tradition of programming, I'll start with a simple "Hello World" type program. I'll show you how to define an XML-RPC server, and have that server register a handler. This handler takes in a Java String parameter and the user's name, and returns "Hello" and the user's name; for example, the method might return "Hello Shirley" when invoked. Then you'll need to make this handler available for XML-RPC clients. Finally, I'll demonstrate building a simple client to connect to the server and request the method invocation.

In a practical case, the XML-RPC server and handler would be on one machine, probably a heavy-duty server, and the client on another machine, invoking the procedure calls remotely. However, if you don't have multiple machines available, you can still use the examples locally. Although this will be much faster than an actual client and server, you can still see how the pieces fit together and get a taste of XML-RPC.

11.2.1. XML-RPC Libraries

A lot of work has already gone into RPC, and more recently XML-RPC. Like using SAX, DOM, and JDOM for XML handling, there is no reason to reinvent the wheel when there are good, even exceptional, Java packages in existence for your desired purpose. The center for information about XML-RPC and links to libraries for Java as well as many other languages can be found at http://www.xmlrpc.com. Sponsored by Userland (http://www.userland.com), this site has a public specification on XML-RPC, information on what datatypes are supported, and some tutorials on XML-RPC use. Most importantly, it directs you to the XML-RPC package for Java. Following the link on the main page, you are directed to Hannes Wallnofer's site at http://classic.helma.at/hannes/xmlrpc/.

On Hannes's site is a description of the classes in his XML-RPC package and instructions. Download the archive file and expand the files into your development area or IDE. You should then be able to compile these classes; there is one Java servlet example that requires the servlet classes (servlet.jar for Servlet API 2.2). You can obtain these classes with the Tomcat servlet engine by pointing your web browser to http://jakarta.apache.org. If you do not wish to play with the servlet example, the servlet classes are not required for the programs in this chapter.

The core distribution (which does not include the applet or regular expression examples in the downloaded archive) is made up of thirteen classes, all in the helma.xmlrpc package. These are in a ready-to-use format in the lib/xmlrpc.jar file of the distribution. The classes within that distribution are detailed briefly in Table 11-1.

Table 11-1. The XML-RPC classes

Class

Purpose

XmlRpc

Core class allowing method calls on handlers by an XML-RPC server.

XmlRpcClient

Class for client to use for RPC communication over HTTP, including proxy and cookie support.

XmlRpcClientLite

Class for client to use when a less-featured HTTP client is needed (no cookies, proxy support).

XmlRpcServer

Class for servers to use to receive RPC calls.

XmlRpcServlet

Provides the functionality of XmlRpcServer in a servlet format.

XmlRpcProxyServlet

Acts as an XML-RPC servlet proxy.

XmlRpcHandler

Base interface for controlling XML-RPC interactions by handlers.

AuthenticatedXmlRpcHandler

Same as XmlRpcHandler, but allows for authentication.

Base64

Encodes and decodes between bytes and base 64 encoding characters.

Benchmark

Times roundtrip XML-RPC interactions for a specific SAX driver.

WebServer

A lightweight HTTP server for use by XML-RPC servers.

The SAX classes (from earlier examples) and a SAX driver are not included in the distribution, but they are required for operation. In other words, you need a complete XML parser implementation that supports SAX. I continue to use Apache Xerces in these examples, although the libraries support any SAX 1.0-compatible driver.

Once you have all the source files compiled, ensure that the XML-RPC classes, SAX classes, and your XML parser classes are all in your environment's classpath. This should have you ready to write your own custom code and start the process of "saying hello." Keep the XML-RPC source files handy, as looking at what is going on under the hood can aid in your understanding of the examples.

11.2.2. Writing the Handler

The first thing you need to do is write the class and method you want invoked remotely. This is usually called a handler. Beware, though, as the XML-RPC server mechanism that dispatches requests is also often called a handler; again, naming ambiguity rears its ugly head. A clearer distinction can be drawn as follows: an XML-RPC handler is a method or set of methods that takes an XML-RPC request, decodes its contents, and dispatches the request to a class and method. A response handler, or simply handler, is any method that can be invoked by an XML-RPC handler. With the XML-RPC libraries for Java, you do not need to write an XML-RPC handler because one is included as part of the helma.xmlrpc.XmlRpcServer class. You only need to write a class with one or more methods to register with the server.

It might surprise you to learn that creating a response handler requires no subclassing or other special treatment in your code. Any method can be invoked via XML-RPC as long as its parameter and return types are supported (able to be encoded) by XML-RPC. Table 11-2 lists all currently supported Java types that can be used in XML-RPC method signatures.

Table 11-2. Supported Java types in XML-RPC

XML-RPC datatype

Java type

int

int

boolean

boolean

string

String

double

double

dateTime.iso8601

Date

struct

Hashtable

array

Vector

base64

byte[]

nil

null

Although this list includes only a small number of types, they handle most of the XML-RPC requests made over a network. The method in this example only needs to take in a String (the name to say "hello" to), and return a String, and so fits these requirements. This is enough information to write a simple handler class, shown in Example 11-1.

Example 11-1. Handler class with remote method

package javaxml2;

public class HelloHandler {

	public String sayHello(String name) {
		return "Hello " + name;
	}
}

This is as simple as it seems. The method signature takes in and returns legal XML-RPC parameters, so you can safely register it with your (soon to be created) XML-RPC server and know it will be callable via XML-RPC.

11.2.3. Writing the Server

With your handler ready, you need to write a program to start up an XML-RPC server, listen for requests, and dispatch these requests to the handler. For this example, I use the helma.xmlrpc.WebServer class as the request handler. Although you could use a Java servlet, using this lightweight web server implementation allows you to avoid running a servlet engine on the XML-RPC server. I'll spend more time at the end of this chapter discussing servlets in the context of an XML-RPC server. For the server, the example allows the specification of a port to start the server on, and then has the server listen for XML-RPC requests until shut down. Finally, you need to register the class you just created with the server, and specify any other application-specific parameters to the server.

Create the skeleton for this class (shown in Example 11-2) now; you'll need to import the WebServer class and also ensure that a port number is given to the program on the command line when the server is started.

Example 11-2. Skeleton for XML-RPC server

package javaxml2;

import helma.xmlrpc.WebServer;

public class HelloServer {

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java javaxml2.HelloServer [port]");
            System.exit(-1);
        }
    
        // Start the server on specified port
    }
}

Before starting the server, specify the SAX driver for use in parsing and encoding XML. The default SAX driver for these libraries is James Clark's XP parser, available online at http://www.jclark.com. In this code, I instead request the Apache Xerces parser by specifying the SAX Parser implementation class[18] to the XML-RPC engine. This is done through the setDriver( ) method, a static method belonging to the XmlRpc class. This class underpins the WebServer class, but must be imported and used directly to make this change in SAX drivers. A ClassNotFoundException can be thrown by this method, so must be caught in case the driver class cannot be located in your classpath at runtime. Add the necessary import statement and methods to your HelloServer class now:

[18]Currently this XML-RPC library does not support SAX 2.0 or implement the XMLReader interface. As the Apache Xerces SAXParser class implements both the SAX 1.0 Parser interface and SAX 2.0 XMLReader interface, no code needs to be changed in the examples if SAX 2.0 updates are made to the libraries. However, if you are using a different vendor's parser, you may need to specify a SAX 2.0 class if the XML-RPC libraries are modified to use SAX 2.0.

package javaxml2;

import helma.xmlrpc.WebServer;
import helma.xmlrpc.XmlRpc;

public class HelloServer {

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java javaxml2.HelloServer [port]");
            System.exit(-1);
        }
    
        try {
            // Use the Apache Xerces SAX Driver
            XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");

            // Start the server
            
        } catch (ClassNotFoundException e) {
            System.out.println("Could not locate SAX Driver");
        }                    
    }
}

At this point, you are ready to add the main portion of the code, which creates the HTTP listener that services XML-RPC requests, and then registers some handler classes that are available for remote procedure calls. Creating the listener is very simple; the WebServer helper class I have been discussing can be instantiated by supplying it the port to listen to, and just that easily, the server is servicing XML-RPC requests. Although no classes are available to be called yet, you do have a working XML-RPC server. Let's add in the line to create and start the server, as well as a status line for display purposes. You'll also need to add another import statement and exception handler, this one for java.io.IOException. Because the server must start up on a port, it can throw an IOException if the port is inaccessible or if other problems occur in server startup. The modified code fragment looks like this:

package javaxml2;

import java.io.IOException;

import helma.xmlrpc.WebServer;
import helma.xmlrpc.XmlRpc;

public class HelloServer {

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java javaxml2.HelloServer [port]");
            System.exit(-1);
        }
    
        try {
            // Use the Apache Xerces SAX Driver
            XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");

            // Start the server
            System.out.println("Starting XML-RPC Server...");
            WebServer server = new WebServer(Integer.parseInt(args[0]));            
            
        } catch (ClassNotFoundException e) {
            System.out.println("Could not locate SAX Driver");
        } catch (IOException e) {
            System.out.println("Could not start server: " + 
                e.getMessage( ));
        }                                
    }
}

Compile this class and give it a try; it is completely functional, and should print out the status line and then pause, waiting for requests. You now need to add the handler class to the server so that it can receive requests.

One of the most significant differences between RMI and RPC is the way methods are made available. In RMI, a remote interface has the method signature for each remote method. If a method is implemented on the server class, but no matching signature is added to the remote interface, the new method cannot be invoked by an RMI client. This makes for a large amount of code modification and recompilation in the development of RMI classes. This process is quite a bit different, and is generally considered easier and more flexible, in RPC. When a request comes in to an RPC server, the request contains a set of parameters and a textual value, usually in the form "classname.methodname." This signifies to the RPC server that the requested method is in the class "classname" and is named "methodname." The RPC server tries to find a matching class and method that take parameter types that match the types within the RPC request as input. Once a match is made, the method is called, and the result is encoded and sent back to the client.

Thus, the method requested is never explicitly defined in the XML-RPC server, but rather in the request from the client. Only a class instance is registered with the XML-RPC server. You can add methods to that class, restart the XML-RPC server with no code changes (allowing it to register an updated class instance), and then immediately request the new methods within your client code. As long as you can determine and send the correct parameters to the server, the new methods are instantly accessible. This is one of the advantages of XML-RPC over RMI, in that it can more closely represent an API; there are no client stubs, skeletons, or interfaces that must be updated. If a method is added, the method signature can be published to the client community and used immediately.

Now that you've read about how easily an RPC handler can be used, I demonstrate how to register one in the HelloHandler example. The WebServer class allows the addition of a handler through the addHandler( ) method. This method takes a name as input to register the handler class to, and an instance of the handler class itself. This is typically accessed by instantiating a new class with its constructor (using the new keyword), although in the next section I'll look at using other methods, in the event that an instance should be shared instead of created by each client. In the current example, instantiating a new class is an acceptable solution. Register the HelloHandler class to the name "hello". You can include status lines to show what is occurring in the server as it adds the handler:

        try {
            // Use the Apache Xerces SAX Driver
            XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");

            // Start the server
            System.out.println("Starting XML-RPC Server...");
            WebServer server = new WebServer(Integer.parseInt(args[0]));            
            
            // Register the handler class
            server.addHandler("hello", new HelloHandler( ));
            System.out.println(
                "Registered HelloHandler class to \"hello\"");     

            System.out.println("Now accepting requests...");
            
        } catch (ClassNotFoundException e) {
            System.out.println("Could not locate SAX Driver");
        } catch (IOException e) {
            System.out.println("Could not start server: " + 
                e.getMessage( ));
        }      

Now recompile this source file and start up the server. Your output should look similar to Example 11-3.[19]

[19]If you are on a Unix machine, you must be logged in as the root user to start a service up on a port lower than 1024. To avoid these problems, consider using a higher numbered port, as shown in Example 11-3.

Example 11-3. Starting the server

$ java javaxml2.HelloServer 8585
Starting XML-RPC Server...
Registered HelloHandler class to "hello"
Now accepting requests...

It's that simple! You can now write a client for the server, and test communications across a network using XML-RPC. This is another advantage of XML-RPC; the barrier for entry into coding servers and clients is low, compared to the complexity of using RMI. Read on, and see creating a client is just as straightforward.

11.2.4. Writing the Client

With the server running and accepting requests, you done the hardest part of coding the XML-RPC application (believe it or not, that was the hard part!). Now you need to construct a simple client to call the sayHello( ) method remotely. This is made simple by using the helma.xmlrpc.XmlRpcClient . This class takes care of many of the details on the client side that its analogs, XmlRpcServer and WebServer, do on the server. To write your client, you need this class as well as the XmlRpc class; this client must handle encoding of the request, so again set the SAX driver class to use with the setDriver( ) method. Begin your client code with these required import statements, checking for an argument to pass as the parameter to the sayHello( ) method on the server, and some exception handling. Create the Java source file shown in Example 11-4 and save it as HelloClient.java.

Example 11-4. A client for the XML-RPC server

package javaxml2;

import helma.xmlrpc.XmlRpc;
import helma.xmlrpc.XmlRpcClient;

public class HelloClient {
 
    public static void main(String args[]) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java HelloClient [your name]");
            System.exit(-1);
        }
        
        try {
            // Use the Apache Xerces SAX Driver
            XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");

            // Specify the Server

            // Create request

            // Make a request and print the result
          
        } catch (ClassNotFoundException e) {
            System.out.println("Could not locate SAX Driver");
        }        
    } 
}

As with the rest of the code in this chapter, this is simple and straightforward. To create an XML-RPC client, you need to instantiate the XmlRpcClient class, which requires the hostname of the XML-RPC server to connect to. This should be a complete URL, including the http:// protocol prefix. In creating the client, a java.net.MalformedURLException can be thrown when this URL is in an unacceptable format. You can add this class to the list of imported classes, instantiate the client, and add the required exception handler:

package javaxml2;

import java.net.MalformedURLException;

import helma.xmlrpc.XmlRpc;
import helma.xmlrpc.XmlRpcClient;

public class HelloClient {
 
    public static void main(String args[]) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java HelloClient [your name]");
            System.exit(-1);
        }
        
        try {
            // Use the Apache Xerces SAX Driver
            XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");
            
            // Specify the server
            XmlRpcClient client = 
                new XmlRpcClient("http://localhost:8585/");  

            // Create request

            // Make a request and print the result          
          
        } catch (ClassNotFoundException e) {
            System.out.println("Could not locate SAX Driver");
        } catch (MalformedURLException e) {
            System.out.println(
                "Incorrect URL for XML-RPC server format: " + 
                e.getMessage( ));
        }        
    } 
}

Although no actual RPC calls are being made, you now have a fully functional client application. You can compile and run this application, although you won't see any activity, as no connection is made until a request is initiated.

WARNING: Make sure you use the port number in your source code that you plan to specify to the server when you start it up. Obviously, this is a poor way to implement connectivity between the client and server; changing the port the server listens to requires changing the source code of our client! In your own applications, make this a user-defined variable; I've kept it simple for example purposes.

The ease with which this client and our server come together is impressive. Still, this program is not of much use until it actually makes a request and receives a response. To encode the request, invoke the execute( ) method on your XmlRpcClient instance. This method takes in two parameters: the name of the class identifier and method to invoke, which is a single String parameter, and a Vector containing the method parameters to pass in to the specified method. The class identifier is the name you registered to the HelloHandler class on the XML-RPC server; this identifier can be the actual name of the class, but it is often something more readable and meaningful to the client, and in this case it was "hello". The name of the method to invoke is appended to this, separated from the class identifier with a period, in the form [class identifier].[method name]. The parameters must be in the form of a Java Vector, and should include any parameter objects that are needed by the specified method. In the simple sayHello( ) method, this is a String with the name of the user, which should have been specified on the command line.

Once the XML-RPC client encodes this request, it sends the request to the XML-RPC server. The server locates the class that matches the request's class identifier, and looks for a matching method name. If a matching method name is found, the parameter types for the method are compared with the parameters in the request. If a match occurs, the method is executed. If multiple methods are found with the same name, the parameters determine which method is invoked; this process allows normal Java overloading to occur in the handler classes. The result of the method invocation is encoded by the XML-RPC server, and sent back to the client as a Java Object (which in turn could be a Vector of Objects!). This result can be cast to the appropriate Java type, and used in the client normally. If a matching class identifier/method/parameter signature is not found, an XmlRpcException is thrown back to the client. This ensures the client is not trying to invoke a method or handler that does not exist, or sending incorrect parameters.

All this happens with a few additional lines of Java code. You must import the XmlRpcException class, as well as java.io.IOException; the latter is thrown when communication between the client and server causes error conditions. You can then add the Vector class and instantiate it, adding to it a single String parameter. This allows your code to invoke the execute( ) method with the name of the handler, the method to call, and its parameters; the result of this call is cast to a String, which is printed out to the screen. In this example, the local machine is running the XML-RPC server on port 8585:

package javaxml2;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Vector;

import helma.xmlrpc.XmlRpc;
import helma.xmlrpc.XmlRpcClient;
import helma.xmlrpc.XmlRpcException;

public class HelloClient {
 
    public static void main(String args[]) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java HelloClient [your name]");
            System.exit(-1);
        }
        
        try {
            // Use the Apache Xerces SAX Driver
            XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");
            
            // Specify the server
            XmlRpcClient client = 
                new XmlRpcClient("http://localhost:8585/");  
                
            // Create request  
            Vector params = new Vector( );            
            params.addElement(args[0]);
            
            // Make a request and print the result          
            String result = 
               (String)client.execute("hello.sayHello", params);
        
            System.out.println("Response from server: " + result);
          
        } catch (ClassNotFoundException e) {
            System.out.println("Could not locate SAX Driver");
        } catch (MalformedURLException e) {
            System.out.println(
                "Incorrect URL for XML-RPC server format: " + 
                e.getMessage( ));
        } catch (XmlRpcException e) {
            System.out.println("XML-RPC Exception: " + e.getMessage( ));
        } catch (IOException e) {
            System.out.println("IO Exception: " + e.getMessage( ));
        }        
    } 
}

That's all that is required to make this work. Now compile your source code and open a command shell for running the example.

11.2.5. Talk to Me

Make sure that you have the XML-RPC classes and your example classes in your environment's classpath. Also, confirm that Apache Xerces or your chosen SAX driver is in your classpath and accessible, as the examples must load these classes for parsing. Once that is set up, start the HelloServer class by giving it a port number. On Windows, use the start command to start the server in a separate process:

c:\javaxml2\build>start java javaxml2.HelloServer 8585
Starting XML-RPC Server...
Registered HelloHandler class to "hello"
Now accepting requests...

On Unix, use the background processing command (&) to make sure you can run your client as well (or open another terminal window and duplicate your environment settings):

$ java javaxml2.HelloServer &
Starting XML-RPC Server...
Registered HelloHandler class to "hello"
Now accepting requests...

You can then run your client by specifying your name to the program as a command-line argument. You should quickly see a response (similar to that shown in Example 11-5) as the HelloServer receives your client's request, handles it, and returns the result of the sayHello( ) method, which is then printed by the client.

Example 11-5. Running the HelloClient class

$ java javaxml2.HelloClient Leigh
Response from server: Hello Leigh

You have just seen XML-RPC in action. Certainly this is not a particularly useful example, but it should have given you an idea of the basics and shown you the simplicity of coding an XML-RPC server and client in Java. With these fundamentals, I want to move on to a more realistic example. In the next section, I'll show you how to build a more useful server, and take a look at what XML-RPC handlers often look like. I'll then demonstrate creating a client (similar to our HelloClient) to test the new code.



Library Navigation Links

Copyright © 2002 O'Reilly & Associates. All rights reserved.