Files
SHiNE-server/shine-server-net-protocol/src/main/java/server/ws/WsConnectionUtils.java
T
AidarKC 4430615117 23 01 25
Промежуточный комит.
Ужас как устал сегодня, узнал что и запросы у меня постоянно закрывают сессию. Надо переделать
2026-01-23 21:52:45 +03:00

155 lines
6.9 KiB
Java

package server.ws;
import org.eclipse.jetty.websocket.api.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
import server.logic.ws_protocol.JSON.ConnectionContext;
import shine.db.entities.SolanaUserEntry;
import java.net.SocketAddress;
import java.util.concurrent.atomic.AtomicLong;
/**
* Утилита для работы с WebSocket-подключениями.
*
* Цель этой версии:
* - всегда логировать "кто закрыл" / "что закрывали" / "в каком состоянии был WS";
* - логировать исключения так, чтобы было видно первопричину;
* - не терять контекст из-за ctx.reset() (сначала снимаем "снимок" полей).
*/
public final class WsConnectionUtils {
private static final Logger log = LoggerFactory.getLogger(WsConnectionUtils.class);
/** Счётчик событий закрытия (удобно коррелировать логи). */
private static final AtomicLong CLOSE_SEQ = new AtomicLong(0);
private WsConnectionUtils() {
// utility
}
public static void closeConnection(ConnectionContext ctx, int statusCode, String reason) {
closeConnection(ctx, statusCode, reason, null, "UNKNOWN");
}
/**
* Расширенное закрытие с указанием инициатора и причины (Throwable).
*
* @param ctx контекст
* @param statusCode код закрытия
* @param reason причина (пойдёт в close frame + логи)
* @param cause исключение/первопричина (если закрываем из catch)
* @param initiator строка "кто инициировал" (handler/op/requestId/etc.)
*/
public static void closeConnection(ConnectionContext ctx,
int statusCode,
String reason,
Throwable cause,
String initiator) {
if (ctx == null) return;
final long closeId = CLOSE_SEQ.incrementAndGet();
// --- СНИМОК КОНТЕКСТА ДО reset() ---
final Session ws = ctx.getWsSession();
final String sessionId = safeString(ctx.getSessionId());
final int authStatus = safeAuthStatus(ctx);
final SolanaUserEntry user = ctx.getSolanaUser();
final String login = (user != null ? safeString(user.getLogin()) : "");
final String activeSessionId =
(ctx.getActiveSession() != null ? safeString(ctx.getActiveSession().getSessionId()) : "");
final boolean wsPresent = (ws != null);
final boolean wsOpen = (ws != null && safeIsOpen(ws));
final String wsInfo = formatWsInfo(ws);
final String threadName = Thread.currentThread().getName();
final int ctxId = System.identityHashCode(ctx);
// Логируем "начало закрытия" всегда, чтобы видеть даже случаи "ws уже закрыт"
if (cause != null) {
log.warn("WS_CLOSE#{} BEGIN initiator={} thread={} ctxId={} login={} sessionId={} activeSessionId={} authStatus={} statusCode={} reason={} wsPresent={} wsOpen={} wsInfo={}",
closeId, initiator, threadName, ctxId, login, sessionId, activeSessionId, authStatus, statusCode, reason, wsPresent, wsOpen, wsInfo, cause);
} else {
log.info("WS_CLOSE#{} BEGIN initiator={} thread={} ctxId={} login={} sessionId={} activeSessionId={} authStatus={} statusCode={} reason={} wsPresent={} wsOpen={} wsInfo={}",
closeId, initiator, threadName, ctxId, login, sessionId, activeSessionId, authStatus, statusCode, reason, wsPresent, wsOpen, wsInfo);
}
// --- ШАГ 1: убрать из реестра (чтобы новые сообщения не шли в мёртвый контекст) ---
try {
ActiveConnectionsRegistry.getInstance().remove(ctx);
log.debug("WS_CLOSE#{} registry.remove OK ctxId={} sessionId={} login={}", closeId, ctxId, sessionId, login);
} catch (Exception e) {
log.warn("WS_CLOSE#{} registry.remove FAIL ctxId={} sessionId={} login={}", closeId, ctxId, sessionId, login, e);
}
// --- ШАГ 2: закрыть WS (если открыт) ---
if (ws != null) {
if (safeIsOpen(ws)) {
try {
ws.close(statusCode, safeString(reason));
log.info("WS_CLOSE#{} ws.close OK ctxId={} sessionId={} login={} statusCode={} reason={}",
closeId, ctxId, sessionId, login, statusCode, reason);
} catch (Exception e) {
log.warn("WS_CLOSE#{} ws.close FAIL ctxId={} sessionId={} login={} statusCode={} reason={} wsInfo={}",
closeId, ctxId, sessionId, login, statusCode, reason, wsInfo, e);
}
} else {
log.info("WS_CLOSE#{} ws already closed ctxId={} sessionId={} login={} wsInfo={}",
closeId, ctxId, sessionId, login, wsInfo);
}
}
// --- ШАГ 3: очистить контекст (в конце, чтобы не потерять поля в логах выше) ---
try {
ctx.reset();
log.debug("WS_CLOSE#{} ctx.reset OK ctxId={} (was sessionId={}, login={})", closeId, ctxId, sessionId, login);
} catch (Exception e) {
log.warn("WS_CLOSE#{} ctx.reset FAIL ctxId={} (was sessionId={}, login={})", closeId, ctxId, sessionId, login, e);
}
log.info("WS_CLOSE#{} END initiator={} ctxId={} sessionId={} login={}", closeId, initiator, ctxId, sessionId, login);
}
private static String safeString(String s) {
return (s == null ? "" : s);
}
private static int safeAuthStatus(ConnectionContext ctx) {
try {
return ctx.getAuthenticationStatus();
} catch (Exception e) {
return -999;
}
}
private static boolean safeIsOpen(Session ws) {
try {
return ws.isOpen();
} catch (Exception e) {
return false;
}
}
private static String formatWsInfo(Session ws) {
if (ws == null) return "null";
String remote = "";
String local = "";
try {
SocketAddress ra = ws.getRemoteAddress();
remote = (ra != null ? ra.toString() : "");
} catch (Exception ignored) { }
try {
SocketAddress la = ws.getLocalAddress();
local = (la != null ? la.toString() : "");
} catch (Exception ignored) { }
return "remote=" + remote + ", local=" + local;
}
}