Question

I've been programming rmi for a while, and after like a month I'm still stuck on a tricky issue. I want to send a Serialized object from a server to a client and I want the client to download the class of this object from the server codebase. I can't get this to work without having the object's class in the classpath of the client. Here's what I've been doing:

I have a ClientLoader asking for a ClientPlayer to an AuthServer. The ClientLoader should download also the class of ClientPlayer and load it dynamically.

This is the authentication server, which should create an object ClientPlayer (or ClientAdmin) and return this to the ClientLoader. (For brevity's sake I omit the interfaces; anyway AlfaBetaInt means Beta implements it and Alfa uses it)

public class AuthServer extends UnicastRemoteObject implements LoaderAuthInt{

    private static MasterServer master;

    public AuthServer() throws RemoteException{
        super();
    }

    public Runnable login(String username, String password) throws RemoteException{
    System.out.println("Requested login with username '" + username + "' and password '" + password + "'");
    if(password.equals("admin"))
        return (Runnable)(new ClientAdmin());
    else

        return (Runnable)(new ClientPlayer(username, (PlayerMasterInt)master));
}

    public static void main(String[] args){

        if(System.getSecurityManager() == null)
        System.setSecurityManager(new RMISecurityManager());

        String port = args[0];

        System.out.println("Current codebase:" + System.getProperty("java.rmi.server.codebase"));

        try{
            master = new MasterServer();//create master server
            System.out.println("MasterServer creato.");

            AuthServer auth = new AuthServer();//create auth server
            System.out.println("AuthServer created.");
            Naming.rebind("//:" + port + "/authServer", (LoaderAuthInt)auth);//rebind auth server
            System.out.println("AuthServer rebinded.");
        }catch(Exception e){
            System.err.println(e);
            System.exit(1);
        }
    }
}

This is the ClientLoader which should download the ClientPlayer (both the object and the class) from the server and run it.

public class ClientLoader{

    public static void main(String[] args){
        if(System.getSecurityManager() == null)
        System.setSecurityManager(new RMISecurityManager());

    Console console = System.console();

    String host = args[0];

    try{
                    //check to see if Server is registered in rmiregistry
        String[] lista = Naming.list("//" + host );
        for( int i = 0; i < lista.length; i = i+1)
            System.out.println(lista[i]);
            //look up the client
        LoaderAuthInt authServer = (LoaderAuthInt)Naming.lookup("//" + host + "/authServer");
        System.out.println("Lookup succesful.");
        if( authServer != null)
            System.out.println("authServer != null.");
        //RUN THE CLIENT!
        Runnable client = authServer.login(console.readLine("Username: "), new String(console.readPassword("Password: ")));

        if(client == null)
            System.err.println("Login not valid");
        else
            client.run();

        System.out.println("Bye bye");
    }catch(Exception e){
        System.err.println(e);
        System.exit(1);
    }
}
}

This is the object that should be sent from the AuthServer to the ClientLoader:

public class ClientPlayer implements Runnable, Serializable, GamePlayerInt{

private String username;
private PlayerMasterInt master;

public ClientPlayer(String username, PlayerMasterInt master){
    this.username = username;
    this.master = master;
}

public void run(){
    Console console = System.console();

    System.out.println("I'm a succesfully authenticated ClientPlayer!");
}catch(RemoteException e){
            System.err.println(e);
                System.exit(1);
        }
    }
}

public String getUsername() throws RemoteException{
    return username;
}
}

Here's the script to launch the server: note that I do specify the codebase and I do set the property useCodeBaseOnly to false

if [ "$1" == "" -o "$2" == "" ]; then
    echo "usage: $0 <ip> <port number>"
else
java    -Djava.rmi.server.codebase=http://$1:8000/ \
        -Djava.rmi.server.hostname=$1 \
        -Djava.security.policy=policy \
        -Djava.rmi.server.useCodebaseOnly=false \
        card.AuthServer $2
fi

Here's how I launch the ClientLoader: again, I set the codebase and useCodeBaseOnly

if [ "$1" == "" -o "$2" == "" ]; then
    echo "usage: $0 <ip> <port>"
else
    java    -Djava.security.policy=policy \
        -Djava.rmi.server.useCodebaseOnly=true \
        -Djava.rmi.server.codebase=http://$1:8000/ \
        card.ClientLoader "$1:$2"
fi

The server runs fine; I have an http server at port 8000 and an rmiregistry at port 2378. The client loader runs... quasi-fine: the method list() on the rmiregistry shows the binded server, the lookup of the server works, I can download the client from the server but when I run it I got a ClassNotFoundException:

    java.rmi.UnmarshalException: error unmarshalling return; nested exception is: 
java.lang.ClassNotFoundException: card.ClientPlayer (no security manager: RMI class loader disabled)
at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:196)
at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:194)
at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:148)
at com.sun.proxy.$Proxy0.login(Unknown Source)
at card.ClientLoader.main(ClientLoader.java:26)
Caused by: java.lang.ClassNotFoundException: card.ClientPlayer (no security manager: RMI class loader disabled)
at sun.rmi.server.LoaderHandler.loadClass(LoaderHandler.java:395)
at sun.rmi.server.LoaderHandler.loadClass(LoaderHandler.java:185)
at java.rmi.server.RMIClassLoader$2.loadClass(RMIClassLoader.java:637)
at java.rmi.server.RMIClassLoader.loadClass(RMIClassLoader.java:264)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:222)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1610)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1515)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1769)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1348)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
at sun.rmi.server.UnicastRef.unmarshalValue(UnicastRef.java:324)
at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:173)
... 4 more

I suspect that the ClientLoader does not uses the right codebase... Any help is greatly appreciated!!

Edit: I added -Djava.security.manager to the client launch script, as suggested... but that causes these security exceptions:

Exception in thread "main" java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "writeFileDescriptor")
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:372)
at java.security.AccessController.checkPermission(AccessController.java:559)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
at java.lang.SecurityManager.checkWrite(SecurityManager.java:954)
at java.io.FileOutputStream.<init>(FileOutputStream.java:244)
at java.io.Console.<init>(Console.java:566)
at java.io.Console.<init>(Console.java:92)
at java.io.Console$2.console(Console.java:540)
at java.lang.System.console(System.java:211)
at card.ClientLoader.main(ClientLoader.java:14)

Please note that I do have the policy file (granting AllPermission)! Does the option -Djava.security.manager change policy configuration in anyway?

Edit: There was a typo in the policy file. [Am I allowed to hate java rmi?] So, back to the ClassNotFoundException, though in a slight different flavour:

java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
java.lang.ClassNotFoundException: card.ClientPlayer
at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:196)
at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:194)
at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:148)
at com.sun.proxy.$Proxy0.login(Unknown Source)
at card.ClientLoader.main(ClientLoader.java:25)
Caused by: java.lang.ClassNotFoundException: card.ClientPlayer
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.rmi.server.LoaderHandler$Loader.loadClass(LoaderHandler.java:1208)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:270)
at sun.rmi.server.LoaderHandler.loadClassForName(LoaderHandler.java:1221)
at sun.rmi.server.LoaderHandler.loadClass(LoaderHandler.java:454)
at sun.rmi.server.LoaderHandler.loadClass(LoaderHandler.java:185)
at java.rmi.server.RMIClassLoader$2.loadClass(RMIClassLoader.java:637)
at java.rmi.server.RMIClassLoader.loadClass(RMIClassLoader.java:264)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:222)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1610)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1515)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1769)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1348)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
at sun.rmi.server.UnicastRef.unmarshalValue(UnicastRef.java:324)
at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:173)
... 4 more

Any idea? I'm really struggling to get this to work...

Was it helpful?

Solution

If you look closer to the stack trace, you will see that the answer is already given:

no security manager: RMI class loader disabled

In order to support class download via RMI there must be a SecurityManager installed. The simplest way to do it is System.setSecurityManager(new SecurityManager());

But then, of course, you must edit the policies to allow your code the actions you want to perform.

Edit: I see, it seems you already did this, but just forgot the -Djava.security.manager option on the command line. Using this option has the same effect as the code snippet above, it installs the default SecurityManager using policy files.


Alternatively you can implement your own SecurityManager. For testing purposes, it’s easy to implement a SecurityManager just allowing everything though this is not recommended for production code.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top