RMI from remote client
When working in distributed environments sometimes is useful to encapsulate certain operations, for example, the following scenario
you are on host1 and you need to automate a process on the hostx, this process is about creating and configuring a given set of heavy tasks which imply the use of processes and threads and some network operations among hosts
one way to do it is using the trendy stuff of 2018 which is… yes, REST services which mask the phases of a given task behind some a neat POST/PUT/GET/HEAD requests and you can manipulate them from your browser
but in this case you don’t really care about a web interface, in fact, there is not a GUI, you only have an ssh connection, a couple of open ports and very short window of time, (yes, it sucks but that is the way how huge enterprise environments are designed), so, given this constrains what can we do?
although it would be a cool going for the REST approach, the cost of implementing it would be really high because of all the things that are needed when working with stateless responses, perhaps we can do it another time, for now, let’s talk about and forgotten treasure of the past, Remote Method Invocation (RMI)
given all this constrains there is always one tool that we can count on, the old and trustworthy Java, I won’t dig too much into what is RMI nor RPC (you can google it ;)) I just will say that is a method on which you can interact with an object which is on a remote host as if it was local, long story show, we can create a class which will encapsulate all these huge tasks (let’s call them transactions) and we can perform them in a synchronous fashion which is going allow us to know what exactly is happening on the other side, so, basically what will happen is:
from the local host, we will call the steps of our transaction from which we will be able to get the status of the task at all times but behind the curtain, those operations will be happening far away from us, perhaps in another part of the world
before continue on, let’s point out some limitations, you need Java on the server and the client and at least one port open on the host, some people don’t like the synchronous stuff nowadays but still, there are certain scenarios where you should care
Server-side
we need to describe the “task” as an interface
package com.donhk; import java.rmi.Remote; import java.rmi.RemoteException; public interface RMIInterface extends Remote { public String helloTo(String name) throws RemoteException; }
then we need a class that will implement our interface and will contain the real logic
package com.donhk; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class ServerOperation extends UnicastRemoteObject implements RMIInterface { private static final long serialVersionUID = 1L; public ServerOperation() throws RemoteException { super(); } @Override public String helloTo(String name) throws RemoteException { System.err.println(name + " is trying to contact!"); return "Server says hello to " + name; } }
for instance, this is what will run on the server, finally, we just need to call this code from somewhere, let’s create a main method
package com.donhk; import java.rmi.RMISecurityManager; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Main { public static void main(String arg[]) { System.setSecurityManager(new SecurityManager()); try { Registry registry = LocateRegistry.getRegistry(1099); registry.rebind("MyServer", new ServerOperation()); System.err.println("Server ready"); } catch (Exception e) { System.err.println("Server exception: " + e.toString()); e.printStackTrace(); } } }
then we create a jar and we copy it to our favorite server
before executing it there is a couple of things to know since we will manipulate this stuff remotely, we need to tell to the JVM that it actually will happen (give permissions)
to do this we need to create a text file like this mypolicy.polic
grant {
permission java.security.AllPermission;
permission java.net.SocketPermission "localhost:1099", "connect, resolve";
permission java.net.SocketPermission "127.0.0.1:1099", "connect, resolve";
};
then we need to start the RMI register (this is what allow us to share the object with the remote client), it is located under the java bin directory
/opt/jdk-9.0.4/bin/rmiregistry #[port]
before executing it you MUST tell the JVM where the classes are located, otherwise, the register won’t know where to find the object definitions, to do so, there are 2 ways to do it, the quick and dirty is not recommended for production
export CLASSPATH=/path/to/the/server.jar
the other ways are to use -Djava.rmi.server.codebase
export CLASSPATH=/path/to/classes rmiregistry #[port]
now we can start up the server
java -Djava.rmi.server.hostname=192.168.0.16 -Djava.security.policy=mypolicy.policy -jar server/rmiserver.jar
server output
Client-side
we need the interface that server is using
package com.donhk; import java.rmi.Remote; import java.rmi.RemoteException; public interface RMIInterface extends Remote { public String helloTo(String name) throws RemoteException; }
the same security policy, this time let’s add it programmatically instead of using the -D
package com.donhk; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Main { public static void main(String[] args) { System.setProperty("java.security.policy", "file:test.policy"); if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); } try { String serverIP = "192.168.0.16"; // or localhost if client and server on same machine. int serverPort = 1099; Registry registry = LocateRegistry.getRegistry(serverIP, serverPort); RMIInterface serverOperation = (RMIInterface) registry.lookup("MyServer"); String response = serverOperation.helloTo("kitty"); System.out.println("server: " + response); } catch (Exception e) { System.err.println("Server exception: " + e.toString()); e.printStackTrace(); } } }
my test.policy quick and dirty
grant { permission java.security.AllPermission; };
that is all, now we can compile the code and execute it
notice these lines
Registry registry = LocateRegistry.getRegistry(serverIP, serverPort); RMIInterface serverOperation = (RMIInterface) registry.lookup("MyServer");
we get the remote object and we land it into the interface, that is the client knows how to interact with the server, it never knows (or cares) what are the methods about and it can call them and if it were a local object
peace and love ☮
bonus, #free #soundtrack
[youtube https://www.youtube.com/watch?v=cYtGJGp-9hA&w=560&h=315]
some links
https://docs.oracle.com/javase/7/docs/technotes/guides/rmi/javarmiproperties.html
https://docs.oracle.com/javase/7/docs/technotes/guides/rmi/faq.html#netfqdn
https://www.tutorialspoint.com/java_rmi/java_rmi_database_application.htm
https://docs.oracle.com/javase/8/docs/technotes/guides/rmi/hello/hello-world.html
http://www.ejbtutorial.com/java-rmi/a-step-by-step-implementation-tutorial-for-java-rmi
https://stackoverflow.com/questions/15685686/java-rmi-connectexception-connection-refused-to-host-127-0-1-1
One thought on “RMI from remote client”