1

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...

  • 1
    Now it’s time to verify that the classes are really downloadable from `http://$1:8000/` as you told to the RMI via the `java.rmi.server.codebase` property. – Holger Dec 04 '13 at 17:34
  • And to look at your HTTP server logs. – user207421 Dec 04 '13 at 22:20
  • @Holger: I'm quite sure the server machine is accessible to the client: I can use a browser and download manually the classes. In a few hours I'll get the server log! – EdoardoSchnell Dec 06 '13 at 00:45
  • Then we can only guess. I would try to printout the java.rmi.server.codebase` property at runtime to check whether the `http://$1:8000/` variable substitution in the script worked as expected. Further, you could try to download the class file from the intended URL from your client via [`URL`](http://docs.oracle.com/javase/7/docs/api/java/net/URL.html) directly to see whether this works. And if this all does not help, do step by step debugging to see what the `ClassLoader` really tries to access. – Holger Dec 06 '13 at 09:06

1 Answers1

2

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Exactly. The security manager is required at the client, the one that is to do the downloading. It isn't required in the server, unless the client has its own codebase for uploading classes to the server. – user207421 Dec 03 '13 at 22:08
  • Wow, thanks for the reply, I didn't know about that option... wandering why the professor didn't tell us about it. Anyway, now it runs only if the client and the server are on the same machine, if they aren't I get a security exception. See edited main post. – EdoardoSchnell Dec 04 '13 at 14:11
  • @EdoardoSchnell: Maybe this helps: http://docs.oracle.com/javase/tutorial/rmi/running.html – Holger Dec 04 '13 at 14:15
  • @Holger: Thanks, I have the policy file as indicated in that link (in fact I have it even more general: `grant{ permission java.security.AllPermission};`) so I don't think that that is the problem... – EdoardoSchnell Dec 04 '13 at 14:34
  • Looks like there was a typo in the policy file. Now I'm back to a different `ClassNotFoundException` and setting `-Djava.security.manager` does not help.. – EdoardoSchnell Dec 04 '13 at 15:58