I have a server client application (Java EE & Android), communication via websockets. Communication works and also the protocol itself for sending objects as json, which will be correctly wrapped, serialized, sent, deserialized, unwrapped and rebuilt. Both applications are using another library project with all possible request and response classes.
Now to my problem: The library should also implement a strategy for non-blocking communication, but transparent request-response implementation. Probably I'm not the first with that problem, so I think there might be some nice implementations out there :).
What I want:
// server should sleep 5000ms and then return 3*3
Future<Integer> f1 = server.put(
new SleepAndReturnSquareRequest(5000, 3),
new FutureCallback<Integer>{
public void onSuccess(Integer square) {
runOnUiThread(new Runnable{
// Android Toast show square
});
}
// impl onFailure
}
);
Future<Date> f2 = server.put(
new TimeRequest(),
new FutureCallback<Date>{
public void onSuccess(Date date) {
// called before other onSuccess
}
// impl onFailure
}
);
// e.g. when the activity in android changes I'll cancel all futures. That means no more callbacks and (later) if possible client sends cancellations to the server for long requests.
The code should send a SleepAndReturnRequest
and then a TimeRequest
of course non-blocking. The first request takes 5 seconds, the second request nearly zero millis. I expect the implementation to call the second callback immediately after the response is received, while the first callback is called after about 5 seconds. The implementation is responsible for request-response matching on the requesting side.
What I've tried and thought about:
Google's guava listenable future
would be a good approach for the 'responding side' I think, because it's just a task running on any thread, sending the result back at the end. That should be easier.
For the 'requesting side' I need some implementation which adds a unique identifier to a message to be able to match the response. Hopefully you can tell me some packages doing that job.
Thank you for helping.
// edit:
I think my question was misunderstood or it isn't precise enough. Think of how to implement GET or POST via websocket. Each GET/POST request has one response and then the connection is closed. Clients connect to a specific port, server takes a thread from a thread-pool, processes the request and responds. The matching of request to response I think is done in the transport layer #4.
Since I want to use websockets, I have to implement that matching in software layer 7.
Here are some steps I'm implementing. K
is the unique key type, V
is the generic type of the content of a message. That could be a string, byte stream, whatever.
public class Synchronizer<K, V> implements UniqueMessageListener<K, V> {
private final ConcurrentMap<K, FutureCallback<V>> callbackMap = new ConcurrentHashMap<>();
private final ListeningExecutorService executor;
private final UniqueMessageFactory<K, V> factory;
private final UniqueMessageSender<K, V> sender;
private UniqueMessageReceiver<K, V> receiver = null;
public Synchronizer(
ListeningExecutorService executor,
UniqueMessageFactory<K, V> factory,
UniqueMessageSender<K, V> sender
) {
this.executor = executor;
this.factory = factory;
this.sender = sender;
}
public void register(UniqueMessageReceiver<K, V> receiver) {
unregister();
this.receiver = receiver;
receiver.addListener(this);
}
public void unregister() {
if(receiver != null) {
receiver.removeListener(this);
receiver = null;
}
}
public Future<V> put(Message<V> message, final FutureCallback<V> callback) {
final UniqueMessage<K, V> uniqueMessage = factory.create(message);
final Future<Boolean> sendFuture = sender.send(uniqueMessage);
final ListenableFuture<Boolean> listenableSendFuture =
JdkFutureAdapters.listenInPoolThread(sendFuture, executor);
listenableSendFuture.addListener(
new Runnable() {
@Override
public void run() {
try {
if(listenableSendFuture.get() == true) {
callbackMap.put(
uniqueMessage.getId(),
callback
);
} else {
// maybe try it later again?
}
} catch(Exception e) {
// ...
}
}
},
executor
);
// implement cancel
return new SynchronizeFuture<>(
listenableSendFuture,
callback
);
}
@Override
public void onReceive(UniqueMessage<K, V> message) {
K id = message.getId();
FutureCallback<V> callback;
callback = callbackMap.remove(id);
if(callback != null) {
callback.onSuccess(message.getContent());
}
}
}
There is a lot to test for me, but I think it will work.