I'm trying to pass an object from one handler to another using the ChannelContext.attr() method:
private class TCPInitializer extends ChannelInitializer<SocketChannel>
{
@Override
public void initChannel(final SocketChannel ch) throws Exception
{
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ReadTimeoutHandler(mIdleTimeout));
pipeline.addLast(new HostMappingHandler<SyslogHandler>(mRegisteredClients,
mChannels));
pipeline.addLast(new DelimiterBasedFrameDecoder(MAX_FRAME_SIZE,
Delimiters.lineDelimiter()));
pipeline.addLast(new Dispatcher());
}
}
The HostMappingHandler is a template class that maps hosts to data:
public class HostMappingHandler<T> extends ChannelStateHandlerAdapter
{
public static final AttributeKey<HostMappedObject> HOST_MAPPING =
new AttributeKey<HostMappedObject>("HostMappingHandler.attr");
private final ChannelGroup mChannels;
private final Map<String, T> mMap;
public HostMappingHandler(Map<String, T> registrations,
ChannelGroup channelGroup)
{
mChannels = channelGroup;
mMap = registrations;
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception
{
SocketAddress addr = ctx.channel().remoteAddress();
T mappedObj = null;
if (addr instanceof InetSocketAddress)
{
String host = ((InetSocketAddress) addr).getHostName().toLowerCase();
mappedObj = mMap.get(host);
}
if (mappedObj != null)
{
// Add the channel to the list so it can be easily removed if unregistered
mChannels.add(ctx.channel());
// Attach the host-mapped object
ctx.attr(HOST_MAPPING).set(new HostMappedObject<T>(mappedObj));
}
else
{
log.debug("Bad host [" + addr + "]; aborting connection request");
ctx.channel().close();
}
super.channelRegistered(ctx);
}
@Override
public void inboundBufferUpdated(ChannelHandlerContext ctx) throws Exception
{
// Find the parser for this host. If the host is no longer registered,
// disconnect the client.
if (ctx.channel().remoteAddress() instanceof InetSocketAddress)
{
InetSocketAddress addr = (InetSocketAddress) ctx.channel().remoteAddress();
T handler = mMap.get(addr.getHostName().toLowerCase());
if (handler == null)
{
log.debug("Host no longer registered");
ctx.channel().close();
}
else
{
log.debug("Sanity Check: " + ctx.attr(HostMappingHandler.HOST_MAPPING).get());
}
}
super.inboundBufferUpdated(ctx);
}
// ==================================================
public static class HostMappedObject<C>
{
private final C mObj;
public HostMappedObject(final C in)
{
mObj = in;
}
public C get()
{
return mObj;
}
}
}
And the dispatcher is currently very simple:
private static class Dispatcher extends ChannelInboundMessageHandlerAdapter<Object>
{
@Override
public void messageReceived(final ChannelHandlerContext ctx,
final Object msg)
throws Exception
{
HostMappingHandler.HostMappedObject wrapper =
ctx.attr(HostMappingHandler.HOST_MAPPING).get();
log.debug("Received: " + wrapper);
}
}
Incidentally the initializer and dispatcher are private classes of a "server" object. However when I run a unit test that registers the localhost and attempts to connect to it, it fails and my debug output shows:
HostMappingHandler.inboundBufferUpdated: Sanity Check: test.comms.netty.HostMappingHandler$HostMappedObject@eb017e
Dispatcher.messageReceived: Received: null
So the mapping is definitely preserved between channel registration and receiving an inbound message in the HostMapper class, and investigating a little further in the debugger shows that the attribute map for the context in Dispatcher does have an entry - the problem is that the value is NULL and not the object.
Am I missing something obvious, or just an alpha bug?
Edit:
I've worked around the problem for now by attaching the data to the Dispatcher's context, by having the HostMappingHandler also take a class to attach to. The bug appears to be that setting an attribute in a handler's ChannelHandlerContext causes the attribute to appear in another handler with the correct key but NULL value. Without knowing the desired workflow, the bug is that either the key shouldn't appear at all, or the value should not be null.
public HostMappingHandler(final Map<String, T> registrations,
final ChannelGroup channelGroup,
final Class<? extends ChannelHandler> channelHandler)
{
mChannels = channelGroup;
mMap = registrations;
mHandler = channelHandler;
}
//...
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception
{
//...
// Attach the host-mapped object
ctx.pipeline().context(mHandler).attr(HOST_MAPPING).set(new HostMappedObject<T>(mappedObj));
//...
}