0

My app should do the next:

  1. Send a POST request to server to get the token.
  2. Connect to the websocket using this token in the headers while handshake.

Short question: To activate WebSocketClientProtocolHandler I have to fire event ctx.fireChannelActive() but from channelRead method because in this method I receive token from server . Is it correct place?

I implemented custom ChannelInboundHandlerAdapter and override:

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    super.channelActive(ctx);

    authenticator.authenticate(ctx.channel()).addListener(new ChannelFutureListener() {
        @Override
        public void operationComplete(ChannelFuture channelFuture) throws Exception {
            if (!channelFuture.isSuccess()) {
                authPromise.tryFailure(channelFuture.cause());
                ctx.fireExceptionCaught(new RuntimeException("Auth is failed."));
            } else {
                ctx.fireUserEventTriggered("Auth is successful");
            }
        }
    });
}

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    if (!(msg instanceof FullHttpResponse)) {
        ctx.fireChannelRead(msg);
    }

    FullHttpResponse response = (FullHttpResponse) msg;
    try {
        authenticator.finishAuthentication(ctx.channel(), response);
        authPromise.trySuccess();
        ctx.pipeline().remove(this);
        ctx.fireChannelActive();
    } finally {
        response.release();
    }
}

Authenticator class adds needed handlers, sends POST request and then it should parse response from server and change the pipeline.

public class Authenticator {

private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());

private final ObjectMapper mapper = new ObjectMapper();
private final MessengerConfig messengerConfig;

public Authenticator(MessengerConfig messengerConfig) {
    this.messengerConfig = messengerConfig;
}

public ChannelFuture authenticate(Channel channel) {
    this.preCheck(channel);
    return this.authenticate(channel, channel.newPromise());
}

private void preCheck(Channel channel) {
    ChannelPipeline pipeline = channel.pipeline();

    HttpClientCodec httpClientCodec = pipeline.get(HttpClientCodec.class);
    if (httpClientCodec == null) {
        LOGGER.warn("Pipeline does not contain HttpClientCodec.");
        pipeline.addFirst(HttpClientCodec.class.getName(), new HttpClientCodec());
        LOGGER.info("HttpClientCodec was added to pipeline.");
    }

    HttpObjectAggregator httpObjectAggregator = pipeline.get(HttpObjectAggregator.class);
    if (httpObjectAggregator == null) {
        LOGGER.warn("Pipeline does not contain HttpObjectAggregator.");
        pipeline.addAfter(
                HttpClientCodec.class.getName(),
                HttpObjectAggregator.class.getName(),
                new HttpObjectAggregator(8192)
        );
        LOGGER.info("HttpObjectAggregator was added to pipeline.");
    }
}

private ChannelFuture authenticate(Channel channel, ChannelPromise promise) {

    HttpRequest request = createAuthRequest();

    try {
        channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                if (channelFuture.isSuccess()) {
                    promise.setSuccess();
                } else {
                    promise.setFailure(new RuntimeException(""));
                }
            }
        });
    } catch (Exception e) {
        LOGGER.error("Error", e);
    }

    return promise;
}

public void finishAuthentication(Channel channel, FullHttpResponse response) {
    ByteBuf content = response.content();
    AuthenticationData authenticationData = null;
    try {
        authenticationData = this.mapper.readValue(content.toString(CharsetUtil.UTF_8), AuthenticationData.class);
    } catch (JsonProcessingException e) {
        LOGGER.error("Can't parse authentication data.", e);
        throw new RuntimeException((e));
    }
    LOGGER.info(Objects.toString(authenticationData));

    DefaultWebSocketClientProtocolHandlerFactory factory = new DefaultWebSocketClientProtocolHandlerFactory();
    WebSocketClientProtocolHandler handler = factory.getHandler(this.messengerConfig, authenticationData);

    ChannelPipeline pipeline = channel.pipeline();
    pipeline.addLast(WebSocketClientProtocolHandler.class.getName(), handler);
    LOGGER.info("WebSocketClientProtocolHandler was added.");
    pipeline.addLast(MessageHandler.class.getName(), new MessageHandler());
    LOGGER.info("MessageHandler was added.");
}

So here I have two stages:

  1. Auth stage with a pipeline:
  • io.netty.handler.codec.http.HttpClientCodec
  • io.netty.handler.codec.http.HttpObjectAggregator
  • AuthenticationHandler

2 Web-socket stage with a pipeline:

  • io.netty.handler.codec.http.HttpClientCodec
  • io.netty.handler.codec.http.HttpObjectAggregator
  • io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandshakeHandler
  • io.netty.handler.codec.http.websocketx.Utf8FrameValidator
  • io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler
  • com.github.apsyvenko.client.messaging.MessageHandler

To activate second stage I have to fire event - ctx.fireChannelActive() but from channelRead.

As a result I got an exception:

18:19:37.055 [nioEventLoopGroup-2-1] WARN i.n.channel.DefaultChannelPipeline - An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception. java.net.SocketException: Connection reset

after hand-shake had started.

0 Answers0