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