refcodes-remoting: Face-to-face lightweight remote method calls

README

The REFCODES.ORG codes represent a group of artifacts consolidating parts of my work in the past years. Several topics are covered which I consider useful for you, programmers, developers and software engineers.

What is this repository for?

This artifact is a lightweight RPC (remote procedure call) toolkit (you may also call it an IPC (inter process communication) toolkit). On your server you publish a Java object through an explicit interface contract; on your client you access this instance through a proxy implementing that contract.

How do I get set up?

To get up and running, include the following dependency (without the three dots “…”) in your pom.xml:

1
2
3
4
5
6
7
8
9
<dependencies>
	...
	<dependency>
		<artifactId>refcodes-remoting</artifactId>
		<groupId>org.refcodes</groupId>
		<version>4.9.9</version>
	</dependency>
	...
</dependencies>

The artifact is available from Maven Central. The source code is hosted at Bitbucket, and the API documentation is available at javadoc.io.

Introduction

Publish an instance through an interface contract on your server, connect to it on your client via a proxy - voilà, you may do remote method calls via the proxy on the instance … just with a few lines of code …

This proxy provides your client exactly the same methods as the instance offers on the server. Invoking any of them methods on the proxy at the client actually causes the according method on the remote instance at the server to be processed. Voilà, there is your remote procedure call!

Neither stubs nor skeletons to be generated anywhere, no need for you to generate any code.

The current transport-neutral API represents calls as versioned InvocationRequest and InvocationResponse values. Remote failures are represented by RemoteError, so raw exception object graphs need not cross the transport boundary. This toolkit can be attached to different connections:

  • Loopback connection: Direct object access on the same JVM (see LoopbackRemoteTest source codes)
  • Input-/OutputStream: Anything which is a Java InputStream/OutputStream can be used (see IoStreamRemoteTest source codes)
  • Socket: Use Java’s ServerSocket / (Client-)Socket mechanism (see ObservableSocketRemoteTest source codes)
  • HTTP: The refcodes-remoting-alt-http adapter carries the transport-neutral invocation contract over path-based HTTP endpoints using refcodes-rest; POST supports the complete contract while suitable read operations can additionally be exposed through GET

The refcodes-remoting-ext-observer provides an observable implementation (as of the refcodes-observer artifact) of the refcodes-remoting tool-box.

Yes, it’s observable: A synchronous PublishProxyEvent lets the client veto publication before the subject is registered. A ProxyPublishedEvent is emitted only after the server has registered the accepted subject, so the proxy can be used directly by the observer. Corresponding events report when proxies and subjects are signed off.

How do I get started?

The transport-neutral API separates the published interface, invocation handling and transport. A local setup, useful for illustrating the contract, looks as follows:

1
2
3
4
5
6
...
ReflectiveRemoteInvocationDispatcher theHandler = new ReflectiveRemoteInvocationDispatcher();
theHandler.publish( "calculator", Calculator.class, theCalculator );
RemoteInvoker theInvoker = theHandler::handle;
Calculator theProxy = RemoteProxyFactory.createProxy( Calculator.class, "calculator", theInvoker );
...

The refcodes-remoting-alt-http adapter supplies an HttpRemoteEndpoint for the server and an HttpRemoteInvoker for the client. TLS, authentication, authorization, request-size limits and timeouts remain responsibilities of the configured transport.

HTTP-friendly remoting

The HTTP adapter is deliberately not a complete REST model: Published objects remain RPC subjects, but each operation receives an addressable endpoint:

POST /remoting/{subjectId}/{operation}
GET  /remoting/{subjectId}/{operation}

POST remains the universal transport and accepts the structured InvocationRequest, including complex arguments and overloaded methods. The legacy POST /remoting endpoint remains available for transport compatibility. The HttpRemoteInvoker uses the path-based POST endpoint and preserves structured remote errors even when the HTTP response itself is not successful.

The server endpoint can be bound with just a few lines:

1
2
3
4
5
...
ReflectiveRemoteInvocationDispatcher theHandler = new ReflectiveRemoteInvocationDispatcher();
theHandler.publish( "calculator", Calculator.class, theCalculator );
HttpRemoteEndpoint.bind( theRestServer, "/remoting", theHandler ).open();
...

By default, HttpGetPolicy.BEAN_READERS permits conservative, argument-free readers such as getSomething(), boolean isSomething() and hasSomething(), as well as isEmpty(), size(), length() and count(). The alternatives are HttpGetPolicy.NONE and the explicitly enabled HttpGetPolicy.CONVENTIONAL_READERS, which also recognizes established query names such as find..., lookup..., read..., query..., search..., contains..., matches..., supports... and can....

Applications can compose those conventions with their own method-name patterns:

1
2
3
4
5
6
7
8
9
10
11
...
HttpGetPolicy thePolicy = HttpGetPolicy.builder().
	withConventionalReaders().
	withIncludePattern( "^(calculate|resolve|probe).*$" ).
	withExcludePattern( "^resolveSecret$" ).
	build();

HttpRemoteEndpoint.bind( theRestServer, "/remoting", theHandler ).
	withHttpGetPolicy( thePolicy ).
	open();
...

Include patterns extend the selected convention, while exclude patterns take precedence. Neither can bypass the hard checks: A GET operation must be a published contract method, must return a value and may accept only scalar types supported by SimpleType. Arrays, Class, complex values, ambiguous overloads and mutating operation names remain unavailable through GET.

GET requires an invocation handler implementing RemoteMethodResolver so the adapter can inspect only the explicitly published contract. The ReflectiveRemoteInvocationDispatcher supplies this metadata. Custom handlers without it remain fully usable through POST and answer GET requests with 405 Method Not Allowed and Allow: POST.

Arguments are supplied as query parameters. Stable indexed names are always available; Java parameter names compiled with -parameters are accepted as additional aliases:

GET /remoting/catalog/findById?arg0=4711

Query values are converted through SimpleType. Missing arguments, empty strings, unknown parameters and conversion failures are distinguished rather than guessed. The endpoint reports bad requests as 400, unknown subjects or operations as 404, methods not permitted for GET as 405, ambiguous overloads as 409 and invocation failures as 500. The response body remains an InvocationResponse, and GET responses carry Cache-Control: no-store.

A method admitted by a GET policy is a promise by the contract author that the operation is read-only and repeatable; this cannot be inferred reliably from a Java method name. Passwords, tokens and other secrets should remain in POST bodies because URLs are commonly retained by clients, proxies and server logs.

The original RemoteServer and RemoteClient API remains available for the legacy datagram protocol. The following socket-based example creates the server object first:

1
2
3
4
...
List<String> theServerList = new ArrayList<String>();
RemoteServer theServer = new RemoteServer();
...

Then you wait for your client to connect to your ServerSocket. From the Socket created upon connection you create an PrefetchBidirectionalStreamConnectionTransceiver which is used to open your Server:

1
2
3
4
5
6
7
8
...
ServerSocket theServerSocket = new ServerSocket( 5161 );
Socket theSocket = theServerSocket.accept();
PrefetchBidirectionalStreamConnectionTransceiver<Serializable> theServerTransceiver = new PrefetchBidirectionalStreamConnectionTransceiver<Serializable>();
theServerTransceiver.open( theSocket.getInputStream(), theSocket.getOutputStream() );
theServer.open( theServerTransceiver );
theServer.publishSubject( theServerList );
...

A Java-object-stream-backed legacy connection performs Java deserialization and therefore must be used only between trusted peers.

Next you setup your client. You create a Socket to your server from which you produce an PrefetchBidirectionalStreamConnectionTransceiver which is used to open your Client

1
2
3
4
5
6
7
...
RemoteClient theClient = new RemoteClient();
Socket theClientSocket = new Socket( "localhost", 5161 );
PrefetchBidirectionalStreamConnectionTransceiver<Serializable> theClientTransceiver = new PrefetchBidirectionalStreamConnectionTransceiver<Serializable>();
theClientTransceiver.open( theClientSocket.getInputStream(), theClientSocket.getOutputStream() );
theClient.open( theClientTransceiver );
...

Snippets of interest

Below find some code snippets which demonstrate the various aspects of using the refcodes-remoting artifact (and , if applicable, its offsprings). See also the example source codes of this artifact for further information on the usage of this artifact.

Access remote instances

Having prepared your setup as above, you now can invoke the methods provided by the theServerList on your client by retrieving the according proxy:

1
2
3
4
5
...
if ( theClient.hasProxy( List.class ) ) {
	List<?> theProxyList = theClient.getProxy( List.class );
}
...

Determine available remote instances

You may also list all published remote instances:

1
2
3
4
5
6
7
...
Iterator<Object> eProxies = theClient.proxies();
while ( eProxies.hasNext() ) {
	Object eProxy = eProxies.next();
	System.out.println( "Remote instance = " + eProxy );
}
...

Examples

Please refer to the example source code (as well as this example source code) for more examples on the usage of this artifact.

The above examples are tweaked to run as unit tests on a single machine!

Contribution guidelines

  • Report issues
  • Finding bugs
  • Helping fix bugs
  • Making code and documentation better
  • Enhancing the code

Who do I talk to?

Licensing Philosophy

This project follows a dual-licensing model designed to balance openness, pragmatism and fair attribution.

You may choose between the LGPL v3.0 or later and the Apache License v2.0 when using this software.

The intention behind this model is simple:

  • Enable use in both open-source and proprietary projects
  • Keep the codebase approachable and reusable
  • Ensure that improvements to the library itself remain available to the community
  • Preserve clear attribution to the original author and the ecosystem

Under the LGPL v3.0+, you are free to use this library in any application. If you modify the library itself, those modifications must be made available under the same license and must retain proper attribution.

Alternatively, the Apache License v2.0 allows broad use, modification and distribution, including commercial usage, provided that copyright notices and the accompanying NOTICE file are preserved.

This dual-licensing approach intentionally avoids artificial barriers while discouraging closed, uncredited forks of the core library. Contributions, improvements and refinements are encouraged to flow back into the project, benefiting both the community and downstream users.

For licensing questions, alternative licensing arrangements or commercial inquiries, please contact the copyright holder.