Saying Hello

If you’ve made it through these several pages of discussion, you probably are at least partially convinced that XML-RPC has some usefulness and that it might be the right solution for some of your development problems. To try to elaborate on XML-RPC, we now look at building some actual working Java code using XML-RPC. In the great tradition of programming, we start with a simple “Hello World” type program. We want to have our XML-RPC server register a handler. This handler takes in a Java String parameter, the user’s name, and returns “Hello” and the user’s name; for example, the method might return “Hello Brett” when invoked. Then we need a server to make our handler available for XML-RPC clients. Finally, we build 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, usually 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.

Getting XML-RPC Libraries

As we said earlier, 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 is http://www.xml-rpc.com. Sponsored by Userland (http://www.userland.com), this site has a public specification on XML-RPC, information on what data types 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://helma.at/hannes/xmlrpc.

On Hannes’s site is a description of the classes in his XML-RPC package as well as instructions on use. 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.

Note

The XML-RPC classes are packages within a zip file, xmlrpc-java.zip. You will need to extract from this archive all the source code within the xmlrpc-java/src/ directory. There is no included jar distribution, so manual compilation of these classes is required. Once compiled, you may want to jar the classes yourself for easy inclusion in your classpath.

The core distribution (which does not include the applet or regular expression examples in the downloaded archive) is made up of eight classes, all in the helma.xmlrpc package. They are XmlRpc, the core class; XmlRpcClient, which is used to connect to an XML-RPC server; XmlRpcServer, which is the server itself; XmlRpcHandler, which allows fine-grained control over XML encoding and processing; and several support and helper classes. XmlRpcException is the exception thrown by these classes; XmlRpcServlet demonstrates how to use a servlet as the HTTP response handler; WebServer is a lightweight HTTP server built specifically for handling XML-RPC requests; and Benchmark allows timing a roundtrip XML-RPC request using a specific SAX driver. Not included in the distribution, but required for operation, are the SAX classes (which you should have from earlier examples) and a SAX driver; in other words, you need a complete XML parser implementation that supports SAX. We continue to use Apache Xerces in our 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 class path. 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 our example.

Writing the Handler

The first thing we need to do is write the class and method we want to be invoked remotely. This is often 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, we do not need to write an XML-RPC handler, as one is included as part of the helma.xmlrpc.XmlRpcServer class. We only need to write a class with one or more methods that we register with the server.

It might surprise you to learn that creating a response handler requires no subclassing or other special treatment in our 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 10.1 lists all currently supported Java types that can be used in XML-RPC method signatures.

Table 10-1. XML-RPC Supported Java Types

XML-RPC Data Type

Java Data Type

int
int
                              boolean
boolean
string
java.lang.String
double
double
dateTime.iso8601
java.util.Date
struct
java.util.Hashtable[a]
array
java.util.Vector
                                 Chapter 10a
                                  
base64
byte[]

[a] Of course, the struct and array types must only contain other legal XML-RPC types.

Although this list includes only a small number of types, you will find that they handle most of the XML-RPC requests that can be made over a network. Because we only want to take in one String parameter, and return a String, our method fits these requirements. We can write our simple handler class now (shown in Example 10.1).

Example 10-1. Handler Class with Method to Be Invoked Remotely

/**
 * <b><code>HelloHandler</code></b> is a simple handler that can
 *   be registered with an XML-RPC server.
 * 
 * @version 1.0
 */
public class HelloHandler {

	/**
	 * <p>
	 * This will take in a <code>String</code> and return
	 *   a hello message to the user indicated.
	 * </p>
	 *
	 * @param name <code>String</code> name of person to say Hello to.
	 * @return <code>String</code> - hello message.
	 */
	public String sayHello(String name) {
		return "Hello " + name;
	}

}

This really is as simple as it seems. The method signature takes in and returns legal XML-RPC parameters, so we can safely register it with our XML-RPC server and know it will be callable via XML-RPC.

Writing the Server

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

We can create the skeleton for this class (shown in Example 10.2) now; we 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 10-2. Skeleton for Hello XML-RPC Server

import helma.xmlrpc.WebServer;

/**
 * <b><code>HelloServer</code></b> is a simple XML-RPC server
 *   that will make the <code>HelloHandler</code> class available
 *   for XML-RPC calls.
 * 
 * @version 1.0
 */
public class HelloServer {
  
    /**
	 * <p>
	 * Start up the XML-RPC server and register a handler.
	 * </p>
	 */
    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java HelloServer [port]");
            System.exit(-1);
        }
    
        // Start the server on specified port
    }
  
}

Before actually starting the server, we need to 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 at http://www.jclark.com. We instead request the Apache Xerces parser by specifying the SAX Parser implementation class to the XML-RPC engine.[14] This is done through the setDriver( ) method, a static method belonging to the XmlRpc class. This class underpins the WebServer class, but we must import it and use it directly to make this change in SAX drivers. The ClassNotFoundException is thrown by this method, so must be caught in case the driver class cannot be located in your class path at runtime. Add the necessary import statement and methods to your HelloServer class now:

import helma.xmlrpc.WebServer;
import helma.xmlrpc.XmlRpc;
...
    /**
	 * <p>
	 * Start up the XML-RPC server and register a handler.
	 * </p>
	 */
    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java 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, we are ready to add the main portion of our 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 we have been discussing can be instantiated by supplying it the port to listen to, and just that easily, our server is servicing XML-RPC requests. Although as of yet we have no classes available to be called, we 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. We 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 is:

               import java.io.IOException;

import helma.xmlrpc.WebServer;
import helma.xmlrpc.XmlRpc;
...
    /**
	 * <p>
	 * Start up the XML-RPC server and register a handler.
	 * </p>
	 */
    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java 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. We now need to add our handler class to the server so that it can receive requests.

One of the most significant differences between RMI and RPC is how 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 quite a bit 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 then tries to find a matching class and method that takes as input to that method parameter types that match the types within the RPC request. Once a match is made, the method is called, and the result is encoded and sent back to the client.

What this fairly long and somewhat complex discussion means is that 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 more closely can 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 how easily an RPC handler can be used, let’s register one in our example. The WebServer class allows the addition of a handler through the addHandler( ) method. This method takes as input a name 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 we look at using other methods, in the event that an instance should be shared instead of created by each client. In our current example, instantiating a new class is an acceptable solution. Let’s register our HelloHandler class to the name “hello.” We also add some additional status lines to let us see what is occurring in the server as it adds the handler:

/**
	 * <p>
	 * Start up the XML-RPC server and register a handler.
	 * </p>
	 */
    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println(
                "Usage: java 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])); 
            
            // Register our 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(  ));
        }            
    }

You can now recompile this source file and start up the server. Your output should look similar to Example 10.3.[15]

Example 10-3. Starting the HelloServer XML-RPC Server Class

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

Believe it or not, it is really that simple! We can write a client for our server, and then 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 extremely low, particularly compared to the complexity of using RMI. Read on, and see that creating a client is just as straightforward.

Writing the Client

With our server running and accepting requests, we have taken care of the hardest part of coding our XML-RPC application (believe it or not, that was the hard part!). Now we need to construct a simple client to call our 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 our client, we need this class as well as the XmlRpc class; our client must handle encoding of the request, so we must again set the SAX driver class to use with the setDriver( ) method. Let’s begin our client code with these required import statements, checking for an argument to pass as the parameter to our sayHello( ) method on the server, and some exception handling. Create the Java source file shown in Example 10.4 and save it as HelloClient.java.

Example 10-4. An XML-RPC Client

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

/**
 * <b><code>HelloClient</code></b> is a simple XML-RPC client
 *   that makes an XML-RPC request to <code>HelloServer</code>.
 * 
 * @version 1.0
 */
public class HelloClient {
  
    /**
	 * <p>
	 * Connect to the XML-RPC server and make a request.
	 * </p>
	 */  
    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 should seem simple and straightforward. To create an XML-RPC client, we 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. We can add this class to our list of imported classes, instantiate our client, and add the required exception handler:

               import java.net.MalformedURLException;

import helma.xmlrpc.XmlRpc;
import helma.xmlrpc.XmlRpcClient;
...
    /**
	 * <p>
	 * Connect to the XML-RPC server and make a request.
	 * </p>
	 */  
    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, we 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 our client and server; changing the port the server listens to requires changing the source code of our client! In the next chapter, we will look at how to address this issue.

Hopefully you are excited about the ease with which this client and our server are coming together, and how little work is required to make use of XML-RPC.

Still, our program is not of much use until it actually makes a request and receives a response. To encode the request, we must invoke the execute( ) method on our XmlRpcClient instance. This method takes in two parameters: the name of the class identifier and method to invoke, which is one single String parameter, and a Vector of the method parameters to pass in to the specified method. The class identifier is the name we registered to our HelloHandler class on the XML-RPC server; although this identifier can be the actual name of the class, it is often something more readable and meaningful to the client, and in our case it is “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 our 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 then 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 then 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 that the client is not trying to invoke a method or handler that does not exist, or sending in incorrect parameters.

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

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

import helma.xmlrpc.XmlRpc;
import helma.xmlrpc.XmlRpcClient;
import helma.xmlrpc.XmlRpcException;
...
    /**
	 * <p>
	 * Connect to the XML-RPC server and make a request.
	 * </p>
	 */  
    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(  ));
               
        }
    }
...

Surprisingly enough, that’s all that is required to make this work! You can compile this code, and then open a command shell for running our example code.

Talk to Me

Make sure that you have the XML-RPC classes and our example classes in your environment’s class path. You also need to confirm that Apache Xerces or your chosen SAX driver is in your class path 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:

D:prodJava and XMLWEB-INFclasses>start java HelloServer 8585
Starting XML-RPC Server...
Registered HelloHandler class to "hello"
Now accepting requests...

In 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 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 10.5) as the HelloServer receives your request, handles it, and returns the result of the sayHello( ) method, which is then printed by our client.

Example 10-5. Running the HelloClient Class

$ java HelloClient Brett
Response from server: Hello Brett

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, we can move on to a more realistic example. In the next section, we build a more practical and useful server, and take a look at what XML-RPC handlers more often look like. We then create a client (similar to our HelloClient) to test our new code.



[14] Currently there are no XML-RPC libraries that support SAX 2.0 and implement the XMLReader interface. It is expected that by late 2000, these updates will occur; 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.

[15] 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 10.3.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.152.87