From 5d8dd86c965b96b80d40c6a76f335ae3ada698cb9053db6ea77995e30d4a2940 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Thu, 4 Dec 2025 13:34:04 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D1=8F?= =?UTF-8?q?=D1=8E=20=D1=81=D0=B5=D1=82=D0=B5=D0=B2=D1=8B=D0=B5=20=D1=85?= =?UTF-8?q?=D1=8D=D0=BD=D0=B4=D0=BB=D0=B5=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ws_protocol/JSON/JsonHandlerRegistry.java | 2 +- .../JSON/JsonInboundProcessor.java | 41 ++-- .../auth/NetAuthSessionNewStep1Handler.java | 33 +-- .../auth/NetSessionRefreshHandler.java | 50 ++--- .../NetAddUserHandler.java | 65 +++--- .../utils/NetExceptionResponseFactory.java | 36 ++++ src/main/java/TestJsonWsClient.java | 197 ++++++++++-------- 7 files changed, 240 insertions(+), 184 deletions(-) rename shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/{auth => tempToTest}/NetAddUserHandler.java (61%) create mode 100644 shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/utils/NetExceptionResponseFactory.java diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java index 1d0dfc3..8095bf0 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java @@ -5,7 +5,7 @@ import server.logic.ws_protocol.JSON.entyties.Auth.NetAuthSessionNewStep1Request import server.logic.ws_protocol.JSON.entyties.Auth.NetSessionRefreshRequest; import server.logic.ws_protocol.JSON.handlers.*; import server.logic.ws_protocol.JSON.entyties.tempToTest.NetAddUserRequest; -import server.logic.ws_protocol.JSON.handlers.auth.NetAddUserHandler; +import server.logic.ws_protocol.JSON.handlers.tempToTest.NetAddUserHandler; import server.logic.ws_protocol.JSON.handlers.auth.NetAuthSessionNewStep1Handler; import server.logic.ws_protocol.JSON.handlers.auth.NetSessionRefreshHandler; diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonInboundProcessor.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonInboundProcessor.java index 4a4993d..31eba19 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonInboundProcessor.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonInboundProcessor.java @@ -55,10 +55,10 @@ public final class JsonInboundProcessor { "EMPTY_JSON", "Пустое JSON-сообщение"); } - // 1. Парсим общий пакет как дерево + // 1. Парсим общий пакет JsonNode root = JSON_MAPPER.readTree(json); - // 2. Берём op и requestId + // 2. op и requestId String op = getTextOrNull(root, "op"); if (op == null || op.isEmpty()) { return buildErrorJson(null, null, WireCodes.Status.BAD_REQUEST, @@ -75,21 +75,27 @@ public final class JsonInboundProcessor { "UNKNOWN_OP", "Неизвестная операция: " + op); } - // 3. Маппим весь JSON в конкретный класс запроса + // 3. Маппим JSON → нужный NetRequest NetRequest request = JSON_MAPPER.treeToValue(root, reqClass); - // 4. Вызываем хэндлер, передавая контекст - NetResponse response = handler.handle(request, ctx); + NetResponse response; - // На всякий случай: если хэндлер не выставил op/requestId - if (response.getOp() == null) { - response.setOp(op); - } - if (response.getRequestId() == null) { - response.setRequestId(requestId); + // 4. Трай-кэтч вокруг хэндлера (важно!) + try { + response = handler.handle(request, ctx); + } catch (Exception handlerError) { + log.error("💥 Ошибка внутри хэндлера '{}'", op, handlerError); + return buildErrorJson(op, requestId, + WireCodes.Status.INTERNAL_ERROR, + "INTERNAL_HANDLER_ERROR", + "Неожиданная ошибка при обработке операции: " + op); } - // 5. Собираем JSON-ответ + // Если хэндлер не выставил op/requestId + if (response.getOp() == null) response.setOp(op); + if (response.getRequestId() == null) response.setRequestId(requestId); + + // 5. Формируем JSON ObjectNode out = JSON_MAPPER.createObjectNode(); out.put("op", response.getOp()); out.put("requestId", response.getRequestId()); @@ -118,16 +124,7 @@ public final class JsonInboundProcessor { } /** - * Генерация JSON-ошибки в формате ответа: - * { - * "op": op, - * "requestId": requestId, - * "status": status, - * "payload": { - * "code": errorCode, - * "message": errorMessage - * } - * } + * Генерация JSON-ошибки */ private static String buildErrorJson(String op, String requestId, diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetAuthSessionNewStep1Handler.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetAuthSessionNewStep1Handler.java index a01af52..6371d01 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetAuthSessionNewStep1Handler.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetAuthSessionNewStep1Handler.java @@ -5,6 +5,7 @@ import server.logic.ws_protocol.JSON.entyties.*; import server.logic.ws_protocol.JSON.entyties.Auth.NetAuthSessionNewStep1Request; import server.logic.ws_protocol.JSON.entyties.Auth.NetAuthSessionNewStep1Response; import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler; +import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory; import server.logic.ws_protocol.WireCodes; import shine.db.dao.SolanaUsersDAO; import shine.db.entities.SolanaUser; @@ -23,15 +24,22 @@ public class NetAuthSessionNewStep1Handler implements JsonMessageHandler { String login = req.getLogin(); if (login == null || login.isBlank()) { - return error(req, WireCodes.Status.BAD_REQUEST, - "EMPTY_LOGIN", "Пустой логин"); + return NetExceptionResponseFactory.error( + req, + WireCodes.Status.BAD_REQUEST, + "EMPTY_LOGIN", + "Пустой логин" + ); } // 1) Проверка: в контексте никто не авторизован if (ctx.getLogin() != null) { - return error(req, WireCodes.Status.BAD_REQUEST, + return NetExceptionResponseFactory.error( + req, + WireCodes.Status.BAD_REQUEST, "ALREADY_AUTHED", - "Попытка повторной авторификации для уже заданного login=" + ctx.getLogin()); + "Попытка повторной авторификации для уже заданного login=" + ctx.getLogin() + ); } // 2) Ищем пользователя в локальной БД @@ -39,8 +47,12 @@ public class NetAuthSessionNewStep1Handler implements JsonMessageHandler { if (solanaUser == null) { // TODO позже — запрос в Solana, если не нашли локально - return error(req, WireCodes.Status.UNVERIFIED, - "UNKNOWN_USER", "Пользователь с таким логином не найден"); + return NetExceptionResponseFactory.error( + req, + WireCodes.Status.UNVERIFIED, + "UNKNOWN_USER", + "Пользователь с таким логином не найден" + ); } // 3) Заполняем контекст полями пользователя @@ -67,13 +79,4 @@ public class NetAuthSessionNewStep1Handler implements JsonMessageHandler { return resp; } - - private NetExceptionResponse error(NetRequest req, int status, String code, String msg) { - NetExceptionResponse resp = new NetExceptionResponse(); - resp.setOp(req.getOp()); - resp.setRequestId(req.getRequestId()); - resp.setStatus(status); - resp.setPayload(Map.of("code", code, "message", msg)); - return resp; - } } diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetSessionRefreshHandler.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetSessionRefreshHandler.java index 2fba372..d851763 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetSessionRefreshHandler.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetSessionRefreshHandler.java @@ -1,18 +1,17 @@ package server.logic.ws_protocol.JSON.handlers.auth; import server.logic.ws_protocol.JSON.ConnectionContext; -import server.logic.ws_protocol.JSON.entyties.NetExceptionResponse; import server.logic.ws_protocol.JSON.entyties.NetRequest; import server.logic.ws_protocol.JSON.entyties.NetResponse; import server.logic.ws_protocol.JSON.entyties.Auth.NetSessionRefreshRequest; import server.logic.ws_protocol.JSON.entyties.Auth.NetSessionRefreshResponse; import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler; +import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory; import server.logic.ws_protocol.WireCodes; import shine.db.dao.ActiveSessionsDAO; import shine.db.entities.ActiveSession; import java.sql.SQLException; -import java.util.Map; /** * Хэндлер SessionRefresh. @@ -35,8 +34,12 @@ public class NetSessionRefreshHandler implements JsonMessageHandler { String sessionPwd = req.getSessionPwd(); if (sessionPwd == null || sessionPwd.isEmpty()) { - return buildError(req, WireCodes.Status.BAD_REQUEST, - "BAD_SESSION_PWD", "Пустой пароль сессии"); + return NetExceptionResponseFactory.error( + req, + WireCodes.Status.BAD_REQUEST, + "BAD_SESSION_PWD", + "Пустой пароль сессии" + ); } ActiveSessionsDAO dao = ActiveSessionsDAO.getInstance(); @@ -45,19 +48,31 @@ public class NetSessionRefreshHandler implements JsonMessageHandler { session = dao.getBySessionId(sessionId); } catch (SQLException e) { // Ошибка БД → внутренняя ошибка сервера - return buildError(req, WireCodes.Status.SERVER_DATA_ERROR, - "DB_ERROR", "Ошибка доступа к базе данных"); + return NetExceptionResponseFactory.error( + req, + WireCodes.Status.SERVER_DATA_ERROR, + "DB_ERROR", + "Ошибка доступа к базе данных" + ); } if (session == null) { - return buildError(req, WireCodes.Status.UNVERIFIED, - "SESSION_NOT_FOUND", "Сессия не найдена"); + return NetExceptionResponseFactory.error( + req, + WireCodes.Status.UNVERIFIED, + "SESSION_NOT_FOUND", + "Сессия не найдена" + ); } String dbPwd = session.getSessionPwd(); if (dbPwd == null || !dbPwd.equals(sessionPwd)) { - return buildError(req, WireCodes.Status.UNVERIFIED, - "SESSION_PWD_MISMATCH", "Неверный пароль сессии"); + return NetExceptionResponseFactory.error( + req, + WireCodes.Status.UNVERIFIED, + "SESSION_PWD_MISMATCH", + "Неверный пароль сессии" + ); } // Всё хорошо — обновляем контекст соединения @@ -76,19 +91,4 @@ public class NetSessionRefreshHandler implements JsonMessageHandler { resp.setPayload(null); // или Map.of("ok", true) return resp; } - - private NetExceptionResponse buildError(NetRequest req, - int status, - String code, - String message) { - NetExceptionResponse resp = new NetExceptionResponse(); - resp.setOp(req.getOp()); - resp.setRequestId(req.getRequestId()); - resp.setStatus(status); - resp.setPayload(Map.of( - "code", code, - "message", message - )); - return resp; - } } diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetAddUserHandler.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/NetAddUserHandler.java similarity index 61% rename from shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetAddUserHandler.java rename to shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/NetAddUserHandler.java index cfe5c6c..7f3e4f8 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetAddUserHandler.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/NetAddUserHandler.java @@ -1,20 +1,19 @@ -package server.logic.ws_protocol.JSON.handlers.auth; +package server.logic.ws_protocol.JSON.handlers.tempToTest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import server.logic.ws_protocol.JSON.ConnectionContext; -import server.logic.ws_protocol.JSON.entyties.tempToTest.NetAddUserRequest; -import server.logic.ws_protocol.JSON.entyties.tempToTest.NetAddUserResponse; -import server.logic.ws_protocol.JSON.entyties.NetExceptionResponse; import server.logic.ws_protocol.JSON.entyties.NetRequest; import server.logic.ws_protocol.JSON.entyties.NetResponse; +import server.logic.ws_protocol.JSON.entyties.tempToTest.NetAddUserRequest; +import server.logic.ws_protocol.JSON.entyties.tempToTest.NetAddUserResponse; import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler; +import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory; import server.logic.ws_protocol.WireCodes; import shine.db.dao.SolanaUsersDAO; import shine.db.entities.SolanaUser; import java.sql.SQLException; -import java.util.Map; /** * Временный Хэндлер AddUser. Используется для тестовой регистрации!!!!!!!! @@ -33,19 +32,18 @@ public class NetAddUserHandler implements JsonMessageHandler { public NetResponse handle(NetRequest baseRequest, ConnectionContext ctx) throws Exception { NetAddUserRequest req = (NetAddUserRequest) baseRequest; - // Минимальная валидация входных данных - if (req.getLogin() == null || req.getLogin().isBlank()) { - return buildError(req, WireCodes.Status.BAD_REQUEST, - "BAD_LOGIN", "Пустой логин"); - } - if (req.getPubkey0() == null || req.getPubkey0().isBlank() - || req.getPubkey1() == null || req.getPubkey1().isBlank()) { - return buildError(req, WireCodes.Status.BAD_REQUEST, - "BAD_PUBKEY", "Публичные ключи не указаны"); - } - if (req.getBchLimit() == null) { - return buildError(req, WireCodes.Status.BAD_REQUEST, - "BAD_BCH_LIMIT", "Не указан лимит блокчейна"); + // Одна общая проверка всех ключевых полей + if (req.getLogin() == null || req.getLogin().isBlank() + || req.getPubkey0() == null || req.getPubkey0().isBlank() + || req.getPubkey1() == null || req.getPubkey1().isBlank() + || req.getBchLimit() == null) { + + return NetExceptionResponseFactory.error( + req, + WireCodes.Status.BAD_REQUEST, + "BAD_FIELDS", + "Некорректные или пустые поля: login, pubkey0, pubkey1, bchLimit" + ); } try { @@ -72,27 +70,20 @@ public class NetAddUserHandler implements JsonMessageHandler { } catch (SQLException e) { log.error("❌ Ошибка при вставке пользователя в БД", e); - return buildError(req, WireCodes.Status.SERVER_DATA_ERROR, - "DB_ERROR", "Ошибка доступа к базе данных"); + return NetExceptionResponseFactory.error( + req, + WireCodes.Status.SERVER_DATA_ERROR, + "DB_ERROR", + "Ошибка доступа к базе данных" + ); } catch (Exception e) { log.error("❌ Неожиданная ошибка в AddUser", e); - return buildError(req, WireCodes.Status.INTERNAL_ERROR, - "INTERNAL_ERROR", "Внутренняя ошибка сервера"); + return NetExceptionResponseFactory.error( + req, + WireCodes.Status.INTERNAL_ERROR, + "INTERNAL_ERROR", + "Внутренняя ошибка сервера" + ); } } - - private NetExceptionResponse buildError(NetRequest req, - int status, - String code, - String message) { - NetExceptionResponse resp = new NetExceptionResponse(); - resp.setOp(req.getOp()); - resp.setRequestId(req.getRequestId()); - resp.setStatus(status); - resp.setPayload(Map.of( - "code", code, - "message", message - )); - return resp; - } } diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/utils/NetExceptionResponseFactory.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/utils/NetExceptionResponseFactory.java new file mode 100644 index 0000000..56985a7 --- /dev/null +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/utils/NetExceptionResponseFactory.java @@ -0,0 +1,36 @@ +package server.logic.ws_protocol.JSON.utils; + + + +import server.logic.ws_protocol.JSON.entyties.NetExceptionResponse; +import server.logic.ws_protocol.JSON.entyties.NetRequest; + +import java.util.Map; + +/** + * Фабрика ошибок для JSON-протокола. + * Создаёт единообразные NetExceptionResponse. + */ +public final class NetExceptionResponseFactory { + + private NetExceptionResponseFactory() { + // запрет на создание объектов + } + + public static NetExceptionResponse error(NetRequest req, + int status, + String code, + String message) { + + NetExceptionResponse resp = new NetExceptionResponse(); + resp.setOp(req.getOp()); + resp.setRequestId(req.getRequestId()); + resp.setStatus(status); + resp.setPayload(Map.of( + "code", code, + "message", message + )); + + return resp; + } +} diff --git a/src/main/java/TestJsonWsClient.java b/src/main/java/TestJsonWsClient.java index 615d843..e273917 100644 --- a/src/main/java/TestJsonWsClient.java +++ b/src/main/java/TestJsonWsClient.java @@ -2,105 +2,134 @@ import java.net.URI; import java.net.http.HttpClient; import java.net.http.WebSocket; import java.net.http.WebSocket.Listener; -import java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.CountDownLatch; public class TestJsonWsClient { + // Адрес сервера + private static final String WS_URI = "ws://localhost:7070/ws"; + + // Отдельные запросы + private static final String JSON_REQUEST_SESSION_REFRESH = """ + { + "op": "SessionRefresh", + "requestId": "test-1", + "sessionId": 123, + "sessionPwd": "test-password" + } + """; + + private static final String JSON_REQUEST_ADD_USER = """ + { + "op": "AddUser", + "requestId": "test-add-1", + "login": "anya1111", + "loginId": 100211, + "bchId": 4222, + "pubkey0": "PUB0", + "pubkey1": "PUB1", + "bchLimit": 1000000 + } + """; + + private static final String JSON_REQUEST_AUTH_SESSION_NEW_STEP1 = """ + { + "op": "AuthSessionNewStep1", + "requestId": "test-auth-1", + "login": "anya1111" + } + """; + + // МАССИВ КОНСТАНТА с запросами — добавляешь сюда любые свои JSON + private static final String[] JSON_REQUESTS = { + JSON_REQUEST_SESSION_REFRESH, + JSON_REQUEST_ADD_USER, + JSON_REQUEST_AUTH_SESSION_NEW_STEP1 + }; + public static void main(String[] args) throws Exception { - String uri = "ws://localhost:7070/ws"; - - - String jsonRequestSessionRefresh = """ - { - "op": "SessionRefresh", - "requestId": "test-1", - "sessionId": 123, - "sessionPwd": "test-password" - } - """; - - String jsonRequestAddUser = """ - { - "op": "AddUser", - "requestId": "test-add-1", - "login": "anya1111", - "loginId": 100211, - "bchId": 4222, - "pubkey0": "PUB0", - "pubkey1": "PUB1", - "bchLimit": 1000000 - } - """; - - String jsonRequestAuthSessionNewStep1 = """ - { - "op": "AuthSessionNewStep1", - "requestId": "test-auth-1", - "login": "anya1111" - } - """; - - - // Тестовый JSON-пакет SessionRefresh - String jsonRequest = jsonRequestAuthSessionNewStep1; - - System.out.println("Подключаемся к " + uri); + System.out.println("Подключаемся к " + WS_URI); CountDownLatch latch = new CountDownLatch(1); HttpClient client = HttpClient.newHttpClient(); - WebSocket webSocket = client.newWebSocketBuilder() - .buildAsync(URI.create(uri), new Listener() { - @Override - public void onOpen(WebSocket webSocket) { - System.out.println("✅ WebSocket подключен"); + ClientListener listener = new ClientListener(JSON_REQUESTS, latch); - // Отправляем JSON сразу после подключения - System.out.println("📤 Отправляем JSON-запрос:"); - System.out.println(jsonRequest); + client.newWebSocketBuilder() + .buildAsync(URI.create(WS_URI), listener) + .join(); - webSocket.sendText(jsonRequest, true); - Listener.super.onOpen(webSocket); - } - - @Override - public CompletionStage onText(WebSocket webSocket, - CharSequence data, - boolean last) { - String message = data.toString(); - System.out.println("📥 Получен TEXT-ответ от сервера:"); - System.out.println(message); - - // После получения первого ответа — закрываем соединение - webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "test done"); - latch.countDown(); - return Listener.super.onText(webSocket, data, last); - } - - @Override - public void onError(WebSocket webSocket, Throwable error) { - System.out.println("❌ Ошибка WebSocket-клиента: " + error.getMessage()); - error.printStackTrace(System.out); - latch.countDown(); - } - - - @Override - public CompletionStage onClose(WebSocket webSocket, - int statusCode, - String reason) { - System.out.println("🔚 Соединение закрыто. Код=" + statusCode + ", причина=" + reason); - latch.countDown(); - return Listener.super.onClose(webSocket, statusCode, reason); - } - }).join(); - - // Ждём, пока получим ответ/ошибку/закрытие + // Ждём, пока всё не завершится (успех/ошибка/закрытие) latch.await(); System.out.println("Тест завершён, выходим."); } + + // Внутренний Listener, который сам по очереди шлёт запросы и печатает ответы + private static class ClientListener implements Listener { + + private final String[] requests; + private final CountDownLatch latch; + private int index = 0; // какой запрос сейчас отправляем/ждём ответ + + ClientListener(String[] requests, CountDownLatch latch) { + this.requests = requests; + this.latch = latch; + } + + @Override + public void onOpen(WebSocket webSocket) { + System.out.println("✅ WebSocket подключен"); + sendNextRequest(webSocket); + Listener.super.onOpen(webSocket); + } + + // Отправка следующего запроса из массива + private void sendNextRequest(WebSocket webSocket) { + if (index < requests.length) { + String json = requests[index]; + System.out.println(); + System.out.println("📤 Отправляем запрос " + (index + 1) + " из " + requests.length + ":"); + System.out.println(json); + webSocket.sendText(json, true); + } else { + System.out.println("✅ Все запросы отправлены, закрываем соединение"); + webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "all tests done"); + } + } + + @Override + public CompletionStage onText(WebSocket webSocket, + CharSequence data, + boolean last) { + // Ответ на текущий запрос (с индексом index) + System.out.println("📥 Ответ на запрос " + (index + 1) + ":"); + System.out.println(data.toString()); + System.out.println("-----------------------------------------------------"); + + // Переходим к следующему запросу + index++; + sendNextRequest(webSocket); + + return CompletableFuture.completedFuture(null); + } + + @Override + public void onError(WebSocket webSocket, Throwable error) { + System.out.println("❌ Ошибка WebSocket-клиента: " + error.getMessage()); + error.printStackTrace(System.out); + latch.countDown(); + } + + @Override + public CompletionStage onClose(WebSocket webSocket, + int statusCode, + String reason) { + System.out.println("🔚 Соединение закрыто. Код=" + statusCode + ", причина=" + reason); + latch.countDown(); + return CompletableFuture.completedFuture(null); + } + } }