This introduction is addressed to programmers who wish to write their own distributed applications in Java using the DistinctÒ ONC RPC/XDR Toolkit for JavaÔ. The sections in this programming guide provide an overview of the following topics.
You may need to refer to the reference part of this manual to understand parts of the examples given in this programmer’s guide.
This toolkit may be used to help you out in several different scenarios. Here are some:
This guide discusses how to write client server applications using the Distinct ONC RPC/XDR Toolkit for Java. It first explains how to write an ONC RPC client and then illustrates how a server is built in Java. It then explains how to pass RPCs through Internet firewalls and then describes some other concepts the understanding of which are fundamental to using this toolkit.
What comes with the Distinct ONC RPC/XDR for Java Toolkit?
The Distinct ONC RPC/XDR Toolkit for Java is a set of tools and libraries that enables you to write pure Java ONC RPC clients and servers. It consists of:
In this section we will illustrate how to use this toolkit to build a Java client. For our example we will use the scenario of an application programmer who wants to write a Java-based front end for an existing ONC RPC service. This front end is an RPC client that may be a stand-alone application or a Java Applet for use inside a Web browser. Both types of clients can be built using the Distinct ONC RPC/XDR Toolkit for Java.
When writing an ONC RPC application the first thing to do is to write an XDR interface definition file. This file describes the data types and the signature of your interface. Distinct ONC RPC/XDR for Java understands the XDR language as described in RFC 1832 (XDR: External Data Representation Standard). By common programming convention XDR files have a .x extension.
The XDR Interface Definition Language File
For the remainder of this guide we will work with the XDR interface definition language (IDL) file demo.x as shown below. It defines a simple service that returns a sequence of consecutive lines from a text:
%/* ***************************************
% * Distinct ONC RPC/XDR for
Java Example *
% ****************************************/
struct request {
int from;
int to;
};
struct result {
int number;
string line<>;
struct result
*next;
};
typedef result *res_list;
program DEMO_SERVER {
version DEMO_VERSION
{
res_list get_line(request) = 1;
} = 1;
} = 0x20000023;
This IDL file has 5 sections:
Although the functionality of the service might be obvious at this point, we show here its implementation in C to clarify any questions you may have. In this simple version the lines of text that can be returned are hard coded and there is no error processing done (e.g. if a client requests a non-existing range of lines).
#include <stdio.h>
#include <rpc/rpc.h>
#include "demo.h"
char *text[] = {
"This is
the first line of text",
"This is
the second line of text",
"This is
the third line.",
"This is
the 4th line",
"This is
the 5th line",
"This is
the 6th line.",
"This is
the 7th line.",
"This is
the 8th line."
};
res_list *get_line_1(request
*req)
{
static res_list
rl;
result *p,
*q;
result **n;
int c;
/* free
any dynamically allocated memory from previous requests
*/
p = rl;
while (p
!= NULL) {
q = p;
p = p->next;
free(q);
}
/* the
handling of the new request starts here*/
rl = NULL;
/* n
points to the pointer where the next list element is inserted
*/
n = &rl;
for (c =
req->from; c < req->to; c++) {
/* allocate the next element of the list */
*n = malloc(sizeof(result));
/* assign the line number
*/
(*n)->number = c;
/* assign the line (string)
*/
(*n)->line = text[c];
n = &((*n)->next);
*n = NULL;
}
return &rl;
}
To build the server binary with just the demo.x IDL file and the demo_server.c file above, run the standard ONC RPC rpcgen (for C) on demo.x and compile and link the generated files demo_svc.c, demo_xdr.c, demo.h, and demo_server.c into one native binary.
Once you have built the above server and have it up and running with the ONC RPC portmapper, you can proceed to build a Java stand-alone client that calls this server.
The following pages will take you through the steps needed to build the Java client. The first thing to do is to run the Jrpcgen provided with the Distinct ONC RPC/XDR for Java Toolkit on the XDR file. The Jrpcgen application translates XDR files into Java stubs. For a complete description of the Jrpcgen command please refer to the reference portion of this manual.
You run Jrpcgen on demo.x by typing:
>java Jrpcgen demo.x
Jrpcgen V2.0, (c) 1997-1998 Distinct
Corporation
demo.x
writing: request.java
writing: result.java
writing: res_list.java
writing: demo.java
The execution of jrpcgen creates four Java classes, and subsequently four files, that implement the client stub for calling the demo service described: There is one file per type definition for request.java, result.java, and res_list.java, and one main stub file called demo.java.
We will now take a look at the demo.java main stub file that has been generated.
/* **************************************
* Distinct ONC RPC/XDR for Java
Example *
****************************************/
import distinct.rpc.*;
import java.io.IOException;
import java.net.InetAddress;
/**
*This
class was automatically generated by Jrpcgen from the RPC/XDR file "demo.x"
<br>.
*
It defines the client interface to a server implementing the "demo" interface.
*/
public class demo extends JRPCClient {
/** Program
ID of the interface*/
public static final int DEMO_SERVER
= 0x20000023;
/** Interface
Version */
public static final int DEMO_VERSION
= 1;
/**
* Creates and connects an RPC client
for a server that implements the "demo" interface.
* Calls the remote Portmapper in order
to get the server.port
* @param host The host on which the
server resides.
* @param stream true for a TCP connection,
false for UDP.
* @exception RPCError When the calls
fail for any reason.
*/
public demo(InetAddress host,
boolean stream) throws RPCError {
super(host,
DEMO_SERVER, DEMO_VERSION, stream);
}
/**
*
Creates and connects an RPC client for a server that implements the "demo"
interface.
* The
client is connected to a server with a known port. (No interaction with
a portmapper)
*
@param host The host on which the server resides.
*
@param port The port on which the server listens.
*
@param stream True for a TCP connection, false for UDP.
*
@exception RPCError When the calls fail for any reason.
*/
public demo(InetAddress host,
int port, boolean stream) throws RPCError {
super(host,
DEMO_SERVER, DEMO_VERSION, port, stream);
}
/**
*
Creates an RPC client for a server that implements the "demo" interface.
*
It initializes it with an externally created protocol client object.
*
@param protocol The protocol object that implements the client connection.
*/
public demo(ClientGeneric protocol)
{
super(protocol);
}
public static final int get_line = 1;
/**
* Stub
method that invokes the server function "get_line" (version 1).
*/
public res_list get_line_1(request
arg) throws RPCError, IOException {
res_list
retval = new res_list();
GetClient().Call(get_line,
arg, retval);
return retval;
}
};
An instance of this demo stub class represents a client to the demo server. It maintains the complete necessary connection status. Without going into all details we can easily see that this class consists of a number of constants, three constructors, and a public method get_line_1() that has the same signature as get_line() in the XDR file. You can also see that the lines from the beginning of the XDR file (the three comment lines) were copied over to the Java source generated, without the leading % characters. This is because Jrpcgen generates comments that are understood by the javadoc auto documentation tool.
Like all main client-stub classes in Distinct ONC RPC/XDR for Java, the demo class is derived from the base class JRPCClient. This class provides the framework for calling RPC servers using the various possible protocols. Similar to the C binding of ONC RPC, the constants are the program's number and version as well as an ordinal number for each procedure. Of the three constructors the first (public demo(InetAddress host, boolean stream) throws RPCError) is probably the most commonly used one. We will use it in our application example. Finally, the get_line_1() method is what we have to invoke when we want to interact with the server. The _1 extension results from the fact that this is the implementation of version 1 of this RPC program. As with the C binding the version number from the XDR file is always appended with an underscore in front of it.
We now take a look into the result.java file, which is one of the type definition files that were generated.
/****************************************
* Distinct ONC RPC/XDR for Java
Example *
****************************************/
import distinct.rpc.*;
/**
* This
class was automatically generated by Jrpcgen from the RPC/XDR file demo.x.<br>
*result:
was
struct
*/
public class result implements
XDRType {
public int
number;
public String
line;
public result
next;
/**
* Encodes
an object of class result in compliance to RFC 1832 (XDR).
* @param
xdrs The XDR output stream.
*/
public void xdr_encode(XDRStream
xdrs)
{
xdrs.xdr_encode_int(number);
xdrs.xdr_encode_string(line);
xdrs.xdr_encode_boolean(next
!= null);
if (next
!= null)
next.xdr_encode(xdrs);
return;
}
/**
* Decodes
an object of class result in compliance to RFC 1832 (XDR).
* @param
xdrs The XDR input stream.
* @exception
RPCError When the call fails for any reason.
*/
public void xdr_decode(XDRStream
xdrs) throws RPCError
{
number =
xdrs.xdr_decode_int();
line = xdrs.xdr_decode_string();
next = null;
if (xdrs.xdr_decode_boolean())
{
next = new result();
next.xdr_decode(xdrs);
}
return;
}
};
Once again you can see that the lines from the beginning of the XDR file (the three comment lines) were copied over to the Java file generated. This happens for all files generated. The data members of the result class mirror the C like definition of the struct result in the XDR file.
The two encoding/decoding methods use the methods of the XDRStream class to encode/decode the data members. The linked-list structure of the result type is mapped in an XDR-typical manner to a recursive invocation guided by a boolean value that indicates a NULL pointer value (and thus, the end of the list).
In most cases the mapping of XDR types to Java types and
classes is quite obvious. For an overview of the complete mapping please
have a look at the following table:
| XDR Type | Java Type |
| (unsigned) int | int |
| (unsigned) long | int |
| (unsigned) short | short |
| (unsigned) char | char |
| (unsigned) hyper | long |
| float | float |
| double | double |
| bool | boolean |
| string | String |
| opaque | byte array |
| fixed length array | Java array |
| variable length array | Java array |
| optional data (pointer like *x) | Reference to an object of class x. If x is a basic type a special wrapper class XDRx that implements XDRType is used instead. |
| enum x | Class x implements XDRType with member variable int value and one constant int per enum constant. |
| struct x | Class x implements XDRType with member variables for each struct member. |
| union x | Class x implements XDRType with member variables for each union member including discriminant. No overlaying of members is supported (neither for type conversion nor for saving space). |
| typedef x y | Class x implements XDRType with member variable value of the redefined type y. |
Like all classes that are created by Jrpcgen from type definitions, result implements the XDRType interface. This interface defines the two methods xdr_encode() and xdr_decode() that are used by the stub implementation for marshalling and unmarshalling the parameter into and from an XDRStream object. Jrpcgen generates the necessary code to implement these methods for the given XDR type.
Typically, if you are using the RPC functionality of the Distinct ONC RPC/XDR for Java Toolkit, you will not have to bother with these methods. As with the C binding, the stub implementation hides all details of their usage. You just have to construct objects of the defined input parameter class and pass them to the stub method (in our case e.g. of class request to demo.get_line()) and you will receive a newly created reply object from the stub (in our case of class res_list). However, if you use the Distinct ONC RPC/XDR Toolkit just for encoding/decoding XDR data (e.g. from a file) you will have to call these methods directly in conjunction with an appropriate XDRStream object.
The other two type definition classes, request.java and res_list.java, are very similar and have exactly the same structure.
Now we are ready to write our first Distinct ONC RPC/XDR for Java application. Below we show the listing of a simple stand-alone Java application that uses the generated stubs to make some invocations of the demo server:
import java.net.*;
public class testapp {
static public
void main(String[] args) {
demo client; // this is the RPC client object
request req = new request();
try {
client = new demo(
InetAddress.getByName(args[0]), // the host
true); // use TCP
for (int i = 0; i < 8; i++)
for (int j = i + 1; j < 8; j++) {
req.from = i;
req.to = j;
System.out.println("from " + i + " to " + j);
res_list rl = client.get_line_1(req);
result res = rl.value;
while (res != null) {
System.out.println(res.number+":"+res.line);
res = res.next;
}
}
client.CloseClient();
}
catch (Exception e) {
System.out.println(e.getMessage());
}
...
}
}
Here we take a step by step look at this application:
If you have compiled the files and you run the program, the output will look like this:
>java testapp localhost
from 0 to 1
0:This is the first line of
text
from 0 to 2
0:This is the first line of
text
1:This is the second line of
text
from 0 to 3
0:This is the first line of
text
1:This is the second line of
text
2:This is the third line.
from 0 to 4
0:This is the first line of
text
1:This is the second line of
text
2:This is the third line.
3:This is the 4th line
from 0 to 5
...
If the output looks like this:
>java testapp localhost
Server not available.
This means that our program has caught an exception because we have most probably forgotten to start the demo server (in this case on localhost, our local machine).
In principle, you can use the Distinct ONC RPC/XDR for Java classes and stubs generated by Jrpcgen inside an applet as described for the stand-alone program above. But, please note that by default all Java-enabled browsers do not allow network connections to hosts other than the one an applet was loaded from. In order to connect your applet to arbitrary RPC servers (or to run an RPC server) inside a browser you have to undertake additional action to work around this restriction:
If both methods are not viable for your application or you have to connect to an RPC server that is protected by a firewall you might want to use the HTTP tunneling protocol that is described below in the section "Connecting Through a Firewall". With this unique feature of the Distinct ONC RPC/XDR for Java Toolkit you can easily separate the Web server and other application or database servers. You should be aware that you could expect performance degradation compared to the direct connection as described earlier.
Building a Server with Distinct ONC RPC/XDR for Java
This section illustrates how to build a Java server. To build a Java server, again we have to run Jrpcgen from the Distinct ONC RPC/XDR Toolkit for Java on the XDR file. This time you run Jrpcgen on demo.x with the -S option, telling it that it has to create the server stub as well. Thus, you type:
>java Jrpcgen -S demo.x
Jrpcgen V2.0, (c) 1997-1998 Distinct
Corporation
demo.x
writing: request.java
writing: result.java
writing: res_list.java
writing: demo.java
writing: demoServer.java
The execution of jrpcgen creates the same four Java classes as before (even if we do not need the client part demo.java this time) plus the additional server stub class named demoServer.java.
We will now take a look into the demoServer.java main stub file that was generated.
/* **************************************
* Distinct ONC RPC/XDR for Java
Example *
****************************************/
import distinct.rpc.*;
/**
* <b>DEMO_SERVER Server
Stub</b>
* This class was automatically
generated by Jrpcgen from the RPC/XDR file "demo.x".<br>
* demo: was interface
DEMO_SERVER
*/
abstract public class demoServer extends JRPCServer {
/** Interface Program
*/
public static final int DEMO_SERVER
= 0x20000023;
/** Interface Version
*/
public static final int DEMO_VERSION
= 1;
public static final int get_line = 1;
/**
* Constructor that creates
the RPC server that implements the "demo" interface.
* @exception RPCError
When the calls fail for any reason.
*/
public demoServer() throws RPCError
{
super(DEMO_SERVER,
DEMO_VERSION, true);
UnregisterServer();
RegisterServer(StartUDP(0),
false);
RegisterServer(StartTCP(0),
true);
}
/**
* Constructor that creates
the RPC server that implements the "demo" interface at a fixed port.
* @param port The port
on which the server listens.
* @exception RPCError
When the calls fails for any reason.
*/
public demoServer(int port) throws
RPCError {
super(DEMO_SERVER,
DEMO_VERSION, true);
UnregisterServer();
RegisterServer(StartUDP(port),
false);
RegisterServer(StartTCP(port),
true);
}
/**
* Dispatcher Routine
that interprets the call requests.
* @param proc The index
of the requested function.
* @param xin read the
input parameter from this XDR stream.
* @param xout write the
return parameter to this XDR stream.
* @return true, if the
function with the index proc can be served, false otherwise.
*/
synchronized public boolean DoCall(int
proc, XDRStream xin, XDRStream xout) {
try {
switch (proc) {
case 0: {
return true;
}
case get_line: {
request arg = new request();
arg.xdr_decode(xin);
res_list ret = get_line_1(arg);
ret.xdr_encode(xout);
return true;
}
default:
return false;
}
}
catch (Exception
e) {
return false;
}
}
/*
* Overwrite these abstract
server methods for implementing the server's functionality.
*/
abstract public res_list get_line_1(request
arg);
};
This stub class demoServer represents the frame for a server that implements the DEMO_SERVER interface described in demo.java. Similar to the client stub we see, this class consists of a number of constants, two constructors, and an abstract method get_line_1() that has the same signature as get_line() in the XDR file. You can also see that the lines from the beginning of the XDR file (the three comment lines) were copied over to the generated Java source without the leading % characters. This is because Jrpcgen generates comments that are understood by the javadoc auto documentation tool.
Like all server stub classes in Distinct ONC RPC/XDR for Java, the class demoServer is derived from the base class JRPCServer. This class provides the framework for implementing RPC servers using the various possible protocols. Again, the constants are the program's number and version and ordinal numbers for the remote procedure. The method DoCall() is the core of the server. It implements dispatching of the remotely called procedures depending on the given ordinal number and it calls marshalling and unmarshalling of the parameters. Finally, the abstract get_line_1() method is the procedure we have to implement for our demo server by overriding it in a derived class.
In Distinct ONC RPC/XDR for Java DoCall() and, consequently, all implemented remote procedures can be called concurrently by different server threads. In the Distinct ONC RPC/XDR for Java implementation of an ONC RPC server, each TCP client is served by its own thread. One additional thread handles all UDP requests.
Now we are ready to write our first RPC server in Java. Below is the listing of a simple Java application that uses the generated stub to implement the demo server:
public class MyServer extends demoServer {
static String text[] = {
"This is
the first line of text",
"This is
the second line of text",
"This is
the third line.",
"This is
the 4th line",
"This is
the 5th line",
"This is
the 6th line.",
"This is
the 7th line.",
"This is
the 8th line."
};
static public void main(String[]
args) {
try {
new MyServer ();
}
catch (RPCError
e) {
System.out.println(e.getMessage());
}
}
public MyServer() throws RPCError
{
super();
}
// here
we override and implement the real remote procedure
public res_list get_line_1(request
arg) {
res_list
rl = new res_list();
result n
= new result();
for (int c
= arg.from; c < arg.to; c++) {
// allocate
the next element in the list
n.next = new result();
n = n.next;
if (c == arg.from)
rl.value = n;
// assign the line number
n.number = c;
// assign the line (string)
n.line = text[c];
}
return rl;
}
}
This Java code implements the same simple server functionality as the C version shown earlier. It defines a new class named MyServer that is derived from the demoServer class and that finally implements the remote procedure get_line_1() in a straightforward manner. Compared to the implementation in C there are two differences worth noting:
If you are not using fixed server ports (see next section) you will need an instance of the ONC Portmapper running on each machine that hosts ONC RPC servers. It is required to bind RPC clients to servers. Basically, the Portmapper is a name server. Server ports of ONC RPC servers are chosen randomly. It is the responsibility of the Portmapper to tell the clients the port number of a server with a given program/version number. The Portmapper itself is a RPC server that listens on a well-known port (111) for these requests. Typically, the Portmapper is running as a demon in the background and it is usually provided by the standard ONC RPC infrastructure that is part of e.g. any network connected UNIX host (e.g. NFS relies on ONC RPC). However, if you are starting your RPC server on a machine that does not provide this infrastructure by default (e.g. all flavors of MS Windows) you have to make sure that a Portmapper is present to enable clients to connect to your new server.
The Distinct ONC RPC/XDR for Java Toolkit provides its own implementation of the Portmapper. This pure Java implementation of the Portmapper implements the portmap interface version 2. As the Portmapper uses a fixed port number there can be only one Portmapper instance per node. Therefore, any server that uses the Jrpcgen generated stub will first check, whether the system already provides a Portmapper, and if not, it will start its own Portmapper. Please note, that all ONC RPC servers that are subsequently started on that node will now also use this instance of the Portmapper. This means, if you shut down the Distinct ONC RPC/XDR for Java server that started the Portmapper, you will also loose the ability to bind new clients to the other, still running servers, as the portmapper that stores the binding information is gone. Connections that were established before the Portmapper went down are not affected, as the binding is established only once during the connection setup. You can avoid this by starting the portmapper manually before starting the server.
To check whether a Portmapper is really running you can use the standard ONC RPC rpcinfo tool with the -p <hostname> option. This should enumerate the servers registered with the portmapper of this host. If this tool reports an error or does not show the servers you were expecting, there is obviously a problem with the Portmapper.
If you have to disable the Portmapper autostart feature of Jrpcgen generated server stubs for some reason, you currently have to edit the xxxServer class. Simply change the third parameter of the invocation super(DEMO_SERVER, DEMO_VERSION, true); in the constructors to false and the server will not try to start a Portmapper.
Most RPC applications are designed for Intranets, and although RPC can be made to run over the Internet, the original specifications were not really designed for this. On the Internet connections over arbitrary ports (like those established by the RPC protocol) are often blocked by firewalls. Please ask your firewall administrator for the details of your network security.
Distinct ONC RPC/XDR for Java allows you to build servers and clients with well-known (fixed) server ports. Depending on the configuration of your firewalls this may be sufficient for you to access certain servers over the Internet.
The fact that RPC server ports are not known in advance makes it difficult to configure a firewall accordingly. When this turns out to be a problem it might help to be able to configure the used ports statically. Distinct ONC RPC/XDR for Java supports this additional feature for Java clients and servers. If you use fixed ports you will not need to run the portmapper application.
To connect a client using a fixed port you only have to make a small change to the initialization sequence. You have to use another constructor of the client class. In our example the demo class can be created using the following code:
client = new demo(
InetAddress.getByName(args[0]),
// the host
Integer.parseInt(args[1]),
// the port
true); //
use TCP
Now, the second command line parameter can be used to specify the known server port of the demo server.
Distinct ONC RPC/XDR for Java comes with a powerful mechanism that wraps RPC requests completely with standard HTTP transactions. The server side CGI script (RPCGw) together with a special protocol client (ClientHTTP) allows for transparent tunneling of the RPC protocol in HTTP. This mechanism allows executing arbitrary RPCs on the Internet where firewalls are blocking all protocols, such as RPC, that do not use fixed ports. It also allows you to call RPC servers that are not located on the same host as the web server but elsewhere in your LAN, from a Java applet. With this feature you can easily separate the Web server and other application or database servers on different machines.
RPCGw translates and executes RPCs that are encapsulated in HTTP requests. It runs as an add on to the Web server at the server site. It uses the standard CGI interface for communication with the Web server. RPCGw receives the RPC request encoded in an HTTP POST request from the ClientHTTP object on its standard input stream via the CGI interface. It decodes the request parameter (host, program number, version number, procedure number, and protocol type (TCP/UDP)), optionally checks an access control list, creates an RPC connection, and forwards the request to the RPC server via the standard RPC protocol. It then waits for the reply and sends the return parameters encapsulated in an HTTP reply (of mime type application/octet stream) via the Web server back to the original requestor. RPCGw is generic in the sense that it does not know about the server interfaces and the parameter types. It simply forwards all requests (if permitted by the access control list). This means, there is no need to adapt RPCGw to a specific RPC server interface; it will work for any interface.
RPCGw is provided in source code as it is a C program and as it should run on any common Web server architecture, it is hard to provide all the different binary formats. With the fairly portable sources and the simple Makefile you should be able to install it on any server architecture.
To install RPCGw you should first compile it for your server architecture. Please take a look into the Makefile provided. You have to customize the compile options for selecting the security policy (none or access control list) and the logging features (on/off and log filename). For compilation of RPCGw you will need the standard ONC RPC library (for all types of UNIX usually included in the standard distribution, for Win32 please check out the Distinct ONC RPC/XDR Toolkit for C developers). After compilation, copy the executable into the script directory of your Web server.
In order to preserve the integrity and the privacy of your LAN RPCGw uses a sophisticated access control list that allows you to specify exactly which procedure of which RPC program/version number can be executed on which host via which protocol. In addition it allows the logging of all RPC activities to a local file. If you have chosen to work with the access control list (highly recommended) use a text editor to create a file named rpcgw.cfg in the same directory. This file holds the access control list. It will be read by RPCGw each time it is invoked. Each line in this file has five entries delimited by white spaces (e.g. tabs): hostname (symbolic name), program(decimal number), version(decimal number), protocol(tcp or udp), procedure(decimal number). Each entry is exactly one string or the * wildcard character. Thus, a simple rpcgw.cfg file might look like this:
island 77 1 tcp 1
* 79 1 * *
Line one of the above example for an access control list allows procedure number 1 in an RPC service with program number 77, version number 1 on host named island via the TCP protocol to be called. Line two allows any procedure in an RPC service with program number 79, version number 1 on any host via TCP or UDP to be called. In general, '*' matches any parameter provided by the remote client. Other regular expressions are not recognized. In order not to harm the security of your local network you should be highly restrictive when creating the RPCGw access control list and allow only access to those services, hosts and procedures that are really required by the application and that cannot be abused by an unauthorized person. In general, please contact your local security manager before installing RPCGw on a Web server with high security priorities or that is visible to the outside world.
Building a Client that uses RPCGw
To create an RPC client object in a Java applet that calls the RPC server via HTTP and RPCGw use the following sequence in your code. First you create a ClientHTTP protocol object that knows how to access the server via RPCGw, and then you initialize the RPC client object (of class demo in our example) with this protocol object.
The following code sequence is nearly identical to the
previous client example. Only the client initialization is changed (the
gray part) to use a ClientHTTP protocol object instead of standard TCP
or UDP. The sample code given here assumes that the code is part of an
applet (otherwise getDocumentBase().getHost() would be undefined) but this
is not required. You may use HTTP tunneling also in stand-alone applications.
Also, please note, that the URL of the RPCGw script (in our case
/cgi-bin/RPCGw) may depend on the configuration of your Web server.
...
demo client;
// this is the RPC client object
request req
= new request();
try {
// Construct the HTTP protocol object
ClientHTTP Protocol = new ClientHTTP(getDocumentBase().getHost(), // my
Web-server
"/cgi-bin/rpcgw", // the URL of the RPCGw program
"island", // host of the RPC server
demo.DEMO_SERVER, // the program number
demo.DEMO_VERSION, // the version number
true); // use TCP ("false" for UDP)
// Initialize the RPC client object
client = new demo(Protocol);
for (int i = 0; i < 8; i++)
for (int j = i + 1; j < 8; j++) {
req.from = i;
req.to = j;
System.out.println("from " + i + " to " + j);
res_list rl = client.get_line_1(req);
result res = rl.value;
while (res != null) {
System.out.println(res.number+":"+res.line);
res = res.next;
}
}
client.CloseClient();
}
catch (Exception
e) {
System.out.println(e.getMessage());
}
...
This section discusses how to do certain things in the Distinct ONC RPC/XDR for Java environment. It includes information on how to authenticate, how to broadcast RPCs, how to run RPCs in batch mode and discusses server data flow, using XDR streams and error and timeout handling.
By default, Distinct ONC RPC/XDR for Java uses NULL authentication. However, this default can be changed at the client object by using setCredential() and setVerifier(). In the other direction the returned verifier from the server can be obtained after each call with getReturnedVerifier(). Please note that Distinct ONC RPC/XDR for Java (like the C binding) provides the data structures for transferring authentication data and the methods for setting, retrieving and checking this data. It does not provide protection against forged authentication data. Any security and access control features have to be implemented by your application. Below is a code sample that creates a Unix authentication (sometimes also called System authentication) and sets it as the new credential of this client. From now on each request will be sent with this authentication.
import java.net.*;
public class testapp {
static public
void main(String[] args) {
demo client; // this is the RPC client object
request req = new request();
try {
client = new demo(
InetAddress.getByName(args[0]), true);
// a new Unix authentication is created (UID
= GID = 0)
client.GetClient().setCredential(new AuthUnix(
(int)(System.currentTimeMillis()/1000),
"myhostname", 0, 0, new int[0]);
for (int i = 0; i < 8; i++)
for (int j = i + 1; j < 8; j++) {
req.from = i;
req.to = j;
System.out.println("from " + i + " to " + j);
res_list rl = client.get_line_1(req);
result res = rl.value;
while (res != null) {
System.out.println(res.number+":"+res.line);
res = res.next;
}
// Here it is assumed that the server returned
an authentication
// (typically of type short)
that is now used for any subsequent calls
client.GetClient().setCredential(client.GetClient().GetReturnedVerifier());
}
}
...
}
}
The class distinct.rpc.JRPCServer provides the DoAuth() method for handling authentication at the server side. Override this method if you want your server to check authentication (the default is no check). DoAuth() receives the credential and verifier provided by the client as well as the call identifier and returns the verifier that should be sent back with the reply to the calling client (simply return null for NULL authentication). It throws a distinct.rpc.RPCAuthError if it wants to signal an unsuccessful authentication. The following code fragment illustrates how DoAuth() can be used in the server code.
...
synchronized
public Auth DoAuth(int proc, Auth cred, Auth verf)throws RPCError {
if (cred.getFlavor() != JRPC.kAUTH_UNIX)
throw new RPCAuthError(JRPC.kAUTH_TOOWEAK);
// convert it to the correct type
AuthUnix unix_cred = new AuthUnix(cred);
if (unix_cred.getUid() != 0)
throw new RPCAuthError(JRPC.kAUTH_BADCRED);
// use the same authenticator as returned verifier
// usually you would use some shortcut here
return unix_cred;
}
...
This example implementation of DoAuth() checks for each call to see if it has a credential of type AuthUnix and if so if the user id is 0. If either condition is not fulfilled it throws a distinct.rpc.RPCAuthError exception in order to terminate any further processing of this call. The remote procedure itself is not called at all in this case.
If you need to use other types of authentication (e.g Kerberos style) you can implement it easily by deriving a new authentication class from the distinct.rpc.Auth base type.
As mentioned in the Authentication section above, new authentication types can be derived from the basic distinct.rpc.Auth type. Distinct ONC RPC/XDR for Java provides a standard implementation for advanced security with authentication of type AUTH_DES. Two versions of the AUTH_DES authentication are available. One version relies on Sun's Java Cryptography Extensions (JCE) version 1.2 and is only available for the US and Canada. The other version utilizes Cryptix IJCE 3.0.3 and can be used anywhere. Please refer to the vendors instructions on how to install and configure each cryptography package for your environment.
Distinct ONC RPC/XDR for Java contains two versions of the following files to support both versions of the AUTH_DES authentication.
Please note that using the Sun JCE 1.2 package requires the Java Runtime Environment (JRE) version 1.2 (also known as Java 2). In all other cases, Distinct ONC RPC/XDR for Java can be used with any JRE version 1.1 or higher.
Secure RPC Client
The following example illustrates the required steps to set up a connection to a Secure RPC server from a Java program.
import java.net.*;
public class testapp
{
static
public void main(String[] args) {
demo client; // this is the RPC client object
try {
// Get the key database
PublicKeyDB DB = new PublicKeyDB(new File("/etc/publickey"));
// Authenticate yourself by entering the password
if (!DB.doLogin(false))
System.exit(-1);
// Create the authenticator
AuthDes a = new AuthDes(DB.getNetname(),
DB.getPassword(),
"unix.wied@home",
DB,
1800);
// Construct the Secure RPC protocol object
ClientSecureRPC Protocol = new ClientSecureRPC(
InetAddress.getByName("localhost"),
demo.DEMO_SERVER,
demo.DEMO_VERSION,
true, // use TCP
a); // the authenticator
// Initialize the RPC client object
client = new demo(Protocol);
request req = new request();
for (int i = 0; i 8; i++)
for (int j = i + 1; j 8; j++) {
req.from = i;
req.to = j;
System.out.println("from " + i + " to " + j);
res_list rl = client.get_line_1(req);
result res = rl.value;
while (res != null) {
System.out.println(res.number+":"+res.line);
res = res.next;
}
}
}
catch (Exception e) { // an RPC error happened !
printStr(e.getMessage());
}
}
This is our standard example again. Initialization of the client happens in five steps.
In the server handling of secure RPC is even simpler. You just have
to create a “AuthDesServerDB” in quite the same way as we did it in the
previous section with the “AuthDes”
authenticator and to invoke it in the server’s “doAuth()” method to
check whether the incoming authenticators are valid. This is illustrated
in the sample code below.
...
public MyServer() throws RPCError, IOException {
super();
PublicKeyDB DB = new PublicKeyDB(new
File("/etc/publickey"));
if (!DB.doLogin(false))
System.exit(-1);
adb = new AuthDesServerDB(DB.getNetname(),
DB.getPassword(), DB);
}
...
synchronized public Auth DoAuth(int proc, Auth cred,
Auth verf) throws RPCAuthError {
AuthDes a = adb.checkAuth(cred,
verf);
SetClientData(a);
return a.getVerifier();
}
...
In the constructor again we create a “PublicKeyDB” object and we use
it to enter our netname and the password (this authenticates the owner
of the server). Then we create an
“AuthDesServerDB” object, that will store all the information necessary
to check the incoming authenticators (including protection against
replays).
In the “doAuth()” method (see also 5.1) we now add the code that performs
this check: basically an invocation of the “AuthDesServerDB.checkAuth()”
method. This call with either throw an
“RPCAuthError” exception or it will return the encoded authenticator
from the client, an object of class AuthDes. In the first case we are done,
as the Java RPC runtime will handle it and
send back a notification about the failed authentication. In the latter
we have to obtain the newly computed verifier that has to be send back
to the client by calling getVerifier() of the returned
“AuthDes” object. This verifier, another object of class “AuthDes”
is then returned from “checkAuth()” and send back by the Java RPC runtime.
In many cases you might also want to store
the reference to the “AuthDes” object or at least its netname also
with "JRPCServer.SetClientData()" for later usage in you server’s code.
It will be needed e.g. to identify the callers of
specific procedures.
NIS
In addition to “ClientSecureRPC” objects that are initialized from a
file or an URL, Java RPC also provides a class “ClientSecureRPCYP” that
obtains the same type of key information
dynamically from a NIS server (formerly called Yellow Pages). “ClientSecureRPCYP”
is a subclass of “ClientSecureRPC”, this means you can use a “ClientSecureRPCYP”
object exactly in
the same way an a “ClientSecureRPC” object. If you create a “ClientSecureRPCYP”
object you have to provide at least the domainname (a string) of the domain
you are in. The simple
constructor that just takes this parameter assumes that a “ypbind”
RPC server is running on the executing machine (typically the case on a
Unix machine). If you cannot rely on this, you might
want to use the second constructor that has an additional parameter:
the Internet address of a host that run a “ypbind” server (typically a
domino server or the host that also runs the RPC
server you want to connect to).
Key Generation
Typically key generation is a service that is provided by your superuser
and your operating system. However, for completeness and for providing
everything you need for a test environment,
Java RPC also comes with a small tool that can generate public/secret
keypairs. If you invoke the “KeyGenerator” Java stand-alone program with
a netname and a password as arguments, it
will generate a new keypair and print it in “etc/publickey” format
to standard output (see example below). In order to create a new user you
can now copy this output into the publickey
database file, URL, or YP map.
java KeyGenerator newuser dummypass
newuser b868798951c98d0c3eb5d7c936a6a3c77ed0dbb20d1f0e2b:7c76bc7c7740
47f270b6428c171e484e73311bce8f839d044606ad014106f5e2
RPC broadcast allows your application to broadcast a message to all computers on a network. Typically you would use it if you were trying to identify which servers are available. RPC broadcast runs over UDP only and always uses NULL authentication. RPC broadcast can handle many responses from all responding servers. It filters out all unsuccessful responses, therefore, if a version mismatch exists between the broadcaster and a remote service, the user of RPC broadcast will never know this. When using RPC broadcast keep in mind that all broadcast messages are sent to the portmapper. Thus, only services that register themselves with their portmapper are accessible via RPC broadcast.
In Distinct ONC RPC/XDR for Java broadcast calls are implemented by the static member function JRPCClient.BroadcastCall(). Syntax and semantics are very similar to the C binding. Here is an example of how this function may be used calling the demo server we used earlier.
import distinct.rpc.BroadcastHandler;
import distinct.rpc.XDRType;
import java.net.*;
public class testapp implements
BroadcastHandler {
// "BroadcastHandler.onReply()"
is the handler called for each reply packet.
public boolean
onReply(XDRType retval, InetAddress addr) {
result res = ((res_list)retval).value;
while (res != null) {
System.out.println(res.number+":"+res.line);
res = res.next;
}
return true; // return with the first answer
}
static public
void main(String[] args) {
testapp app = new testapp();
request req = new request();
res_list rl = new res_list();
try {
for (int i = 0; i < 8; i++)
for (int j = i + 1; j < 8; j++) {
req.from = i;
req.to = j;
System.out.println("from " + i + " to " + j);
demo.BroadcastCall( demo.DEMO_SERVER,
demo.DEMO_VERSION,
demo.get_line,
req, rl,
app);
}
}
catch (Exception e) {
System.out.println(e.getMessage());
}
...
}
}
The changes to the original client program are:
Usually RPC is synchronous, meaning that clients send a call message and wait for the server to reply by indicating that the call succeeded. But this implies that clients may be sitting idle while servers process calls. Therefore, in cases where the client does not require an acknowledgement for every message sent, RPC messages can be placed in a pipeline of calls to the desired server. This process is known as batching. When operating in batch mode, RPC is simply acting as a message passing system. It is possible to batch RPC calls when:
Distinct ONC RPC/XDR for Java provides two additional methods for the use of batched calls, one for the client and one for the server side. A client simply has to set the timeout value of the client object to the value of -1 in order to force all RPC calls to return immediately after sending the request message (by throwing a RPCTimeoutError exception). Use the distinct.rpc.setTimeout() method to modify the timeout value.
The distinct.rpc.JRPCServer class provides the IsBatched() method for handling batched calls at the server. Override this method if you want your server not to send any reply to a request. IsBatched() receives the call identifier and returns a boolean value. Depending on the call identifier it decides whether this call is one that uses the batched mode and if so it simply returns true. This tells Distinct ONC RPC/XDR for Java that there is no need to send any reply to the client.
Control and Data Flow on the Server
As we have seen so far, there are three methods that can be overloaded to implement an ONC RPC server's functionality: JRPCServer.DoAuth(), JRPCServer.DoCall(), and JRPCServer.IsBatched(). The server calls these functions in exactly this order to serve each call, meaning first it calls DoAuth() to check authentication, then it calls DoCall() to perform the remote procedure call itself, and finally, it checks with IsBatch() whether there is a need to send back a reply message. Simple RPC servers only override DoCall() and use defaults for DoAuth() and IsBatched(). You only have to override the latter two methods if you want to change this default.
In some cases you might want to pass data between these three server-specific methods, e.g. if you need authentication data when processing the remote procedure call itself. Distinct ONC RPC/XDR allows for this data flow by providing the JRPCServer.SetClientData() and JRPCServer.SetClientData() methods. With SetClientData() you can register any Java object and retrieve it later by calling getClientData(). The data is stored per-thread. This means one server thread (the entity that serves one client request) can store any data that has to be transferred between the three methods (JRPCServer.DoAuth(),JRPCServer.DoCall(),JRPCServer.IsBatched()) here. The data will no longer be available after the call to IsBatched().
XDRStream implements all the encoding/decoding methods that are required for encoding/decoding the Java basic types according to RFC 1832 (XDR). It also provides a simple memory management for the streamed data. XDRStream implements a dynamically growing buffer of bytes (in fact a queue). The constructors allow for the creation of an empty XDRStream(with default allocation size 1024), an empty XDRStream with user defined allocation size (just a possible optimization), or an XDRStream initialized with the content of an existing byte array (usually the start of the decoding procedure). The put_byte(), put_bytes(), and all xdr_encode_xxx() methods add bytes to the head of the queue, while the get_byte(), get_bytes(), and all xdr_decode_xxx() methods consume bytes from the tail. With get_length() and get_data() you can request the current length and content of the queue without changing its status. The reset() methods reset the XDRStream to an empty queue. dump() just prints the content of the queue in hex and ASCII to System.out (typically used for debugging).
When you want to use Distinct ONC RPC/XDR for Java just for reading or writing XDR encoded data you might want to use an XDRStream object and just manipulate the contents of its buffer. Alternatively you can derive your own XDRStream class that handles I/O automatically.
Deriving your own XDRStream Classes
All xdr_decode and xdr_encode methods for the basic types (and therefore also the methods created by Jrpcgen for user-defined XDR types) use the put_byte/s and get_byte/s methods for writing to and reading from the buffer. This means, you can derive your own XDRStream class by overriding just these methods for building an XDRStream that reads from and/or writes to any data source you like. Examples of classes derived from XDRStream are XDRInputStream, which reads from a standard Java InputStream (writing is undefined for this class), and XDROutputStream, which writes to a standard Java OutputStream (reading is undefined for this class). As an illustration, below is an implementation of a class named MyInputStream that basically does the same as the XDRInputStream class (only RPC specific time-out handling is omitted). It just provides a new constructor that takes the InputStream and overrides the get_byte/s() and the reset() methods.
import distinct.rpc.*;
import java.io.*;
public class MyXDRInputStream extends XDRStream {
InputStream input;
public MyXDRInputStream(InputStream
inp) {
input = inp;
}
public byte get_byte() throws
RPCError {
int b;
try {
b = input.read();
}
catch (IOException
e) {
throw new RPCError("I/O Error.");
}
if (b ==
-1)
throw new RPCError("I/O Error (EOF).");
return (byte)
(b & 0xff);
}
public byte[] get_bytes(int n)
throws RPCError {
byte b[]
= new byte[n];
int b_read;
int pos =
0;
try {
while (pos != n) {
b_read = input.read(b, pos, n - pos);
if (b_read == -1)
throw new RPCError("I/O Error (EOF).");
pos = pos + b_read;
}
}
catch (IOException
e) {
throw new RPCError("I/O Error.");
}
return b;
}
public void reset() {
try {
while (input.available() > 0)
input.skip(input.available());
}
catch (IOException
e) {}
}
}
Whenever asynchronous network protocols are designed, timeouts play an important role. Since setting a timeout for a response is the only way to determine whether your server is still alive, it is crucial to choose the right timeout value. If it is too short, you might think your server has crashed when it may just be working slowly under a heavy load. If the timeout is too long, you might waste important time waiting for something that will never happen.
RPC maintains timeout values for both types of communication protocols (UDP and TCP). Typically, it is more important to have a timeout in UDP sessions, as the protocol itself does not provide any error detection. In addition, as UDP does not guarantee message delivery at all, a retransmission feature is desirable for UDP clients. RPC implements both. TCP provides reliable streams and usually it is a good idea to rely on the TCP connection management to determine whether your server is still alive. But, as this does not protect your client from endlessly looping (or totally overloaded) servers, RPC also has a time-out mechanism for TCP connections.
By default, the RPC time out is set to 25 seconds (UDP retransmission time out is 5 seconds). When a client does not receive any byte of the reply message for more than 25 seconds it assumes the server has crashed and throws a timeout RPCError exception. You can change this time-out value by calling JRPCClient.SetTimeout() on you client object, specifying the new time-out value in milliseconds (a value of 0 means no timeout at all).
Whenever the Distinct ONC RPC/XDR for Java runtime detects
a non-recoverable error in a client call it will throw an exception. Usually
this will be an IOException when the problem
is directly related to low-level I/O or an RPCError
when the problem has a close relation to the RPC protocol. For details
about the problem please look into the message string of the particular
exception. Once you have received an exception, the status of the client
object is undefined. Therefore, it is important to reestablish the connection
by creating a new client object before trying to call the server again.
| Exception | Reason |
| RPCAuthError | An authentication error occurred |
| RPCDecodeError | A problem such as an unexpected EOF has occurred during XDR decoding |
| RPCServerError | An RPC server is not available or not registered with the Portmapper |
| RPCTimeoutError | An RPC blocked longer than the timeout value of the client |
| RPCError | For all other errors |