ChannelContext.attr() メソッドを使用して、あるハンドラーから別のハンドラーにオブジェクトを渡そうとしています。
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());
}
}
HostMappingHandler は、ホストをデータにマップするテンプレート クラスです。
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;
}
}
}
ディスパッチャーは現在非常にシンプルです。
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);
}
}
ちなみに、イニシャライザとディスパッチャは「サーバー」オブジェクトのプライベート クラスです。ただし、ローカルホストを登録してそれに接続しようとする単体テストを実行すると、失敗し、デバッグ出力に次のように表示されます。
HostMappingHandler.inboundBufferUpdated: Sanity Check: test.comms.netty.HostMappingHandler$HostMappedObject@eb017e
Dispatcher.messageReceived: Received: null
そのため、マッピングはチャネル登録と HostMapper クラスでのインバウンド メッセージの受信の間で確実に保持されます。デバッガーでさらに調査すると、Dispatcher のコンテキストの属性マップにエントリがあることがわかります。問題は、値が NULL であることです。オブジェクトではありません。
明らかな何かが欠けているのでしょうか、それとも単にアルファ版のバグですか?
編集:
HostMappingHandler にもアタッチ先のクラスを持たせることで、Dispatcher のコンテキストにデータをアタッチすることで、今のところ問題を回避しました。このバグは、ハンドラーの ChannelHandlerContext で属性を設定すると、その属性が別のハンドラーに表示され、キーは正しいが値が NULL になるというバグのようです。目的のワークフローがわからない場合、キーがまったく表示されないか、値が 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));
//...
}