Asynchronous 捕获出站ChannelHandler的所有异常处理

Asynchronous 捕获出站ChannelHandler的所有异常处理,asynchronous,exception,exception-handling,netty,Asynchronous,Exception,Exception Handling,Netty,在Netty中,您有入站和出站处理程序的概念。只需在管道末端(尾部)添加通道处理程序并实现exceptionCaught覆盖,即可实现一个捕获所有入站异常处理程序。沿入站管道发生的异常将沿着处理程序传播,直到遇到最后一个异常(如果不在过程中处理) 传出处理程序没有完全相反的定义。相反(根据Netty in Action,第94页),您需要将一个侦听器添加到频道的未来,或者将一个侦听器添加到传递到处理程序的写入方法中的承诺 由于我不确定在何处插入前者,我想我会选择后者,因此我制作了以下Channe

在Netty中,您有入站和出站处理程序的概念。只需在管道末端(尾部)添加通道处理程序并实现
exceptionCaught
覆盖,即可实现一个捕获所有入站异常处理程序。沿入站管道发生的异常将沿着处理程序传播,直到遇到最后一个异常(如果不在过程中处理)

传出处理程序没有完全相反的定义。相反(根据Netty in Action,第94页),您需要将一个侦听器添加到频道的
未来
,或者将一个侦听器添加到传递到
处理程序的
写入
方法中的
承诺

由于我不确定在何处插入前者,我想我会选择后者,因此我制作了以下
ChannelOutboundHandler

}

/**
 * Catch and log errors happening in the outgoing direction
 *
 * @see <p>p94 in "Netty In Action"</p>
 */
private ChannelOutboundHandlerAdapter createOutgoingErrorHandler() {
    return new ChannelOutboundHandlerAdapter() {
        @Override
        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
            logger.info("howdy! (never gets this far)");

            final ChannelFutureListener channelFutureListener = future -> {
                if (!future.isSuccess()) {
                    future.cause().printStackTrace();
                    // ctx.writeAndFlush(serverErrorJSON("an error!"));
                    future.channel().writeAndFlush(serverErrorJSON("an error!"));
                    future.channel().close();
                }
            };
            promise.addListener(channelFutureListener);
            ctx.write(msg, promise);
        }
    };
问题是,如果我在
HttpLoggerHandler.write()
中抛出运行时异常,则永远不会调用错误处理程序的
write
方法

我该怎么做?任何传出处理程序中的错误都应该“冒泡”到附加到头部的处理程序


需要注意的一件重要事情是,我不仅仅想关闭通道,我还想向客户机回写一条错误消息(从
serverErrorJSON(“…”)
。在我尝试处理程序顺序时(也从中尝试内容),我已激活了侦听器,但我无法编写任何内容。如果在侦听器中使用
ctx.write()
,则在使用
future.channel()时,似乎陷入了循环.write…
什么也没做。

基本上你所做的是正确的…唯一不正确的是处理程序的顺序。你的
ChannelOutboundHandlerAdapter
将被放置在管道中“作为最后一个出站处理程序”。这意味着它应该是这样的:

pipeline.addLast(
        new HttpLoggerHandler(),
        createOutgoingErrorHandler(),
        authHandlerFactory.get());

这是因为出站事件从管道的尾部流向管道的头部,而入站事件从管道的头部流向尾部。

对于将捕获错误的出站处理程序,似乎没有一个通用的“捕获所有异常处理程序”概念。这意味着,除非您向c注册了侦听器atch某个错误运行时的错误可能会导致错误被“吞没”,让你挠头为什么什么也不返回

这就是说,如果有一个处理程序/侦听器总是在给定错误的情况下执行(因为它需要非常通用),那么它可能没有意义,但它确实会使日志记录错误比需要的更复杂

在写了(我建议签出!)之后,我得到了这些见解,这些见解基本上就是我的JUnit测试的名称(经过一些正则表达式操作):

  • 侦听器可以在父写入完成后写入通道
  • 写侦听器可以从管道中删除侦听器,并在错误的写操作上进行写操作
  • 如果传递相同的承诺,则在成功时调用所有侦听器
  • 靠近尾部的错误处理程序无法从靠近头部的处理程序捕获错误
  • netty不会调用下一个处理程序运行时写入异常
  • netty在正常写入时调用写入侦听器一次
  • netty在错误写入时调用写入侦听器一次
  • netty使用其写入的消息调用下一个write处理程序
  • 承诺可以用来倾听下一步的成功或失败
  • 如果承诺被传递,那么承诺可以用来倾听非即时结果
  • 如果传递了新的承诺,则承诺不能用于侦听非即时结果
  • 如果承诺没有被传递,承诺就不能被用来倾听非即时的结果
  • 如果没有传递承诺,则只有添加到最终写入的侦听器在出错时被调用
  • 如果承诺没有传递,则只有添加到最终写入的侦听器才会在成功时被调用
  • 写入侦听器从尾部调用
这个洞察意味着,给出问题中的示例,如果尾部附近出现错误,并且
authHandler
没有传递承诺,那么头部附近的错误处理程序将永远不会被调用,因为它提供了一个新的承诺,因为
ctx.write(msg)
本质上是
ctx.channel.write(msg,newPromise())

在我们的情况下,我们最终通过在所有业务逻辑处理程序之间注入相同的可共享错误处理来解决这个问题

处理者看起来像这样

@ChannelHandler.Sharable
class OutboundErrorHandler extends ChannelOutboundHandlerAdapter {

    private final static Logger logger = LoggerFactory.getLogger(OutboundErrorHandler.class);
    private Throwable handledCause = null;

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
        ctx.write(msg, promise).addListener(writeResult -> handleWriteResult(ctx, writeResult));
    }

    private void handleWriteResult(ChannelHandlerContext ctx, Future<?> writeResult) {
        if (!writeResult.isSuccess()) {
            final Throwable cause = writeResult.cause();

            if (cause instanceof ClosedChannelException) {
                // no reason to close an already closed channel - just ignore
                return;
            }

            // Since this handler is shared and added multiple times
            // we need to avoid spamming the logs N number of times for the same error
            if (handledCause == cause) return;
            handledCause = cause;

            logger.error("Uncaught exception on write!", cause);

            // By checking on channel writability and closing the channel after writing the error message,
            // only the first listener will signal the error to the client
            final Channel channel = ctx.channel();
            if (channel.isWritable()) {
                ctx.writeAndFlush(serverErrorJSON(cause.getMessage()), channel.newPromise());
                ctx.close();
            }
        }
    }
}
@ChannelHandler.Sharable
类OutboundErrorHandler扩展ChannelOutboundHandlerAdapter{
私有最终静态记录器Logger=LoggerFactory.getLogger(OutboundErrorHandler.class);
私有可丢弃handledCause=null;
@凌驾
公共无效写入(ChannelHandlerContext ctx、对象消息、ChannelPromise承诺){
write(msg,promise).addListener(writeResult->handleWriteResult(ctx,writeResult));
}
私有无效handleWriteResult(ChannelHandlerContext ctx,未来写入结果){
如果(!writeResult.isSuccess()){
最终可丢弃原因=writeResult.cause();
if(导致ClosedChannel异常的实例){
//没有理由关闭已经关闭的频道-忽略即可
返回;
}
//因为这个处理程序被多次共享和添加
//我们需要避免对同一错误多次滥发日志
if(handledCause==原因)返回;
处理的原因=原因;
logger.error(“写入时未捕获异常!”,原因);
//通过检查通道可写性并在写入错误消息后关闭通道,
//只有第一个侦听器将向客户端发送错误信号
最终通道=
@ChannelHandler.Sharable
class OutboundErrorHandler extends ChannelOutboundHandlerAdapter {

    private final static Logger logger = LoggerFactory.getLogger(OutboundErrorHandler.class);
    private Throwable handledCause = null;

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
        ctx.write(msg, promise).addListener(writeResult -> handleWriteResult(ctx, writeResult));
    }

    private void handleWriteResult(ChannelHandlerContext ctx, Future<?> writeResult) {
        if (!writeResult.isSuccess()) {
            final Throwable cause = writeResult.cause();

            if (cause instanceof ClosedChannelException) {
                // no reason to close an already closed channel - just ignore
                return;
            }

            // Since this handler is shared and added multiple times
            // we need to avoid spamming the logs N number of times for the same error
            if (handledCause == cause) return;
            handledCause = cause;

            logger.error("Uncaught exception on write!", cause);

            // By checking on channel writability and closing the channel after writing the error message,
            // only the first listener will signal the error to the client
            final Channel channel = ctx.channel();
            if (channel.isWritable()) {
                ctx.writeAndFlush(serverErrorJSON(cause.getMessage()), channel.newPromise());
                ctx.close();
            }
        }
    }
}
// Prepend the error handler to every entry in the pipeline. 
// The intention behind this is to have a catch-all
// outbound error handler and thereby avoiding the need to attach a
// listener to every ctx.write(...).
final OutboundErrorHandler outboundErrorHandler = new OutboundErrorHandler();
for (Map.Entry<String, ChannelHandler> entry : pipeline) {
    pipeline.addBefore(entry.getKey(), entry.getKey() + "#OutboundErrorHandler", outboundErrorHandler);
}
    //Inbound propagation
    socketChannel.pipeline()
      .addLast(new Decoder())
      .addLast(new ExceptionHandler());

    //Outbound propagation
    socketChannel.pipeline()
      .addFirst(new OutboundExceptionRouter())
      .addFirst(new Encoder());
public class ExceptionHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("Exception caught on channel", cause);
    }
}
public class OutboundExceptionRouter extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        promise.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
        super.write(ctx, msg, promise);
    }
}