Optimize SslHandler's detection of supressable exceptions, so it will not break on different OS's or jdk impls. See #79
This commit is contained in:
parent
a23baa1b9b
commit
3f1478b653
@ -29,10 +29,15 @@ import io.netty.channel.ChannelPipeline;
|
|||||||
import io.netty.channel.DefaultChannelFuture;
|
import io.netty.channel.DefaultChannelFuture;
|
||||||
import io.netty.logging.InternalLogger;
|
import io.netty.logging.InternalLogger;
|
||||||
import io.netty.logging.InternalLoggerFactory;
|
import io.netty.logging.InternalLoggerFactory;
|
||||||
|
import io.netty.util.internal.DetectionUtil;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.Socket;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.ClosedChannelException;
|
import java.nio.channels.ClosedChannelException;
|
||||||
|
import java.nio.channels.DatagramChannel;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@ -149,9 +154,8 @@ public class SslHandler
|
|||||||
private static final InternalLogger logger =
|
private static final InternalLogger logger =
|
||||||
InternalLoggerFactory.getInstance(SslHandler.class);
|
InternalLoggerFactory.getInstance(SslHandler.class);
|
||||||
|
|
||||||
private static final Pattern IGNORABLE_ERROR_MESSAGE = Pattern.compile(
|
private static final Pattern IGNORABLE_CLASS_IN_STACK = Pattern.compile(
|
||||||
"^.*(?:connection.*reset|connection.*closed|broken.*pipe).*$",
|
"^.*(Socket|DatagramChannel|SctpChannel).*$");
|
||||||
Pattern.CASE_INSENSITIVE);
|
|
||||||
|
|
||||||
private volatile ChannelHandlerContext ctx;
|
private volatile ChannelHandlerContext ctx;
|
||||||
private final SSLEngine engine;
|
private final SSLEngine engine;
|
||||||
@ -438,29 +442,89 @@ public class SslHandler
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
if (cause instanceof IOException && engine.isOutboundDone()) {
|
if (ignoreException(cause)) {
|
||||||
String message = String.valueOf(cause.getMessage()).toLowerCase();
|
// It is safe to ignore the 'connection reset by peer' or
|
||||||
if (IGNORABLE_ERROR_MESSAGE.matcher(message).matches()) {
|
// 'broken pipe' error after sending closure_notify.
|
||||||
// It is safe to ignore the 'connection reset by peer' or
|
if (logger.isDebugEnabled()) {
|
||||||
// 'broken pipe' error after sending closure_notify.
|
logger.debug(
|
||||||
if (logger.isDebugEnabled()) {
|
"Swallowing a 'connection reset by peer / " +
|
||||||
logger.debug(
|
"broken pipe' error occurred while writing " +
|
||||||
"Swallowing a 'connection reset by peer / " +
|
"'closure_notify'", cause);
|
||||||
"broken pipe' error occurred while writing " +
|
|
||||||
"'closure_notify'", cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the connection explicitly just in case the transport
|
|
||||||
// did not close the connection automatically.
|
|
||||||
if (ctx.channel().isActive()) {
|
|
||||||
ctx.close();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close the connection explicitly just in case the transport
|
||||||
|
// did not close the connection automatically.
|
||||||
|
if (ctx.channel().isActive()) {
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
super.exceptionCaught(ctx, cause);
|
super.exceptionCaught(ctx, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given {@link Throwable} can be ignore and just "swallowed"
|
||||||
|
*
|
||||||
|
* When an ssl connection is closed a close_notify message is sent.
|
||||||
|
* After that the peer also sends close_notify however, it's not mandatory to receive
|
||||||
|
* the close_notify. The party who sent the initial close_notify can close the connection immediately
|
||||||
|
* then the peer will get connection reset error.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private boolean ignoreException(Throwable t) {
|
||||||
|
if (!(t instanceof SSLException) && t instanceof IOException && engine.isOutboundDone()) {
|
||||||
|
|
||||||
|
// Inspect the StackTraceElements to see if it was a connection reset / broken pipe or not
|
||||||
|
StackTraceElement[] elements = t.getStackTrace();
|
||||||
|
for (StackTraceElement element: elements) {
|
||||||
|
String classname = element.getClassName();
|
||||||
|
String methodname = element.getMethodName();
|
||||||
|
|
||||||
|
// skip all classes that belong to the io.netty package
|
||||||
|
if (classname.startsWith("io.netty.")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the method name is read if not skip it
|
||||||
|
if (!methodname.equals("read")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will also match against SocketInputStream which is used by openjdk 7 and maybe
|
||||||
|
// also others
|
||||||
|
if (IGNORABLE_CLASS_IN_STACK.matcher(classname).matches()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// No match by now.. Try to load the class via classloader and inspect it.
|
||||||
|
// This is mainly done as other JDK implementations may differ in name of
|
||||||
|
// the impl.
|
||||||
|
Class<?> clazz = getClass().getClassLoader().loadClass(classname);
|
||||||
|
|
||||||
|
if (SocketChannel.class.isAssignableFrom(clazz)
|
||||||
|
|| DatagramChannel.class.isAssignableFrom(clazz)
|
||||||
|
|| Socket.class.isAssignableFrom(clazz)
|
||||||
|
|| DatagramSocket.class.isAssignableFrom(clazz)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// also match against SctpChannel via String matching as it may not present.
|
||||||
|
if (DetectionUtil.javaVersion() >= 7
|
||||||
|
&& "com.sun.nio.sctp.SctpChannel".equals(clazz.getSuperclass().getName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
// This should not happen just ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void inboundBufferUpdated(final ChannelHandlerContext ctx) throws Exception {
|
public void inboundBufferUpdated(final ChannelHandlerContext ctx) throws Exception {
|
||||||
final ByteBuf in = ctx.inboundByteBuffer();
|
final ByteBuf in = ctx.inboundByteBuffer();
|
||||||
|
Loading…
Reference in New Issue
Block a user