Chapter 9. Mobile code

  • Outline—How it works—Uses—Security considerations—Setup—HTTP servers—Other protocols—Deployment—Downloading the client

In this chapter

RMI supports the remarkable facility of code mobility. You can send an object to a target even if the target doesn't have the code (the class file). While code mobility is an important feature, it can also prove difficult to implement if you are new to RMI. This chapter describes how code mobility works and why it is useful. It also provides information for implementing and deploying mobile code, along with sample code that highlights the two most common problems.

Outline

An RMI server or client can be configured with a codebase—a list of one or more URLs, each of which specifies a global location for Java class files. RMI transmits the codebase associated with each transmitted class. The receiving end can automatically use this information to retrieve any classes it doesn't have installed locally.

Figure 9.1 illustrates classes being downloaded dynamically from an HTTP server on request by a client.

Code mobility

Figure 9.1. Code mobility

The codebase associated with a class is (a) the value, if any, of the URL from which it was loaded by RMIClassLoader.loadClass, otherwise (b) the value, if any, of the Java system property java.rmi.server.codebase in the JVM which last loaded it from its CLASSPATH.

How code mobility works

As we have seen in earlier chapters, RMI uses Java Serialization to transport method parameters from the client to the server, and to transport return values and exceptions from the server to the client. The process of packaging at the sender, prior to actual transmission, is called marshalling, and the process of unpacking at the receiver, on receipt of a transmission, is called unmarshalling.

When marshalling an object—specifically, when RMI's implementation of ObjectOutputStream.annotateClass is called—RMI annotates the marshal stream with the codebase information for the class, if any. When unmarshalling an object, if its class is not available locally and if the stream has been annotated with codebase information for that class, RMI attempts to use the codebase information to download the class dynamically.

Format of a codebase

A codebase is a list of one or more URLs separated by spaces. Each item in the list is a standard URL, of the form

protocol://host[:port]/file

where:

  • protocol is the URL protocol, usually http for RMIcodebases

  • host specifies the location of the resource within the network

  • the optional port specifies the TCP/IP port at which the host service is listening, and defaults to the standard port for the protocol (e.g. 21 for FTP, 80 for HTTP)

  • file specifies the location of the resource within the host.

When a class file, say rmibook.quicktour.RemoteEcho, can't be found locally on the CLASSPATH, the fully qualified class name is converted to a class filename, say javarmi/quicktour/RemoteEcho.class. What happens next depends on the form of file in the codebase URL.

  1. If file ends with a “/”, it is assumed to be a directory under which class files (and other class resources) are located as individual files in directories corresponding to their package names. The class filename is appended to the codebase URL to form a lookup URL. The RMI class loader tries to connect to the lookup URL; if this succeeds, the data is read and the class can be loaded.

  2. If file does not end with a “/”, it is assumed to name a JAR file. If it hasn't already happened, the JAR is retrieved and cached locally, and the local copy is searched for the class filename; if this succeeds, the class can be loaded.

In either case, if the process fails, the next URL in the codebase is tried. If all codebase URLs fail, a ClassNotFoundException is thrown.

Sometimes a NoClassDefFoundError is encountered. This is a symptom of a previous ClassNotFoundException, and usually indicates a stale RMI registry. See §A.4.11.

Uses of code mobility

Code mobility in RMI has a number of important uses, of which the following are merely the most obvious.

True polymorphism

Polymorphism is Greek for “many forms”. In object-oriented terminology, it means that a derived class can be used wherever any of its base classes can be used. This is a fundamental object-oriented design technique, and it is also the basic mechanism for exploiting code mobility.

Just specify your remote interfaces polymorphically. This means specifying the result or parameter types (or both) of your remote interfaces in terms of base classes (or Java interfaces), but passing or returning objects of derived classes. If the derived class is unknown to the receiver, normally this technique fails with a ClassNotFoundException at the receiver. However, if the object is being received by RMI and the class has been annotated with a valid codebase, the RMI runtime in the receiver downloads the class dynamically—on demand—from the codebase, and execution continues.

By this means you can truly exploit object-oriented polymorphism in RMI applications. Consider the following example:

public interface InterfaceKnownToClient { ... }

public interface RemoteService extends java.rmi.Remote
{
       InterfaceKnownToClient method() throws RemoteException;
}

A server which implements RemoteService can return any implementation of InterfaceKnownToClient it likes from RemoteService.method. If the class of the result is not known to the client, and if the correct codebase conditions exist at the server, the implementation class will be downloaded to the client.

This technique allows the server to vary the implementation class unbeknownst to the client, for application-specific reasons. It also allows the implementation class itself to evolve without having to be redeployed to all clients, and without being constrained by the versioning rules of serialization.

The same technique can be used in the reverse direction, allowing the client to supply the implementation class to the server:

public interface InterfaceKnownToServer { ... }

public interface RemoteService extends java.rmi.Remote
{
       void method(InterfaceKnownToServer is) throws RemoteException;
}

Thinner clients

You can use code mobility to reduce the amount of code installed at the client.

Application rollout

You can use code mobility to avoid the “rollout” problem—client reinstallations—for classes which are likely to evolve over the life of an application. By ensuring that such classes are always obtained dynamically, you never have to redistribute them statically. You only have to update a single copy of the class at the codebase server: RMI propagates the class as required.

Application integrity

By ensuring that sensitive classes are not installed at the client but are obtained dynamically, you can reduce the possibility of external tampering with class files.

Security considerations

Obviously the “codebase” feature raises questions about security. We shouldn't necessarily trust the other end of an RMI communication to provide us with classes willy-nilly.

For this reason, RMI class loading via the codebase is completely disabled unless a Java security manager is running. The Java security model provides the ability to tailor completely the security environment for RMI applications generally, and downloaded classes in particular.

By default, Java's security manager provides complete security if it is installed. Even if a security manager is running, every permission the application requires must be explicitly configured. The default security configuration doesn't permit any external interactions to occur—with the local file system, the network, or the GUI. When configuring the application's permissions, classes from different codebases can be assigned different sets of permissions, including the null set—no permissions.

RMI only attempts to download code from the codebase if a Java security manager is installed in the JVM. Any RMI server or client which needs to download code must install a security manager. Typically the standard java.security.SecurityManager or its close relation the java.rmi.RMISecurityManager is used.

In JDK 1.1, java.lang.SecurityManager was an abstract class, and RMISecurityManager was a concrete extension of it. In Java 2 (JDK 1.2 and later), the security architecture was extensively revised: java.lang.SecurityManager became a concrete class, and RMISecurityManager became a simple derivation from it, with identical behaviour.

To allow RMI clients or servers to download code, they must run under a security manager with an appropriate security policy file. If you specifically want to disable this feature, either don't install a security manager, or install a security policy file which prevents it.[1]

A number of possible settings for class downloading suggest themselves:

  • disabled throughout (clients and servers)

  • enabled throughout (clients and servers)

  • enabled for all clients and disabled for all servers

  • disabled throughout except from trusted codebases

  • enabled for all clients, and disabled for all servers except from trusted codebases.

With Java's fine-grained access control, it is possible to configure security in even more detail. You can define a security policy which accepts classes from a number of trusted codebases, but allows classes from each codebase a different set of permissions—a different security profile. Security management for RMI is discussed in Chapter 8.

Setup

This section discusses the steps needed to allow RMI to download classes from the codebase. RMI clients can be allowed to do this if an RMI server returns an object whose actual type is not known at the client. RMI servers can be allowed to do this if a client sends a object in a remote method call whose actual type is not known at the server.

In the above, “returns an object” and “sends an object” includes both the actual object sent as a parameter or returned as a result and any object reachable from that object in the serialization process. “Returns an object” also includes exceptions thrown by remote method invocations.

Codebase property

An RMI server or client which wishes its classes to be available to the other party must have an appropriate value set in the system property java.rmi.server.codebase. This causes all classes marshalled by RMI to be annotated with the codebase property.

It does not cause that server or client itself to load the classes from the codebase: the class is acquired from the CLASSPATH and then annotated with the codebase. This is a possible source of error. The JVM which does the annotating still runs if the codebase property is missing or incorrect, but its clients will get ClassNotFoundExceptions. In other words, the fact that an RMI server starts is, by itself, no indication that the codebase has been correctly specified and is working.

As an alternative to setting the codebase property, from JDK 1.3 the server or client can acquire a required class via the RMIClassLoader.loadClass method, specifying a URL as the class location. This has the same effect—it causes the loaded class to be annotated with its codebase location—but it has the advantage that multiple codebases can be used at each end. This technique is therefore more flexible, but it is a little more inconvenient to access classes in this way, and it requires that the classes so accessed be unavailable via the CLASSPATH; otherwise no annotation will occur.

RMI registry

RMI code mobility will not work unless the registry is correctly set up. This is a frequent source of error. You must use the “standard” registry configuration described in §6.10.1.

The registry problems most frequently encountered are as follows:

  1. A ClassNotFoundException (nested in an UnmarshalException) when binding a server to the registry indicates that the binding program hasn't correctly set the codebase property.

  2. A ClassNotFoundException (nested in an UnmarshalException) when a client looks up the registry indicates that the codebase property has been lost by the registry because it is improperly configured.

To understand these problems, consider what happens when an RMI server is bound to the registry. When Naming.bind or Registry.bind is called, the registry receives the name and the RMI stub for the server being registered.

If the RMI registry is running in an environment with no java.rmi.server.codebase property and no CLASSPATH access to application classes, and the caller of bind doesn't have a java.rmi.server.codebase set, the registry itself will encounter a ClassNotFoundException when unmarshalling the arguments. This exception will be wrapped in an UnmarshalException and thrown remotely back to the caller—the server.

Example 9.1 shows a server initialization.

Example 9.1. Server-side codebase setup problem

Remote     remote = new RemoteXXX(...);// args not shown
Naming.rebind("name",remote);
// If this throws a ClassNotFoundException wrapped in a
// RemoteException, the codebase was not specified
// correctly when running this code

Regardless of the server's codebase settings, if the RMI registry is able to load the stub class from its CLASSPATH it will do so, and if it doesn't have java.rmi.server.codebase set, it won't annotate the class when it has done so. The result is an un-annotated RMI stub class in the registry. Now, when a client calls Naming.lookup or Registry.lookup, a ClassNotFoundException will be thrown when the client unmarshals the result, and this will be wrapped in an UnmarshalException at the client.

Example 9.2 shows a client initialization.

Example 9.2. Client-side codebase setup problem

Remote         remote = Naming.lookup("name");
// If this throws a ClassNotFoundException wrapped in a
// RemoteException, the Registry was able to load the stub class directly
// (not via the codebase), or the codebase was not specified
// when running the Registry

Codebase server

The codebase is a list of one or more URLs. Each URL refers to a codebase server, typically an HTTP server. Each such server needs to be running and available at the specified URL. You can check this from a client with a Web browser. The codebase server doesn't need to run in the same computer as the RMI services, or even in the same network segment; it just has to be accessible to codebase clients.

Client installations

Client installations should contain the JDK and the client side of the application. This includes any remote interfaces, because the client knows about these. It does not include RMI servers, skeletons or stubs. Servers and skeletons do not belong at clients at all; stubs can be downloaded if the system is set up correctly. The client installation should also exclude any classes which you specifically want to download to the client; this would include any classes which the client acquires polymorphically, i.e. via references to base classes or interfaces.

The useCodebaseOnly property

Further control can be gained over dynamic code loading by setting the java.rmi.server.useCodebaseOnly property to true. This forces RMI to ignore the codebase transmitted with the class, and use the codebase configured in java.rmi.server.codebase to load classes not found on the CLASSPATH.

This is useful when both servers and clients are executed with java.rmi.server.codebase properties. It causes each end of a remote call to ignore the codebase information coming from the other end, and to use its own value instead.

This technique provides some additional security. It becomes impossible to tamper with the transmission so as to cause the target to load a class from a different codebase—to interpolate unauthorized classes into the target.

This may be particularly appropriate for RMI servers uploading classes from clients, as client environments may be only partially trusted.

HTTP servers

Any HTTP server—any server which obeys the HTTP protocol—can be used for code mobility. HTTP servers are available from a number of sources.

The lightweight HTTP class server

Sun provides a free class file server. This is a “lightweight” and simple HTTP server which can be used during development of RMI systems using code mobility. The class file server is shipped with recent versions of the JDK and Jini, and can be obtained as source code from

Use of the lightweight class server is unrestricted. However, you probably wouldn't choose to deploy this as your codebase server in production, for a number of reasons:

  • it is not a supported product

  • it is a simple implementation which does not scale to large numbers of clients and requests

  • it is deliberately “crippled” to serve only class files.

This last restriction means that the server will not handle class files in JAR archives. It also means that the server will not satisfy requests for application resources made via the Class.getResource or Class.getResourceAsStream methods—for instance, application graphics or ResourceBundles associated with classes acquired from a codebase.

In fact the restriction to class files is very simple to undo, consisting of changes to about three lines of code. You should make this change if you want to test dynamic loading of classes from JAR files or mobility of application resources without having to use a full-strength HTTP server.

Other http servers

The Java 2 Enterprise Edition (J2EE) comes with a built-in HTTP server. Sun's Java Web Server is an HTTP server. Many other free and commercial HTTP servers are available.

Other protocols

You are not limited to HTTP for the codebase protocol. You can use any protocol supported by Java, i.e. any protocol which can be used in a Java URL—any protocol for which Java implements a client.

The file: protocol

You can use the file: protocol. This can only work in an installation where every client has file-level access to the codebase, typically via a network shared drive. It will not work if the file: URL refers to a file on the codebase server in terms of its own local file system, because clients will interpret the URL relative to their own local file systems. URLs must make sense to the client.

When constructing a file: URL, there is no hostname, so the URL typically starts with file:/// before the directory/filename part.

The “|” character is sometimes seen in file: URLs instead of the MS-DOS “:” drive separator, and was generated by the File.toURL method in some JDK versions. This substitution is unnecessary as from JDK 1.3 at least, and doesn't comply with the proposed URL and URI standards.[2]

FTP—file transfer protocol

You can use the ftp: protocol. This requires you to have an FTP server running at the codebase location.

FTP is not always supported through firewalls, so it would generally not be a suitable choice for:

  • corporate wide-area networks (WANs)

  • intranets containing firewalls

  • the Internet itself.

Most Web servers also provide an FTP service, including Sun's Java Web Server and the servers provided with J2EE. The “lightweight” HTTP class server discussed above does not provide an FTP service.

HTTPS—secure hypertext transfer protocol

If you have installed the Java Secure Sockets Extension (JSSE), you can use the https: protocol. HTTPS is a secure version of HTTP; in fact it is HTTP transmitted over secure sockets (SSL) instead of plain sockets, the difference being that secure sockets are authenticated and encrypted. (For more information on SSL see Chapter 16.)

Using HTTPS instead of HTTP provides you with secured access to the codebase from within your application. You must specify https as the protocol in the URL for the codebase, and set the URL's hostname to that of the HTTPS server. If you have HTTP proxies, you must also set the system properties https.proxyHost and https.proxyPort appropriately. You must also arrange for the system property java.content.handler.pkgs to be set appropriately, as described in the JSSE documentation.

J2EE comes with a built-in HTTPS server. Sun's Java Web Server provides an HTTPS service. Many other free and commercial HTTPS servers are available.

Using HTTPS rather than HTTP provides security at the expense of performance. There is an overhead in setting up the initial secure socket connection, and there is an encryption overhead per transmission.

Authentication

HTTP and HTTPS servers (or their proxies) can be set up to require a client to provide authentication before access to the requested resource is granted.

Normally in a Java HTTP or HTTPS client this will cause an authentication failure. You can provide a java.net.Authenticator to respond to any such authentication process. In this way the application can provide the authentication required itself. This information could be obtained from:

  • a “login” dialog popped-up during the Authenticator callback

  • a prior application-specific login process

  • a local configuration or profile

  • data hard-wired into the application.

Deployment

The simplest way to deploy an RMI application with mobile code is with a single codebase server serving all clients, as shown in Figure 9.2.

Simple codebase deployment

Figure 9.2. Simple codebase deployment

In a larger intranet, especially one with large numbers of RMI clients, interior firewalls, and/or slow network segments, a more sophisticated deployment is indicated. Such a deployment would use a tree of HTTP caching proxy servers to propagate class traffic and reduce the load on the root HTTP server, as shown in Figure 9.3.

Large-scale codebase deployment

Figure 9.3. Large-scale codebase deployment

In this kind of configuration, the network is divided into “enclaves”, reflecting locality or firewall configuration. Each enclave has a local HTTP caching proxy server, through which all HTTP requests are directed.[3] Each such proxy communicates once with the “root” HTTP server to obtain each class file as requested by a client, and caches the result locally. Thereafter, the proxy is able to satisfy class-file requests for that file from its local cache. This continues until the cache expires or a newer version of the file requested is observed at the root.

In this way, the traffic to the root server is reduced from that generated by n clients to that generated by m proxy servers, where n and m can be in any desired ratio.

In a truly enormous deployment, or one with a large number of slow network segments, you might introduce further intermediate layers of HTTP proxy servers.

Downloading the client

It is possible to download almost the entire client. The application software permanently installed at the client can be limited to a simple bootstrap program.

This technique has tremendous advantages in large-scale deployments, in that it completely eliminates the rollout phase of application deployment. You can build a self-refreshing system in which a new client is acquired every time a user logs in.

All that the bootstrap program needs to do is acquire a single class which conforms to a known interface. For maximum simplicity, the client can be an implementation of the java.lang.Runnable interface, so that all the bootstrap program has to do is execute the client's run method:

public interface Client extends Runnable { ... }

Provided the actual class implementing this interface is not available on the client's CLASSPATH, it will be downloaded from the codebase.

Three variants of this scheme are possible:

  • bootstrapping via RMIClassLoader

  • bootstrapping via an RMI call

  • bootstrapping via a MarshalledObject.

In all cases, a security manager must be installed in the bootstrap program.

The network traffic for class loading can be moderated by interposing HTTP caching proxies with suitable expiry times, as in Figure 9.3 above.

RMIClassLoader bootstrap

In this technique, the RMIClassLoader is used to acquire the class from a well-known codebase URL, as shown in Example 9.3.

Example 9.3. URL Client bootstrap

public class URLClientBootstrap
{
       static String codebase    = ...; // codebase URL
       static String clientClass = ...; // client class-name

       public static void    main(String[] args) throws Exception
       {
              System.setSecurityManager(new SecurityManager());
              Class cl = RMIClassLoader.loadClass(codebase,clientClass);
              Runnable  client = (Runnable)cl.newInstance();
              client.run();
       }
}

In a realistic application, the codebase and clientClass values would be acquired via the command line or an external configuration file, and exceptions would be handled more appropriately.

The downloaded-client technique can be used even if the client proper makes no use of RMI. A similar technique is used by Java to force downloading of classes used by Java applets.

Bootstrapping via RMI

In this technique, an RMI call is used to acquire the client from a well-known RMI server. The server must ensure that the java.rmi.server.codebase property is set appropriately, or that it acquires the class to be sent to the client via the RMIClassLoader as in Example 9.3. The client side of this technique is illustrated in Example 9.4.

Example 9.4. RMI client bootstrap

// Remote interface defining the bootstrap service
public interface    Bootstrap extends java.rmi.Remote
{
       Runnable     getClient() throws RemoteException;
}

// The client bootstrap
public class RMIClientBootstrap
{
       static String bootstrapServer = ...; // RMI registry URL

       public static void main(String[] args) throws Exception
       {
              System.setSecurityManager(new RMISecurityManager());
              Bootstrap  bootstrap = (Bootstrap)Naming.lookup
                                            (bootstrapServer);
              Runnable  client = bootstrap.getClient();
              client.run();
       }
}

In this case, the client implementation class and all classes reachable from it must be serializable or remote.

In a realistic application, the bootstrapServer value would be acquired via the command line or an external configuration file, and exceptions would be handled more appropriately.

MarshalledObject bootstrap

In this technique, the client acquires a sequence of bytes from somewhere, possibly a URL, de-serializes an object from it, casts the result to MarshalledObject, and calls MarshalledObject.get to obtain and execute the client (e.g. as a Runnable, followed by a call to Runnable.run). This assumes that the inverse sequence of operations—construction of a MarshalledObject from a client instance, and serializing it to a client-accessible location—has occurred at some previous time. The client side of this technique is illustrated in Example 9.5.

Example 9.5. Bootstrapping via a MarshalledObject

public class MarshalledObjectClientBootstrap
{
       public static void main(String[] args) throws Exception
       {
              System.setSecurityManager(new SecurityManager());
              InputStream in = new URL(args[0]).openStream();
              ObjectInputStream oin = new ObjectInputStream(in);
              Object      object = ((MarshalledObject)oin.readObject)).get();
              Runnable    client = (Runnable)object;
              client.run();
       }
}

The setup required for this technique is illustrated in Example 9.6.

This technique doesn't require the client to be able to connect anywhere. The MarshalledObject can contain arbitrary code to locate the appropriate client object or server; indeed, it could perform a secondary bootstrap via RMI as described above.

Example 9.6. Setup for the MarshalledObject bootstrap

public class MarshalledObjectClientBootstrapCreation
{
       public static void  main(String[] args) throws Exception
       {
              System.setSecurityManager(new SecurityManager());
              // Load and annotate client class, and construct a client
              Class        cl = RMIClassLoader
                                 .loadClass(codebase,clientClass);
              Runnable     client = cl.newInstance(...);
              OutputStream         out = ...;    // create some output stream
              ObjectOutputStream           oout = new ObjectOutputStream(out);
              oout.writeObject(new MarshalledObject(client));
              oout.close();
       }
}


[1] For example, you could disallow network access other than to the RMI server host(s), assuming you have enough control over them to prevent HTTP services being run on them.

[2] RFCs 1738 and 2396.

[3] by setting the Java system properties http.proxyHost and http.proxyPort.

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

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