methods here } public class DataServerHandler extends {@link ChannelInboundMessageHandlerAdapter}<Message> {
private boolean loggedIn; {@code @Override}public void messageReceived( {@link ChannelHandlerContext} ctx, Message message) {{@link Channel} ch = e.getChannel();if (message instanceof LoginMessage) { authenticate((LoginMessage) message);
loggedIn = true; } else (message instanceof GetDataMessage) { if (
loggedIn) { ch.write(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 {@link ChannelInitializer#initChannel(Channel)}. public class DataServerInitializer extends {@link ChannelInitializer}< {@link Channel}> { {@code @Override}public void initChannel( {@link Channel} channel) {channel.pipeline().addLast("handler", new DataServerHandler()); } }
Using an attachment
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 an
attachment which is provided by {@link ChannelHandlerContext}:
public interface Message { // your methods here } {@code @Sharable}public class DataServerHandler extends {@link ChannelInboundMessageHandlerAdapter}<Message> { private final {@link AttributeKey}< {@link Boolean}> auth = new {@link AttributeKey}< {@link Boolean}>("auth"); // This handler will receive a sequence of increasing integers starting // from 1. {@code @Override}public void messageReceived( {@link ChannelHandlerContext} ctx, {@link Integer} integer) {{@link Attribute}< {@link Boolean}> attr = ctx.getAttr(auth); {@code @Override}public void messageReceived( {@link ChannelHandlerContext} ctx, Message message) {{@link Channel} ch = ctx.channel();if (message instanceof LoginMessage) { authenticate((LoginMessage) o); attr.set(true); } else (message instanceof GetDataMessage) { if (Boolean.TRUE.equals(attr.get())) { ch.write(fetchSecret((GetDataMessage) o)); } else { fail(); } } } ... }
Now that the state of the handler is stored as an attachment, you can add the same handler instance to different pipelines:
public class DataServerInitializer extends {@link ChannelInitializer}< {@link Channel}> { private static final DataServerHandler SHARED = new DataServerHandler(); {@code @Override}public void initChannel( {@link Channel} channel) {channel.pipeline().addLast("handler", SHARED); } }
The {@code @Sharable} annotation
In the examples above which used an attachment, you might have noticed the {@code @Sharable} annotation.
If a {@link ChannelHandler} is annotated with the {@code @Sharable}annotation, it means you can create an instance of the handler just once and add it to one or more {@link ChannelPipeline}s multiple times without a race condition.
If this annotation is not specified, 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.
This annotation is provided for documentation purpose, just like the JCIP annotations.
Additional resources worth reading
Please refer to the {@link ChannelHandler}, and {@link 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.