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.
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.
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.
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.
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.
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.
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 Object
s!). 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.
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.
3.137.152.87