As part of my protocol, I'd like the client to send its version number when a new connection is established. I'd like this to be done in a separate handler in the pipeline, so please bear with me as this may be a fairly basic question but I'm not sure how to do it. The other thing is that I'd like to then be able to send POJO's back and forth through the connection (pipeline). Also I'd love to add an Authentication handler. Anyways, right now I'm getting some kind of error and I'm pretty sure it's because the version check is not being properly digested from the pipeline.
Basically the code I have below is setup to send "Hello World" which the server prints out after the version has been checked when the connection has been established. At least in theory, in reality this isn't quite working ;)
Currently I have:
Client.java
public static void main(String[] args)
{
...
// Set up the pipeline factory.
bootstrap.setPipelineFactory(new ChannelPipelineFactory()
{
@Override
public ChannelPipeline getPipeline() throws Exception
{
return Channels.pipeline(
new ObjectEncoder(),
new ObjectDecoder(),
new VersionClientHandler(),
new BusinessLogicClientHandler());
}
});
...
// The idea is that it will all be request and response. Much like http but with pojo's.
ChannelFuture lastWriteFuture = channel.write("Hello world".getBytes());
if (lastWriteFuture != null)
{
System.out.println("waiting for message to be sent");
lastWriteFuture.awaitUninterruptibly();
}
...
}
VersionClientHandler.java
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
{
ChannelBuffer versionBuffer = ChannelBuffers.buffer(VERSION_STRING_LENGTH);
versionBuffer.writeBytes("v123.45a".getBytes());
// If I understand correctly, the next line says use the rest of the stream to do what you need to the next Handler in the pipeline?
Channels.write(ctx, e.getFuture(), versionBuffer);
}
BusinessLogicClientHandler.java
Not really doing anything at this point. Should it?
Server.java
public static void main(String[] args)
{
...
public ChannelPipeline getPipeline() throws Exception
{
return Channels.pipeline(
new ObjectEncoder(),
new ObjectDecoder(),
new VersionServerHandler(),
new BusinessLogicServerHandler());
}
...
}
VersionServerHandler.java
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
{
ChannelBuffer versionBuffer = ChannelBuffers.buffer(VERSION_NUMBER_MAX_SIZE);
System.out.println("isReadable - messageReceived: " + versionBuffer.readable()); // returns false???
// Basically I want to read it and confirm the client and server versions match.
// And if the match fails either send a message or throw an exception
// How do I also pass on the stream to the next Handler?
}
BusinessLogicServerHandler.java
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
{
e.getMessage();
byte[] message = (byte[])e.getMessage(); // "Hello World" in byte[] from Client.java
}
So basically what I want is that the version number is passed and validated when the channel is connected as part of the communication protocol. All done automatically behind the scenes. Similarly I'd love to pass the authentication mechanism this way.
I did see some code which looked somewhat like what I wanted to do with the Secure Chat example, but I couldn't really figure it out. Any help on how to setup this code would be really appreciated. I know I could do it all in one massive handler, but that's the point of the pipeline, to break it up into units that make logical sense.
I found the solution!!!
There were a number of issues.
On VersionClientHandler, the new code is:
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
{
String version = "v123.45a";
ChannelBuffer versionBuffer = ChannelBuffers.buffer(VERSION_STRING_LENGTH);
versionBuffer.writeBytes(version.getBytes());
e.getChannel().write(version);
}
Notice the last line, e.getChannel().write(version);
instead of Channels.write(ctx, e.getFuture(), versionBuffer);
I'm not sure of the why. In fact, I'm about to start looking into why I have the ChannelBuffers code there because it doesn't seem to do anything...
On VersionServerHandler.java I now have:
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
{
String versionCheck = (String)e.getMessage();
System.out.println("VersionServerHandler - " + versionCheck);
ctx.getPipeline().remove(VersionServerHandler.class);
}
Notice I don't read through the buffer anymore, I just do e.getMessage()
and cast to the correct type of object. Above this I added ctx.getPipeline().remove(VersionServerHandler.class);
which is there to remove that handler from any further processing. It's no longer required after the initial connection. Thanks to Dennis for that tip.
Conclusion
The rest is much as I expected. the key is that I wasn't correctly understanding how to read the buffers and pass the info around. And the error messages and examples weren't quite clear. As soon as you add the POJO channels from Netty to your pipeline, you need to start dealing only in objects, for all handlers. I missed that one. The concepts were right, it's just how I tried to read the data from the channels that was wrong.
The other big tip, was to remove the handlers from the pipeline if you don't need them after the initial connection. I assume the same will be true for an authentication handler. It would be great to have confirmation here, but I'll have to figure that one out later.