Skip to content

Commit 8154dda

Browse files
0utplayderklaro
andauthored
fix: suppress connection exceptions that are expected (#96)
Co-authored-by: Pasqual Koschmieder <[email protected]>
1 parent a43694a commit 8154dda

File tree

4 files changed

+111
-11
lines changed

4 files changed

+111
-11
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2019-2024 CloudNetService team & contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package eu.cloudnetservice.ext.rest.netty;
18+
19+
import io.netty5.channel.ChannelException;
20+
import io.netty5.handler.timeout.ReadTimeoutException;
21+
import io.netty5.util.concurrent.FutureListener;
22+
import java.io.IOException;
23+
import java.nio.channels.ClosedChannelException;
24+
import java.util.Locale;
25+
import javax.net.ssl.SSLException;
26+
import lombok.NonNull;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
30+
/**
31+
* A utility class for logging exceptions that occur while processing a request.
32+
*
33+
* @since 1.0
34+
*/
35+
final class NettyExceptionLogger {
36+
37+
private static final Logger LOGGER = LoggerFactory.getLogger(NettyExceptionLogger.class);
38+
39+
/**
40+
* A future listener that logs the exception produced by the future to which it was attached if it is relevant enough.
41+
* If the exception is deemed to be irrelevant in the current context, it is silently ignored.
42+
*/
43+
public static final FutureListener<Object> LOG_ON_FAILURE = future -> {
44+
if (future.isFailed() && !future.isCancelled()) {
45+
NettyExceptionLogger.handleConnectionException(future.cause());
46+
}
47+
};
48+
49+
private NettyExceptionLogger() {
50+
throw new UnsupportedOperationException();
51+
}
52+
53+
/**
54+
* Logs the given exception if it is relevant enough, silently ignoring it otherwise.
55+
*
56+
* @param cause the cause to log if it is relevant enough.
57+
* @throws NullPointerException if the given cause is null.
58+
*/
59+
static void handleConnectionException(@NonNull Throwable cause) {
60+
if (LOGGER.isTraceEnabled()) {
61+
LOGGER.trace("Caught exception while processing rest request", cause);
62+
return;
63+
}
64+
65+
if (cause instanceof ClosedChannelException || cause instanceof ReadTimeoutException) {
66+
// happens when trying to write to a channel that was improperly closed by the remote
67+
return;
68+
}
69+
70+
var message = cause.getMessage();
71+
if (message != null) {
72+
var lowerMessage = message.toLowerCase(Locale.ROOT);
73+
if (cause instanceof SSLException && lowerMessage.contains("closed already")) {
74+
// happens when remote closes the connection while ssl-related work is in progress
75+
return;
76+
}
77+
78+
if ((cause instanceof IOException || cause instanceof ChannelException) && canIgnoreException(lowerMessage)) {
79+
// some sort of socket error that can safely be ignored
80+
return;
81+
}
82+
}
83+
84+
LOGGER.warn("Caught exception while processing rest request", cause);
85+
}
86+
87+
/**
88+
* Checks if the given error associated with the given message can be safely ignored.
89+
*
90+
* @param message the error message to check.
91+
* @return true if the error associated with the given message can be safely ignored, false otherwise.
92+
* @throws NullPointerException if the given message is null.
93+
*/
94+
private static boolean canIgnoreException(@NonNull String message) {
95+
var broken = message.contains("broken");
96+
if (broken && message.contains("pipe")) {
97+
return true;
98+
}
99+
100+
if (!message.contains("connection")) {
101+
return false;
102+
}
103+
104+
// connection related with an ignorable state
105+
return broken || message.contains("closed") || message.contains("reset") || message.contains("abort");
106+
}
107+
}

web-impl-netty/src/main/java/eu/cloudnetservice/ext/rest/netty/NettyHttpServerHandler.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,9 @@
3838
import io.netty5.handler.codec.http.HttpRequest;
3939
import io.netty5.handler.codec.http.HttpResponseStatus;
4040
import io.netty5.handler.codec.http.HttpUtil;
41-
import io.netty5.handler.timeout.ReadTimeoutException;
4241
import io.netty5.util.AttributeKey;
4342
import io.netty5.util.Send;
4443
import io.netty5.util.concurrent.Future;
45-
import java.io.IOException;
4644
import java.net.URI;
4745
import java.util.HashMap;
4846
import java.util.concurrent.ExecutorService;
@@ -106,9 +104,7 @@ public void channelInactive(@NonNull ChannelHandlerContext ctx) {
106104
*/
107105
@Override
108106
public void channelExceptionCaught(@NonNull ChannelHandlerContext ctx, @NonNull Throwable cause) {
109-
if (!(cause instanceof IOException) && !(cause instanceof ReadTimeoutException)) {
110-
LOGGER.error("Exception caught during processing of http request", cause);
111-
}
107+
NettyExceptionLogger.handleConnectionException(cause);
112108
}
113109

114110
/**
@@ -248,7 +244,7 @@ private void handleMessage(
248244
}
249245

250246
// add the listener that fires the exception if an error occurs during writing of the response
251-
future.addListener(channel, ChannelFutureListeners.FIRE_EXCEPTION_ON_FAILURE);
247+
future.addListener(NettyExceptionLogger.LOG_ON_FAILURE);
252248
if (context.closeAfter) {
253249
future.addListener(channel, ChannelFutureListeners.CLOSE);
254250
}

web-impl-netty/src/main/java/eu/cloudnetservice/ext/rest/netty/NettyWebSocketServerChannel.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ public NettyWebSocketServerChannel(@NonNull HttpChannel httpChannel, @NonNull Ch
128128

129129
this.channel
130130
.writeAndFlush(webSocketFrame)
131-
.addListener(this.channel, ChannelFutureListeners.FIRE_EXCEPTION_ON_FAILURE);
131+
.addListener(NettyExceptionLogger.LOG_ON_FAILURE);
132132
return this;
133133
}
134134

web-impl-netty/src/main/java/eu/cloudnetservice/ext/rest/netty/NettyWebSocketServerChannelHandler.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import io.netty5.handler.codec.http.websocketx.PongWebSocketFrame;
2626
import io.netty5.handler.codec.http.websocketx.TextWebSocketFrame;
2727
import io.netty5.handler.codec.http.websocketx.WebSocketFrame;
28-
import java.io.IOException;
2928
import lombok.NonNull;
3029
import org.slf4j.Logger;
3130
import org.slf4j.LoggerFactory;
@@ -56,9 +55,7 @@ public NettyWebSocketServerChannelHandler(@NonNull NettyWebSocketServerChannel w
5655
*/
5756
@Override
5857
public void channelExceptionCaught(@NonNull ChannelHandlerContext ctx, @NonNull Throwable cause) {
59-
if (!(cause instanceof IOException)) {
60-
LOGGER.error("Caught exception in websocket connection", cause);
61-
}
58+
NettyExceptionLogger.handleConnectionException(cause);
6259
}
6360

6461
/**

0 commit comments

Comments
 (0)