Create reusable DisconnectedClientHelper

See gh-26181
This commit is contained in:
rstoyanchev
2023-10-17 14:51:56 +01:00
parent 49ff96dd0b
commit a8019f2d0b
3 changed files with 112 additions and 80 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,14 +23,12 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.socket.CloseStatus;
@@ -43,6 +41,7 @@ import org.springframework.web.socket.sockjs.frame.SockJsFrame;
import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec;
import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig;
import org.springframework.web.socket.sockjs.transport.SockJsSession;
import org.springframework.web.util.DisconnectedClientHelper;
/**
* An abstract base class for SockJS sessions implementing {@link SockJsSession}.
@@ -57,38 +56,16 @@ public abstract class AbstractSockJsSession implements SockJsSession {
/**
* Log category to use on network IO exceptions after a client has gone away.
* <p>Servlet containers don't expose a client disconnected callback; see
* <a href="https://github.com/eclipse-ee4j/servlet-api/issues/44">eclipse-ee4j/servlet-api#44</a>.
* Therefore, network IO failures may occur simply because a client has gone away,
* and that can fill the logs with unnecessary stack traces.
* <p>We make a best effort to identify such network failures, on a per-server
* basis, and log them under a separate log category. A simple one-line message
* is logged at DEBUG level, while a full stack trace is shown at TRACE level.
* @see #disconnectedClientLogger
* Log category to use for network failure after a client has gone away.
* @see DisconnectedClientHelper
*/
public static final String DISCONNECTED_CLIENT_LOG_CATEGORY =
"org.springframework.web.socket.sockjs.DisconnectedClient";
/**
* Tomcat: ClientAbortException or EOFException
* Jetty: EofException
* WildFly, GlassFish: java.io.IOException "Broken pipe" (already covered)
* <p>TODO:
* This definition is currently duplicated between HttpWebHandlerAdapter
* and AbstractSockJsSession. It is a candidate for a common utility class.
* @see #indicatesDisconnectedClient(Throwable)
*/
private static final Set<String> DISCONNECTED_CLIENT_EXCEPTIONS =
Set.of("ClientAbortException", "EOFException", "EofException");
private static final DisconnectedClientHelper disconnectedClientHelper =
new DisconnectedClientHelper(DISCONNECTED_CLIENT_LOG_CATEGORY);
/**
* Separate logger to use on network IO failure after a client has gone away.
* @see #DISCONNECTED_CLIENT_LOG_CATEGORY
*/
protected static final Log disconnectedClientLogger = LogFactory.getLog(DISCONNECTED_CLIENT_LOG_CATEGORY);
protected final Log logger = LogFactory.getLog(getClass());
protected final Object responseLock = new Object();
@@ -346,28 +323,11 @@ public abstract class AbstractSockJsSession implements SockJsSession {
protected abstract void writeFrameInternal(SockJsFrame frame) throws IOException;
private void logWriteFrameFailure(Throwable ex) {
if (indicatesDisconnectedClient(ex)) {
if (disconnectedClientLogger.isTraceEnabled()) {
disconnectedClientLogger.trace("Looks like the client has gone away", ex);
}
else if (disconnectedClientLogger.isDebugEnabled()) {
disconnectedClientLogger.debug("Looks like the client has gone away: " + ex +
" (For a full stack trace, set the log category '" + DISCONNECTED_CLIENT_LOG_CATEGORY +
"' to TRACE level.)");
}
}
else {
if (!disconnectedClientHelper.checkAndLogClientDisconnectedException(ex)) {
logger.debug("Terminating connection after failure to send message to client", ex);
}
}
private boolean indicatesDisconnectedClient(Throwable ex) {
String message = NestedExceptionUtils.getMostSpecificCause(ex).getMessage();
message = (message != null ? message.toLowerCase() : "");
String className = ex.getClass().getSimpleName();
return (message.contains("broken pipe") || DISCONNECTED_CLIENT_EXCEPTIONS.contains(className));
}
// Delegation methods