Credits
All sample source code on this site was generated from Java source using Java2Html.
The original idea for the CGI server functionality was proposed by Robert Harder in Colorado, USA and refined in a series of follow up email exchanges. Thanks Robert.
For the impatient
I know, I know. You're writing code to a deadline and don't have time for waffle. Fine, I get it. This page describes all the possible ways to use RoX. The easiest way to use Rox on the client side is to use the dynamic proxy based API. A similar API exists on the server side with options for both synchronous and asynchronous request handling.
Introduction
For the purposes of the examples listed here, we'll assume that the server exposes the following XML-RPC methods:
Method Name | Parameters | Returns | Notes |
---|---|---|---|
example.sum | An array of integers | An integer | Returns the sum of the integers in the list provided. |
example.getDate | None | A date | Returns the server's current date and example. |
example.getInfo | A boolean | A struct or a string | If the input parameter is true, returns a struct with two keys, 'info' (a string) and 'version' (a double). If the input parameter is false, returns a double with the value that would be returned under 'version'. This is a bit contrived and really only exists to illustrate handling structs. |
Calling methods
The simplest example invokes a remote method without any parameters and ignores the
return code. The example below uses the synchronous client API. execute()
blocks until the response is available (just like standard Java call-semantics).
XmlRpcClient client = new XmlRpcClient(new URL(url));
client.execute("example.getDate", null);
System.out.println("getDate() returned");
Most of the time, though, you're interested in passing parameters and checking return values. The
example below builds on the previous example and illustrates a return type. We've skipped straight
to complex types. In this case the return value is an XML-RPC <struct>
. Simple
types (like ints and strings) are returned as instances of the "natural" Java class (java.lang.Integer
and java.lang.String
in this case). By default, structs are unpacked into java.util.Map
instances (although it's possible to get back something more useful, more later).
XmlRpcClient client = new XmlRpcClient(new URL(url));
Map rsp = (Map)client.execute("example.getDate", null);
System.out.println("getDate() returned with");
System.out.println(" today is "+rsp.get("today"));
System.out.println(" info: "+rsp.get("info"));
We're still not passing any parameters. Let's rectify that right now and call a method that computes a sum over a list of integers. The example below illustrates a few new things:
- Parameters are passed as an array of type
java.lang.Object
. This array can be empty and it can benull
. - There's an example of a simple type in here (the return type is an int).
- We're using a
java.util.List
as a parameter (which is marshalled as an XML-RPC<array>
).
List list = new ArrayList();
list.add(new Integer(2));
list.add(new Integer(3));
Integer sum = (Integer)client.execute("example.sum", new Object[]{list});
System.out.println("sum() over "+list+" = "+sum);
So far so good. We hinted earlier that it's possible to get back something more useful than a java.util.Map
.
Map's are all very good and well but dealing with them tends to be painful (there's a lot of casting) and error-prone
(typos will get you and the value in a map may not be of the type you're expecting). The example below defines
a simple Java "struct" called TimeInfo
. Notice that the java.lang.Class
instance for
TimeInfo
is passed to an overloaded version of execute()
. This indicates that instead of
a java.util.Map
instance you'd like an instance of TimeInfo
back. This only applies
if the return value is a <struct>
(if it isn't then the additional java.lang.Class
parameter is simply ignored).
import java.util.Date;
public class TimeInfo {
public Date today;
public String info;
public String toString() {
return "TimeInfo[today=" + today + ", info=" + info + "]";
}
}
XmlRpcClient client = new XmlRpcClient(new URL(url));
TimeInfo rsp = (TimeInfo)client.execute("example.getDate", null, TimeInfo.class);
System.out.println("getDate() returned with");
System.out.println(" today is "+rsp.today);
System.out.println(" info: "+rsp.info);
If you're still paying attention then the question on your mind is probably something like "How does RoX initialize
the TimeInfo
instance?"
Actually, if you're really paying attention then the answer is probably self-evident: it looks for public fields named after the members in the XML-RPC struct (with a little massaging). RoX will actually do even more than that. It will also try to match member names to public getter and setter methods (for more details see the discussion on unmarshalling).
So far all of our calls have been synchronous. If you expect the remote call to take some time to complete and you have other things for your local thread to be doing then you can ask Rox to execute a call on your behalf and call you back when the result is available.
Doing this is as easy as implementing the AsynchronousResponseHandler
interface and using
another overloaded version of execute()
. This is illustrated in the next example.
public class AsyncClient implements AsynchronousResponseHandler {
public void handleResponse(RpcCall call,
RpcResponse response, RpcResponseContext context) {
TimeInfo timeinfo = (TimeInfo) response.getReturnValue();
System.out.println("getDate() returned with");
System.out.println(" today is " + timeinfo.today);
System.out.println(" info: " + timeinfo.info);
}
public void handleException(RpcCall call, Throwable e, RpcResponseContext context) {
e.printStackTrace();
}
}
XmlRpcClient client = new XmlRpcClient(new URL(url));
client.execute("example.getDate", null, new AsyncClient());
In the spirit of leaving the best until last, there's one more way you can call an XML-RPC method using RoX. You'll need a common interface shared by the client and the server. That's not entirely true, it just needs to match in a contractual sense. Both sides don't need to physically share an interface. As long as the server-side interface is a superset of the client-side interface all will be well. So, assuming we have an interface as shown below:
public interface CommonInterface {
public int sum(int[] list);
public String getVersionInfo(boolean verbose);
public Date getDate();
}
The following will work:
XmlRpcClient client = new XmlRpcClient(new URL(url));
CommonInterface server = (CommonInterface) client.proxyObject(
"example.", CommonInterface.class);
System.out.println("Date: " + server.getDate());
System.out.println("Version: " + server.getVersionInfo(true));
int[] list = new int[]{1,2,3,4,5,6};
System.out.println("Sum of " + Arrays.toString(list) + ": " + server.sum(list));
If that didn't blow your skirt up then take a closer look. The call to proxyObject
returned
an object that implemented the interface passed in (or all public interfaces if the java.lang.Class
isn't an interface). This means you can typecast the returned object to any of these interfaces. But the
really nifty part is that if you invoke methods on that object they'll automatically be translated into
XML-RPC calls using the client instance the proxy was created by.
Yeah, take a closer look at that example.
Handling method calls on the server side
The server side of the picture shares many similarities
to the client side. The basic approach is based on handlers.
Handlers are registered with a URI (which may be null
)
and a method regular expression. These are used to map
incoming method calls to handlers.
public class Server implements SynchronousRequestHandler {
public RpcResponse handleRequest(RpcCall call, RpcCallContext context)
throws Exception {
// Handle the request
}
}
XmlRpcServer server = new XmlRpcServer(InetAddress.getByName(host), port);
server.registerHandler(null, "^example\\.", new Server());
server.start();
Similar marshalling options exist on the server side as exist for the client side of the picture.
When registering your handler it's possible to specify the java.lang.Class
to use for
each parameter (assuming it's a struct). If this isn't provided (as in the previous example), or
if a null
class is provided for a particular parameter then a java.util.Map
will be used (just like the client side of the picture). If this type-mapping is dynamic then
you can pass in an UnmarshallerAid
implementation instead
of directly providing a list of classes. This is useful if you want a single handler to deal with
a range of methods (remember that a regular expression is used to map calls to handlers).
The server side of the picture also includes asynchronous call support. This makes it possible to pass method calls to other application threads and process them when you next have capacity. The asynchronous call interface is passed the method call and a response channel. The latter is used to send back a response when one is available. As you can see in the example below, the only difference from the previous case is the handler.
public class Server implements AsynchronousRequestHandler {
public void handleRequest(RpcCall call, RpcCallContext context, ResponseChannel rspChannel)
throws Exception {
// Handle the request.
}
}
XmlRpcServer server = new XmlRpcServer(InetAddress.getByName(host), port);
server.registerHandler(null, "^example\\.", new Server());
server.start();
Of course what would be really cool would be if something could handle all that tedious
mapping between method names and the actual implementation. Well, if you call now, you'll
get all of that free, and more! Proxying plain old Java objects on the server side is
pretty straightforward. The example below illustrates how. The best part is that once
you've set yourself up like this, adding methods is just a case of writing them. The
mapping happens seamlessly provided you're reasonably careful about how you define
your methods (RoX does as much as it can to match calls up to methods but there's
only so much it can do, don't expect it to map a <list>
to your
own homegrown list type, at least not yet).
public class Server {
public int sum(int[] list) {
// Implementation
}
}
XmlRpcServer server = new XmlRpcServer(InetAddress.getByName(host), port);
server.registerProxyingHandler(null, "^example\\.(.*)", new Server());
server.start();
Things get marginally more complex if you want POJO proxying and asynchronous handlers on the server side, but not by much. The example below illustrates this.
public class Server implements AsynchronousRequestHandler, UnmarshallerAid,
RMIServerInterface {
private static final String NAME_PATTERN = "^example\\.(.*)";
private XmlRpcMethodProxy proxy;
public Server() {
this.proxy = new XmlRpcMethodProxy(NAME_PATTERN, this);
}
public void handleRequest(RpcCall call, RpcCallContext context, ResponseChannel rspChannel)
throws Exception {
// A deferred method call can be handed off to an application thread ...
DeferredMethodCall defCall = new DeferredMethodCall(this.proxy, call, rspChannel);
// ... and executed like this.
defCall.invoke();
}
public Class getType(String methodName, int index) {
return this.proxy.getType(methodName, index);
}
public FieldNameCodec getFieldNameCodec(String methodName ) {
// The default codec is almost always fine.
return null;
}
}
XmlRpcServer server = new XmlRpcServer(InetAddress.getByName(host), port);
Server handler = new Server();
server.registerHandler(null, Server.NAME_PATTERN, handler, handler);
server.start();
XML-RPC over HTTPS - Using SSL
With Java 1.4 Sun introduced native support for SSL in the form of JSSE.
RoX can take advantage of this if you want to secure your RPC communications.
Configuration is as per Sun's instructions regarding
configuring JSSE. Beyond that, using SSL with RoX is very simple. If you're
using RoX as a client just make sure the protocol in your URL is set to
https
instead of http
. If you're using RoX as
a server then you simply use a constructor with an additional boolean
parameter and pass in true
. There are a few other slightly more
fiddly bits if you want more control but that's it. Assuming JSSE has been
configured correctly, or if you're happy with an anonymous SSL session,
you're done.
It's a kind of magic
One last piece of magic before we're done.
This little party trick is proprietary (i.e. I made it up) and so it's not enabled by default since that would make Rox a non-compliant XML-RPC server implementation out of the box, and we wouldn't want that, would we?
The basic idea is pretty simple. Rox can, if you ask nicely, unpack HTTP GET requests that use the CGI parameter passing notation, and map these onto your XML-RPC method call handlers. Why would you want to do this? Well, often your XML-RPC method calls are actually pretty simple and XML can be pretty verbose. For example, adding two integers might logically look like this:
add(1, 2)but translated into an XML-RPC call it looks something like this
<?xml version="1.0"?> <methodCall?> <methodName?>add</methodName?> <params?> <param?> <value?><int?>1</int?></value?> </param?> <param?> <value?><int?>2</int?></value?> </param?> </params?> </methodCall?>
Sure, you rarely have to deal with this directly, that's why libraries like Rox exist. But you may not always have the luxury of an XML-RPC library. Perhaps none exists for the language you're using (unlikely but possible), or the environment you're working in is too contrained (perhaps you have limited resources) to support an XML-RPC library. Or maybe you're just lazy and simply shelling out to cURL or Wget appeals. Whatever the reasons, the functionality I'm describing means clients can invoke exactly the same XML-RPC server handler the above XML fragment would invoke using the following GET request:
GET http://localhost/add?1&2Using curl or wget is pretty straightforward:
curl http://localhost/add?1&2 wget http://localhost/add?1&2Sure, you still need to unpack the response, but for simple functions this is often pretty straightforward.
Enabling this functionality is a one-liner. The sample snippet below is exactly the same as that used in the section on handling methods on the server side, with a one line addtion (four actually, but two are empty lines and one is a comment to call out the change).
public class Server implements SynchronousRequestHandler {
public RpcResponse handleRequest(RpcCall call, RpcCallContext context)
throws Exception {
// Handle the request
}
}
XmlRpcServer server = new XmlRpcServer(InetAddress.getByName(host), port);
// Vive Le Difference!
server.registerRequestUnmarshaller(HttpConstants.Methods.GET, new CgiRequestUnmarshaller());
server.registerHandler(null, "^example\\.", new Server());
server.start();
A few points are worth calling out explicitly:
- This is not an either-or proposition. You can have your cake and eat it too. Registering the CGI request
unmarshaller doesn't mean you can't handle vanilla XML-RPC methods at the same time. It is possible to disable
the default XML-RPC unmarshaller by registering a
null
handler for the HTTP POST method. Why you'd want to do that, I'm not sure, but you can. - It's possible to roll your own handlers, take a look at the
com.flat502.rox.server.CgiRequestUnmarshaller
class as a starting point. And there's nothing preventing you from registering handlers for custom HTTP verbs. - You get pretty much all of Rox's unmarshalling and method dispatch magic for free. If you're using
the server side dynamic proxy based API then Rox will look the method up by
name and use it's signature to convert values to their appropriate type. So if you've defined an add method
with a signature like this
public int add(int a, int b)
then Rox will look up the method, discover it takes two integer arguments, and convert the two values in our original GET request:GET http://localhost/add?1&2
into integers before calling theadd
method.
Thanks for flying
That's it. There's not much more to say except enjoy.
List of examples
- AsyncClientDemo.java
- AsyncServerDemo.java
- RMIAsyncServerDemo.java
- RMIClientDemo.java
- RMISyncServerDemo.java
- SyncClientDemo.java
- SyncServerDemo.java
- SSLClientDemo.java
- SSLServerDemo.java
- CgiServerDemo.java
- ValidationSuiteServer.java
Supplemental source files