Netty - How to get server response in the client - java

I'm mostly there with Netty but one concept is still alluding me, and I can't find anything in the tutorials and so on. Firstly I do understand that Netty is asynchronous, but there must be a way for a client to call the server and be able to get a response beyond the handler. Let me explain more.
I have a client as illustrated below. And please note that I understand it's bootstrapped and a new connection is established on each call, that's just there to make the example smaller and more succinct. Please ignore that fact.
Client.java
// ServerResponse is a result from the server, in this case
// a list of users of the system (ignore that each time it's all bootstrapped).
public User[] callServerForInformationFromGUIWidget()
{
ClientBootstrap bootstrap = new ClientBootstrap(...);
bootstrap.setPipelineFactory(...);
ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));
Channel channel = future.awaitUninterruptibly().getChannel();
// Where request is a POJO sent to the server,
// with a request such as get me a list of users
RequestPojo request = new RequestPojo(requestUserListCommand);
ChannelFuture lastWriteFuture = channel.write(request);
if(lastWriteFuture != null)
lastWriteFuture.awaitUninterruptibly();
}
Now I understand how to get the data on the server, and fire back the result. The only thing is how do I handle it on the client side? Yes the clientHandler class can do something like the following:
ClientHandler.java
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
{
User[] users = (User[])e.getMessage();
}
The problem is how does the client code actually get that result? All the examples are similar to a chat service, where the event fires off something else on the client that's not waiting on a response. Even the http client example I found lacking this. The documentation overall is really good, but it's lacking on how to do callbacks. Anyways, in this case I need the client to get the response from the server, and based on the results it will do what it needs.
In other words, how do I write the client to do something like this:
IdealClient.java
// ServerResponse is a result from the server, in this case
// a list of users of the system.
public User[] callServerForInformationFromGUIWidget()
{
...
RequestPojo request = new RequestPojo(requestUserListCommand);
ChannelFuture lastWriteFuture = channel.write(request);
if(lastWriteFuture != null)
lastWriteFuture.awaitUninterruptibly();
User[] users = resultFromCallToServer();
performSomeAction(users);
}
Because the handler doesn't know who is looking for the answer, or who asked the question. And if it's done in the handler, than how?
Back to my comments about the examples, the http client (and handler) examples just dump the result to System.out. If you had a GUI how would you pass the result from your request up to the GUI? I never saw any examples for this.

Jestan is correct. In my case I have a client that need to process price tick data. I use Antlr for the parsing. I fire my events in my parser, but in my case my protocol is String based. Below is an example without Antlr, I pass the String message in your case it could be the users.
//----------------- Event --------------
public class DataChangeEvent {
private String message;
public DataChangeEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
//----------------- Listener --------------
public interface DataChangeListenter {
public void dataChangeEvent(DataChangeEvent event);
}
//----------------- Event Handler that fires the dataChange events --------------
// This class needs to be static since you need to register all your classes that want to be notified of data change events
public class DataChangedHandler {
private static List<DataChangeListenter> listeners = new ArrayList<DataChangeListenter>();
public static void registerDataChangeListener(DataChangeListenter listener) {
listeners.add(listener);
}
public static void fireDataChange(DataChangeEvent dataChangeEvent) {
for(DataChangeListenter listenter : listeners) {
listenter.dataChangeEvent(dataChangeEvent);
}
}
}
//----------------- Example class that implements the listener and registers itself for events --------------
public class ProcessMessage implements DataChangeListenter {
public ProcessMessage() {
DataChangedHandler.registerDataChangeListener(this);
}
public void dataChangeEvent(DataChangeEvent event) {
//Depending on your protocal, I use Antlr to parse my message
System.out.println(event.getMessage());
}
}
//---------------- Netty Handler -----------
public class TelnetClientHandler extends SimpleChannelHandler {
private static final Logger logger = Logger.getLogger(TelnetClientHandler.class.getName());
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
String message = (String) e.getMessage();
DataChangedHandler.fireDataChange(message);
}
}

You have to handle it in the Handler with messageReceived(). I'm not sure what your issue is exactly. My guess is you have a response to a request that changes depending on what request was made? Maybe a concrete description of something you are doing of a response that has to know what request it came from. One thing you might be able to do is to pass a long living object the handler that knows the outstanding request, and it can match up the response when it receives it. The pipeline factory method can pass a reference to a manager type object to the Handler.
This was pretty much what I was trying to say. Your Handler is created in the PipelineFactory which is easy to pass parameters to the Handler from there:
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.nulDelimiter()));
pipeline.addLast("decoder", new XMLDecoder() );
pipeline.addLast("encoder", new XMLEncoder() );
// notice here I'm passing two objects to the Handler so it can
// call the UI.
pipeline.addLast("handler", new MyHandler(param1, param2));
return pipeline;
}
});
When you create your pipeline you'll add your Handler upon a new connection. Simply pass one or more objects that allows it to communicate back to the UI or a controller.

Related

Is there any better way to call a method from netty than implement methods inside the handler?

This is a simple question. Netty seems to be a great tool for sending information between servers and clients. I want to send informations, and also events..or rather called instructions what do to.
public class PojoServerHandler extends ChannelInboundHandlerAdapter {
private Logger logger = LoggerFactory.getLogger(getClass());
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Message body = (Message) msg;
logger.info("server read msg id:{}, body:{}", body.getId(), body.getBody());
Message response = new Message();
response.setId(1024);
response.setFrom("server");
response.setBody("hello from server");
ctx.writeAndFlush(response);
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.error("server caught exception", cause);
ctx.close();
}
}
I have this code here and it describes a handler for a server. When the client send its message to the server. The method channelRead going to be called directly.
Is it smart to implements a switch case inside channelRead if I want to use other functions around my application, or can Netty do that in another way?
It is possible to have multiple handlers each doing a different task instead of having a switch case. You can simply call
ctx.fireChannelRead(msg);
if you plan not to handle the message in a particular handler, then it will trigger the next handler in the pipeline.

How do i get the status code for a response i subscribe to using the JDK's HttpClient?

Java 11 introduced a new standard HTTP client. A request is sent using HttpClient:send, which returns a HttpResponse.
The HttpResponse::statusCode method can be used to find the HTTP status of the response.
HttpClient::send also takes a BodyHandler which is used to handle the body of the response. A useful family of BodyHandlers are those which wrap a Flow.Subscription, created with BodyHandlers::fromSubscriber and relatives. These are a useful way of dealing with infinite streams of data, such as server-sent events.
However, it seems that if you use one of these BodyHandlers, the flow is delivered on the thread which called HttpClient::send, and so for an infinite stream, that method never returns. Since it never returns, you never get a HttpResponse with which you can determine the status.
So, how do i get the status code for a response i subscribe to?
As noted in the documentation, these BodyHandlers
do not examine the status code, meaning the body is always accepted
with a hint that
a custom handler can be used to examine the status code and headers, and return a different body subscriber, of the same type, as appropriate
There does not seem to be a convenience method or class for this, but such a thing is moderately straightforward:
// a subscriber which expresses a complete lack of interest in the body
private static class Unsubscriber implements HttpResponse.BodySubscriber<Void> {
#Override
public CompletionStage<Void> getBody() {
return CompletableFuture.completedStage(null);
}
#Override
public void onSubscribe(Flow.Subscription subscription) {
subscription.cancel();
}
#Override
public void onNext(List<ByteBuffer> item) {}
#Override
public void onError(Throwable throwable) {}
#Override
public void onComplete() {}
}
// wraps another handler, and only uses it for an expected status
private static HttpResponse.BodyHandler<Void> expectingStatus(int expected, HttpResponse.BodyHandler<Void> handler) {
return responseInfo -> responseInfo.statusCode() == expected ? handler.apply(responseInfo) : new Unsubscriber();
}
// used like this
Flow.Subscriber<String> subscriber = createSubscriberSomehow();
HttpResponse<Void> response = HttpClient.newHttpClient()
.send(HttpRequest.newBuilder()
.uri(URI.create("http://example.org/api"))
.build(),
expectingStatus(200, HttpResponse.BodyHandlers.fromLineSubscriber(subscriber)));

How to read Message in netty in other class

I want to read a message at a specific position in an class other than InboundHandler. I can't find a way to read it expect in the channelRead0 method, which is called from the netty framework.
For example:
context.writeMessage("message");
String msg = context.readMessage;
If this is not possible, how can I map a result, which I get in the channelRead0 method to a specific call I made in another class?
The Netty framework is designed to be asynchronously driven. Using this analogy, it can handle large amount of connections with minimal threading usage. I you are creating an api that uses the netty framework to dispatch calls to a remote location, you should use the same analogy for your calls.
Instead of making your api return the value direct, make it return a Future<?> or a Promise<?>. There are different ways of implementing this system in your application, the simplest way is creating a custom handler that maps the incoming requests to the Promises in a FIFO queue.
An example of this could be the following:
This is heavily based on this answer that I submitted in the past.
We start with out handler that maps the requests to requests in our pipeline:
public class MyLastHandler extends SimpleInboundHandler<String> {
private final SynchronousQueue<Promise<String>> queue;
public MyLastHandler (SynchronousQueue<Promise<String>> queue) {
super();
this.queue = queue;
}
// The following is called messageReceived(ChannelHandlerContext, String) in 5.0.
#Override
public void channelRead0(ChannelHandlerContext ctx, String msg) {
this.queue.remove().setSuccss(msg);
// Or setFailure(Throwable)
}
}
We then need to have a method of sending the commands to a remote server:
Channel channel = ....;
SynchronousQueue<Promise<String>> queue = ....;
public Future<String> sendCommandAsync(String command) {
return sendCommandAsync(command, new DefaultPromise<>());
}
public Future<String> sendCommandAsync(String command, Promise<String> promise) {
synchronized(channel) {
queue.offer(promise);
channel.write(command);
}
channel.flush();
}
After we have done our methods, we need a way to call it:
sendCommandAsync("USER anonymous",
new DefaultPromise<>().addListener(
(Future<String> f) -> {
String response = f.get();
if (response.startWidth("331")) {
// do something
}
// etc
}
)
);
If the called would like to use our a api as a blocking call, he can also do that:
String response = sendCommandAsync("USER anonymous").get();
if (response.startWidth("331")) {
// do something
}
// etc
Notice that Future.get() can throw an InterruptedException if the Thread state is interrupted, unlike a socket read operation, who can only be cancelled by some interaction on the socket. This exception should not be a problem in the FutureListener.

How to send more then one response to client from Netty

I want to send more than one response to client based on back end process. But in Netty examples I saw echo server is sending back the response at the same time.
My requirement is, I need to validate the client and send him OK response, then send him the DB updates when available.
How can I send more responses to client? Pls direct me to an example or any guide?
at every point in your pipeline you can get the pipeline Channel object from the MessageEvent object (or ChannelEvent) which is passed from handler to handler. you can use this information to send multiple responses at different points in the pipeline.
if we take the echo server example as a base, we can add a handler which send the echo again (that can be done also in the same handler, but the example is to show that multiple handlers can respond).
public class EchoServerHandler extends ChannelHandlerAdapter {
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
Channel ch = e.getChannel();
// first message
ch.write(e.getMessage());
}
// ...
}
public class EchoServerHandler2 extends ChannelHandlerAdapter {
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
Channel ch = e.getChannel();
// send second message
ch.write(e.getMessage());
}
// ...
}
You can do that as long as you have the reference to the relevant Channel (or ChannelHandlerContext). For example, you can do this:
public class MyHandler extends ChannelHandlerAdapter {
...
public void channelRead(ctx, msg) {
MyRequest req = (MyRequest) msg;
ctx.write(new MyFirstResponse(..));
executor.execute(new Runnable() {
public void run() {
// Perform database operation
..
ctx.write(new MySecondResponse(...));
}
}
}
...
}
You can do this as long as Netty doesn't close the Channel. Its better you call close() yourself when you're done.
Here's a sample: https://stackoverflow.com/a/48128514/2557517

How attach an object to a channel before handlers receive events with Netty?

I have a synchronization issue regarding a bind request and a upstream handler that receives a channelBound event. I need to attach an object to the channel before the handler can ever receive the channelBound event due to the fact that the handler needs to use the object to handle the callback. Example below.
Handler example:
public class MyClientHandler extends SimpleChannelUpstreamHandler {
#Override
public void channelBound(ChannelHandlerContext ctx, ChannelStateEvent e) {
/* Problem: This can occur while the channel attachment is still null. */
MyStatefulObject obj = e.getChannel().getAttachment();
/* Do important things with attachment. */
}
}
Main example:
ClientBootstrap bootstrap = ... //Assume this has been configured correctly.
ChannelFuture f = bootstrap.bind(new InetSocketAddress("192.168.0.15", 0));
/* It is possible the boundEvent has already been fired upstream
* by the IO thread when I get here.
*/
f.getChannel().setAttachment(new MyStatefulObject());
Possible Soultions
I've come up with a couple of solutions to get around this but they both kind of "smell" which is why I'm here asking if anyone has a clean way of doing this.
Solution 1: Spin or block in the channelBound callback until the attachment is not null. I don't like this solution because it ties up an I/O worker.
Solution 2: Make MyClientHandler in to a bi-directional handler and get the attachment using a ThreadLocal in a bindRequested downstream callback. I don't like this because it relies on a Netty implementation detail that the requesting thread is used to fire the bindRequested event.
I find solution 1 to be more tolerable than solution 2. So if that is what I need to do I will.
Is there an easy way to get a channel reference without requesting a bind or connect first?
Yes it is possible that, boundEvent can get the handler before you set the attachment to the channel.
If the attachment is very specific to every channel your opening, then you can register a channel future listener on bind future and set the attachment on operationComplete()
by setting up everything without using BootStraps. Following is a modified version of EchoClient Example, It works fine.
// Configure the client.
final NioClientSocketChannelFactory clientSocketChannelFactory = new NioClientSocketChannelFactory(
Executors.newCachedThreadPool());
// Set up the pipeline factory.
final ChannelPipelineFactory channelPipelineFactory = new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(
new MyClientHandler());
}
};
ChannelPipeline pipeline = channelPipelineFactory.getPipeline();
final Channel channel = clientSocketChannelFactory.newChannel(pipeline);
channel.getConfig().setPipelineFactory(channelPipelineFactory);
channel.getConfig().setOption("tcpNoDelay", true);
channel.getConfig().setOption("receiveBufferSize", 1048576);
channel.getConfig().setOption("sendBufferSize", 1048576);
ChannelFuture boundFuture = Channels.future(channel);
boundFuture.addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
future.getChannel().setAttachment(new Object());// set the channel attachment
}
}
});
channel.getPipeline().sendDownstream(new DownstreamChannelStateEvent(channel, boundFuture, ChannelState.BOUND, new InetSocketAddress(host, 0)));
ChannelFuture connectFuture = Channels.future(channel);
channel.getPipeline().sendDownstream(new DownstreamChannelStateEvent(channel, connectFuture, ChannelState.CONNECTED, new InetSocketAddress(host, port)));
channel.getCloseFuture().awaitUninterruptibly();
clientSocketChannelFactory.releaseExternalResources();// do not forget to do this
Make your ChannelPipelineFactory implementation accept a constructor parameter and specify the attachment there. Place a handler in front of all other handlers and make the first handler's channelOpen() method sets the attachment, and then remove the first handler from the pipeline, because it's not needed anymore.

Categories