Interface ChannelHandler

  • All Known Implementing Classes:
    ChannelHandlerAdapter, ChannelInitializer, CombinedChannelDuplexHandler, SimpleChannelInboundHandler, SimpleUserEventChannelHandler

    public interface ChannelHandler
    Handles an I/O event or intercepts an I/O operation, and forwards it to its next handler in its ChannelPipeline.

    The context object

    A ChannelHandler is provided with a ChannelHandlerContext object. A ChannelHandler is supposed to interact with the ChannelPipeline it belongs to via a context object. Using the context object, the ChannelHandler can pass events upstream or downstream, modify the pipeline dynamically, or store the information (using AttributeKeys) which is specific to the handler.

    State management

    A ChannelHandler often needs to store some stateful information. The simplest and recommended approach is to use member variables:
     public interface Message {
         // your methods here
     }
    
     public class DataServerHandler extends SimpleChannelInboundHandler<Message> {
    
         private boolean loggedIn;
    
         @Override
         public void messageReceived(ChannelHandlerContext ctx, Message message) {
             if (message instanceof LoginMessage) {
                 authenticate((LoginMessage) message);
                 loggedIn = true;
             } else (message instanceof GetDataMessage) {
                 if (loggedIn) {
                     ctx.writeAndFlush(fetchSecret((GetDataMessage) message));
                 } else {
                     fail();
                 }
             }
         }
         ...
     }
     
    Because the handler instance has a state variable which is dedicated to one connection, you have to create a new handler instance for each new channel to avoid a race condition where a unauthenticated client can get the confidential information:
     // Create a new handler instance per channel.
     // See ChannelInitializer.initChannel(Channel).
     public class DataServerInitializer extends ChannelInitializer<Channel> {
         @Override
         public void initChannel(Channel channel) {
             channel.pipeline().addLast("handler", new DataServerHandler());
         }
     }
    
     

    Using AttributeKeys

    Although it's recommended to use member variables to store the state of a handler, for some reason you might not want to create many handler instances. In such a case, you can use AttributeKeys which is provided by ChannelHandlerContext:
     public interface Message {
         // your methods here
     }
    
     public class DataServerHandler extends SimpleChannelInboundHandler<Message> {
         private final AttributeKey<Boolean> auth =
               AttributeKey.valueOf("auth");
    
         @Override
         public boolean isSharable() {
             return true;
         }
         @Override
         public void channelRead(ChannelHandlerContext ctx, Message message) {
             Attribute<Boolean> attr = ctx.attr(auth);
             if (message instanceof LoginMessage) {
                 authenticate((LoginMessage) o);
                 attr.set(true);
             } else (message instanceof GetDataMessage) {
                 if (Boolean.TRUE.equals(attr.get())) {
                     ctx.writeAndFlush(fetchSecret((GetDataMessage) o));
                 } else {
                     fail();
                 }
             }
         }
         ...
     }
     
    Now that the state of the handler is attached to the ChannelHandlerContext, you can add the same handler instance to different pipelines:
     public class DataServerInitializer extends ChannelInitializer<Channel> {
    
         private static final DataServerHandler SHARED = new DataServerHandler();
    
         @Override
         public void initChannel(Channel channel) {
             channel.pipeline().addLast("handler", SHARED);
         }
     }
     

    The isSharable() method

    In the example above which used an AttributeKey, you might have noticed the isSharable() method is override to return true.

    If the isSharable() is returningtrue, it means you can create an instance of the handler just once and add it to one or more ChannelPipelines multiple times without a race condition.

    If this method is not implemented and return false, you have to create a new handler instance every time you add it to a pipeline because it has unshared state such as member variables.

    Additional resources worth reading

    Please refer to the ChannelHandler, and ChannelPipeline to find out more about inbound and outbound operations, what fundamental differences they have, how they flow in a pipeline, and how to handle the operation in your application.