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...
If you look closer to the stack trace, you will see that the answer is already given:
In order to support class download via RMI there must be a
SecurityManager
installed. The simplest way to do it isSystem.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 defaultSecurityManager
using policy files.Alternatively you can implement your own
SecurityManager
. For testing purposes, it’s easy to implement aSecurityManager
just allowing everything though this is not recommended for production code.