SHA256
23 01 25
Промежуточный комит. Ужас как устал сегодня, узнал что и запросы у меня постоянно закрывают сессию. Надо переделать
This commit is contained in:
@@ -1,66 +0,0 @@
|
||||
package server.logic;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.binary.handlers.*;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Обработчик входящих сообщение на сервер.
|
||||
* По коду сообщения (первые 4 байта сообщения) находи нужный хэндлер и передаёт в него сообщение
|
||||
* Получает и возвращает ответ от хэндлера
|
||||
*/
|
||||
public final class InboundMessageProcessor {
|
||||
private static final Logger log = LoggerFactory.getLogger(InboundMessageProcessor.class);
|
||||
|
||||
private static final Map<Integer, MessageHandler> HANDLERS = Map.of(
|
||||
// WireCodes.Op.PING, new PingHandler()
|
||||
// WireCodes.Op.ADD_BLOCK, new AddBlockHandler(),
|
||||
// WireCodes.Op.GET_BLOCKCHAIN,new GetBlockchainHandler()
|
||||
// WireCodes.Op.SEARCH_USERS, new SearchUsersHandler(),
|
||||
// WireCodes.Op.GET_LAST_BLOCK_INFO,new GetLastBlockInfoHandler()
|
||||
|
||||
);
|
||||
|
||||
private InboundMessageProcessor() {}
|
||||
|
||||
public static byte[] process(byte[] msg) {
|
||||
if (msg == null || msg.length < 4)
|
||||
return intTo4Bytes(WireCodes.Status.BAD_REQUEST);
|
||||
|
||||
int op = first4ToInt(msg);
|
||||
MessageHandler h = HANDLERS.get(op);
|
||||
if (h == null) {
|
||||
log.warn("Неизвестная операция: {}", op);
|
||||
return intTo4Bytes(WireCodes.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
try {
|
||||
return h.handle(msg);
|
||||
} catch (Exception e) {
|
||||
log.error("Ошибка при обработке операции {}", op, e);
|
||||
return intTo4Bytes(WireCodes.Status.INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private static int first4ToInt(byte[] msg) {
|
||||
return ByteBuffer.wrap(msg, 0, 4)
|
||||
.order(ByteOrder.BIG_ENDIAN)
|
||||
.getInt();
|
||||
}
|
||||
|
||||
public static byte[] intTo4Bytes(int code) {
|
||||
return ByteBuffer.allocate(4)
|
||||
.order(ByteOrder.BIG_ENDIAN)
|
||||
.putInt(code)
|
||||
.array();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package server.logic.ws_protocol.binary.handlers;
|
||||
|
||||
/**
|
||||
* Общий интерфейс для всех обработчиков входящих сообщений.
|
||||
*/
|
||||
public interface MessageHandler {
|
||||
/**
|
||||
* Обработать входящее сообщение и вернуть бинарный ответ.
|
||||
*/
|
||||
byte[] handle(byte[] msg);
|
||||
}
|
||||
@@ -5,85 +5,128 @@ import org.eclipse.jetty.websocket.api.WriteCallback;
|
||||
import org.eclipse.jetty.websocket.api.annotations.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.InboundMessageProcessor;
|
||||
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.JsonInboundProcessor;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@WebSocket
|
||||
public class BlockchainWsEndpoint {
|
||||
private static final Logger log = LoggerFactory.getLogger(BlockchainWsEndpoint.class);
|
||||
|
||||
/**
|
||||
* Общий пул для обработки ВСЕХ входящих сообщений.
|
||||
* Важно: не commonPool, чтобы под нагрузкой всё было предсказуемо.
|
||||
*/
|
||||
private static final ExecutorService WS_EXECUTOR = new ThreadPoolExecutor(
|
||||
Math.max(2, Runtime.getRuntime().availableProcessors()),
|
||||
Math.max(2, Runtime.getRuntime().availableProcessors()),
|
||||
60L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(10_000),
|
||||
new ThreadFactory() {
|
||||
private final AtomicLong n = new AtomicLong(1);
|
||||
@Override public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r, "ws-worker-" + n.getAndIncrement());
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
},
|
||||
new ThreadPoolExecutor.AbortPolicy()
|
||||
);
|
||||
|
||||
private Session session;
|
||||
|
||||
/** Контекст для текущего WebSocket-соединения. */
|
||||
private final ConnectionContext connectionContext = new ConnectionContext();
|
||||
|
||||
/**
|
||||
* Хвост очереди per-session: гарантирует строгую последовательность.
|
||||
* Каждое новое сообщение добавляется в цепочку.
|
||||
*/
|
||||
private CompletableFuture<Void> queueTail = CompletableFuture.completedFuture(null);
|
||||
|
||||
/** Защита от гонки при обновлении queueTail. */
|
||||
private final Object queueLock = new Object();
|
||||
|
||||
@OnWebSocketConnect
|
||||
public void onConnect(Session session) {
|
||||
this.session = session;
|
||||
// Привязываем WebSocket-сессию к ConnectionContext
|
||||
connectionContext.setWsSession(session);
|
||||
log.info("WS connected: {}", session.getRemoteAddress());
|
||||
}
|
||||
|
||||
// JSON only
|
||||
@OnWebSocketMessage
|
||||
public void onBinary(byte[] payload, int offset, int length) {
|
||||
byte[] msg = new byte[length];
|
||||
System.arraycopy(payload, offset, msg, 0, length);
|
||||
public void onText(String message) {
|
||||
// Быстро отфильтруем мусор
|
||||
if (message == null || message.isBlank()) return;
|
||||
|
||||
// Асинхронно обрабатываем входящее бинарное сообщение
|
||||
CompletableFuture
|
||||
.supplyAsync(() -> InboundMessageProcessor.process(msg))
|
||||
.thenAccept(resp -> {
|
||||
if (resp != null && session != null && session.isOpen()) {
|
||||
session.getRemote().sendBytes(ByteBuffer.wrap(resp), new WriteCallback() {
|
||||
@Override
|
||||
public void writeFailed(Throwable x) {
|
||||
log.warn("Failed to send response", x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSuccess() {
|
||||
log.debug("Response sent successfully");
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.exceptionally(ex -> {
|
||||
log.error("Processing failed", ex);
|
||||
trySendCode(500);
|
||||
return null;
|
||||
});
|
||||
// Добавляем обработку в очередь данного соединения (строго по порядку)
|
||||
enqueue(() -> processJsonAndReply(message));
|
||||
}
|
||||
|
||||
private void trySendCode(int code) {
|
||||
if (session != null && session.isOpen()) {
|
||||
byte[] resp = InboundMessageProcessor.intTo4Bytes(code);
|
||||
session.getRemote().sendBytes(ByteBuffer.wrap(resp), new WriteCallback() {
|
||||
@Override
|
||||
public void writeFailed(Throwable x) {
|
||||
log.warn("Failed to send error code", x);
|
||||
private void enqueue(Runnable task) {
|
||||
synchronized (queueLock) {
|
||||
queueTail = queueTail.thenRunAsync(() -> {
|
||||
try {
|
||||
task.run();
|
||||
} catch (Throwable t) {
|
||||
// Нельзя дать цепочке "сломаться", иначе очередь остановится навсегда
|
||||
log.error("❌ Unhandled error in ws task", t);
|
||||
trySendJsonError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSuccess() {
|
||||
log.debug("Error code {} sent", code);
|
||||
}
|
||||
});
|
||||
}, WS_EXECUTOR);
|
||||
}
|
||||
}
|
||||
|
||||
private void processJsonAndReply(String message) {
|
||||
if (session == null || !session.isOpen()) return;
|
||||
|
||||
log.info("📥 Получено TEXT-сообщение от клиента: {}", message);
|
||||
|
||||
String respJson;
|
||||
try {
|
||||
respJson = JsonInboundProcessor.processJson(message, connectionContext);
|
||||
} catch (Exception ex) {
|
||||
log.error("❌ Ошибка при обработке JSON-сообщения", ex);
|
||||
trySendJsonError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (respJson == null) return;
|
||||
if (session == null || !session.isOpen()) return;
|
||||
|
||||
log.info("📤 Отправляем ответ клиенту: {}", respJson);
|
||||
|
||||
session.getRemote().sendString(respJson, new WriteCallback() {
|
||||
@Override
|
||||
public void writeFailed(Throwable x) {
|
||||
log.warn("⚠️ Не удалось отправить JSON-ответ клиенту: {}", x.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSuccess() {
|
||||
log.debug("✔ JSON-ответ успешно отправлен");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@OnWebSocketClose
|
||||
public void onClose(int statusCode, String reason) {
|
||||
log.info("WS closed: {} {}", statusCode, reason);
|
||||
// Удаляем это подключение из реестра активных соединений
|
||||
|
||||
ActiveConnectionsRegistry.getInstance().remove(connectionContext);
|
||||
// На всякий случай очищаем контекст
|
||||
connectionContext.reset();
|
||||
|
||||
// “Обрываем” очередь: новые задачи всё равно не исполнятся из-за проверки session.isOpen(),
|
||||
// но можно и явно завершить хвост.
|
||||
synchronized (queueLock) {
|
||||
queueTail = CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
this.session = null;
|
||||
}
|
||||
|
||||
@OnWebSocketError
|
||||
@@ -91,49 +134,15 @@ public class BlockchainWsEndpoint {
|
||||
log.error("WS error", cause);
|
||||
}
|
||||
|
||||
// Обработка текстовых JSON-запросов
|
||||
@OnWebSocketMessage
|
||||
public void onText(String message) {
|
||||
log.info("📥 Получено TEXT-сообщение от клиента: {}", message);
|
||||
|
||||
CompletableFuture
|
||||
.supplyAsync(() -> JsonInboundProcessor.processJson(message, connectionContext))
|
||||
.thenAccept(respJson -> {
|
||||
if (respJson != null && session != null && session.isOpen()) {
|
||||
|
||||
log.info("📤 Отправляем ответ клиенту: {}", respJson);
|
||||
|
||||
session.getRemote().sendString(respJson, new WriteCallback() {
|
||||
@Override
|
||||
public void writeFailed(Throwable x) {
|
||||
log.warn("⚠️ Не удалось отправить JSON-ответ клиенту: {}", x.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSuccess() {
|
||||
log.debug("✔ JSON-ответ успешно отправлен");
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.exceptionally(ex -> {
|
||||
log.error("❌ Ошибка при обработке JSON-сообщения", ex);
|
||||
trySendJsonError();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void trySendJsonError() {
|
||||
if (session != null && session.isOpen()) {
|
||||
String resp = "{\"op\":null,\"requestId\":null,\"status\":500,"
|
||||
+ "\"payload\":{\"code\":\"INTERNAL_ERROR\",\"message\":\"Ошибка сервера\"}}";
|
||||
|
||||
log.info("📤 Отправляем клиенту ошибку JSON: {}", resp);
|
||||
|
||||
session.getRemote().sendString(resp, new WriteCallback() {
|
||||
@Override
|
||||
public void writeFailed(Throwable x) {
|
||||
log.warn("⚠️ Не удалось отправить JSON-ответ клиенту: {}", x.toString());
|
||||
log.warn("⚠️ Не удалось отправить JSON-ошибку клиенту: {}", x.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -143,4 +152,4 @@ public class BlockchainWsEndpoint {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user