Remoting with Java

Java May 13, 2016

As I've mentioned previously, I don't look at remoting/RPC as a particularly good foundation for distributed applications - messaging is usually more sensible.
However, I was curious as to how one would go about implementing it, complete with the creation of proxies for non-serializable method parameters and return types. I spent Saturday morning implementing this very roughly in Java; it can be found here.

The main class, ObjectConnection, requires an ObjectInputStream and ObjectOutputStream to work; in a networking situation, these can be obtained by wrapping a Socket's InputStream and OutputStream.

Once connected, one calls 'start()', and obtains a proxy to the other side's initially exported object using 'getInitialRemoteObject(Class clazz)'.

Methods can then be called on this proxy in transparent fashion, passing and returning other objects, which ObjectConnection either serializes or transparently creates proxies for.

The actual scenario I had in mind was a purely local one, where this would play a role in transparently splitting a service-oriented Java application across processes for the purpose of running untrusted services in separate JVMs.

One outstanding weakness is that non-serializable objects cannot be remoted unless they are an interface type. This is a limitation of the standard Java proxy, and could probably be worked around with bytecode generation.

Example

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

interface Appiface {

	void setNumber(int x);
	int getNumber();

	Appiface makeNewApp(String name);
}

public class App implements Appiface {
	
	private int number;
	private final String name;
	
	public void setNumber(int x) {
		System.err.println("Set " + name + "'s number to: " + x);
		this.number = x;
	}


	public int getNumber() {
		return this.number;
	}


	public Appiface makeNewApp(String name) {
		Appiface ai = new App(name);
		return ai;
	}
	
	
	public App(String name) {
		this.name = name;
	}
	
	private static void runServer() throws IOException {
    	final ServerSocket serverSock = new ServerSocket(6666);
    	new Thread() {
    		public void run() {
				Socket sock;
				try {
					sock = serverSock.accept();
					ObjectOutputStream oos = new ObjectOutputStream(sock.getOutputStream());
					ObjectInputStream ois = new ObjectInputStream(sock.getInputStream());
					ObjectConnection connectionA = new ObjectConnection(new App("ServerObject"), ois, oos);
					connectionA.start();
				} catch (IOException e) {
					e.printStackTrace();
				}
		
    		}
    	}.start();
	}
	
	private static Appiface runClient() throws IOException {
		Socket sock = new Socket();
		sock.connect(new InetSocketAddress("localhost", 6666));
		ObjectOutputStream oos = new ObjectOutputStream(sock.getOutputStream());
		ObjectInputStream ois = new ObjectInputStream(sock.getInputStream());
		ObjectConnection connectionB = new ObjectConnection(null, ois, oos);
		connectionB.start();
		return connectionB.getInitialRemoteObject(Appiface.class);
	}
	
	
	public static void main(String[] args) throws Exception {
		// Start a server
		runServer();
		
		// Start the client, get a reference to the server's object.
		Appiface remoteObject = runClient();
		
		// Call some methods on it
		remoteObject.setNumber(5);
		remoteObject.setNumber(2);
		
		// Get a new remote object
		Appiface otherRemoteObject = remoteObject.makeNewApp("OtherServerObject");
		
		// Call a method on that
		otherRemoteObject.setNumber(4);
		
    }


}

Tags