Netty - How to get server response in the client
Asked Answered
G

2

10

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.

Greenes answered 28/1, 2012 at 21:16 Comment(0)
S
5

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);
    }
}
Seaboard answered 29/1, 2012 at 6:54 Comment(1)
I'm trying to follow your guideline for registering listeners but Im getting a "incompatible types: String cannot be converted to DataChangeEvent"... what's wrong? thx.Jilli
O
1

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.

Oligopsony answered 28/1, 2012 at 21:27 Comment(5)
So for example, in the http client example the result is just dumped to System.out. What if you had a GUI you had to pass the result to? For example it had to be passed up to a JTextArea that the handler isn't aware of, or maybe a JDialog, etc. In other words, a JButton is pressed and the event calls the server through the client. How do you basically pass the result from the server back up to the JButton's calling method.Greenes
To add, because the JButton handler may display the result in JTextArea, maybe it will be a JDialog, etc.Greenes
You want to use callbacks(listeners) to handle responses, like hotpottao asych mode hotpotato.biasedbit.com, (which is also uses Netty), then you have to handle that inside the messageReceived :) , I think this kind of callbacks are not provided by default because they are more specific to the protocol/what application does ?Ashmore
Modified my answer with code to try and explain what I meant in my original answer. I'd have a controller that can update my UI how it sees fit, and pass that to the Handler. I wouldn't pass individual UI elements directly to the Handler because that's too much coupling between my low level network and the UI components I choose to use.Oligopsony
@chubbard That's the part I was missing. I didn't understand what you meant by: "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." In retrospect now that I get it, it makes a lot of sense, but trying to get it without seeing the code or still learning the framework, well unfortunately it was too hard to decipher.Greenes

© 2022 - 2024 — McMap. All rights reserved.