diff --git a/README.md b/README.md index 8ab3ad1..4d2eba8 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ All traffic goes through the gateway. # Technology Stack -* Java 17 +* Java 21 * Spring Boot 3 * Spring Security * JWT (JJWT) diff --git a/src/main/java/com/litoralregas/backend_gateway/gateway/websocket/VncGatewayWebSocketHandler.java b/src/main/java/com/litoralregas/backend_gateway/gateway/websocket/VncGatewayWebSocketHandler.java index 77f07b7..077be90 100644 --- a/src/main/java/com/litoralregas/backend_gateway/gateway/websocket/VncGatewayWebSocketHandler.java +++ b/src/main/java/com/litoralregas/backend_gateway/gateway/websocket/VncGatewayWebSocketHandler.java @@ -5,12 +5,22 @@ import com.litoralregas.backend_gateway.client.ClientRepository; import com.litoralregas.backend_gateway.security.JwtService; import jakarta.websocket.ContainerProvider; import jakarta.websocket.WebSocketContainer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; -import org.springframework.web.socket.*; +import org.springframework.web.socket.BinaryMessage; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.PingMessage; +import org.springframework.web.socket.PongMessage; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketMessage; +import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.client.standard.StandardWebSocketClient; import org.springframework.web.socket.handler.BinaryWebSocketHandler; import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; @@ -19,6 +29,11 @@ import java.util.concurrent.ConcurrentLinkedQueue; @Component public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { + private static final Logger log = LoggerFactory.getLogger(VncGatewayWebSocketHandler.class); + + private static final int MAX_BINARY_MESSAGE_BUFFER_SIZE = 4 * 1024 * 1024; + private static final int MAX_TEXT_MESSAGE_BUFFER_SIZE = 1024 * 1024; + private final JwtService jwtService; private final ClientRepository clientRepository; private final StandardWebSocketClient webSocketClient; @@ -34,106 +49,94 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { this.clientRepository = clientRepository; WebSocketContainer container = ContainerProvider.getWebSocketContainer(); - container.setDefaultMaxBinaryMessageBufferSize(4 * 1024 * 1024); - container.setDefaultMaxTextMessageBufferSize(1024 * 1024); + container.setDefaultMaxBinaryMessageBufferSize(MAX_BINARY_MESSAGE_BUFFER_SIZE); + container.setDefaultMaxTextMessageBufferSize(MAX_TEXT_MESSAGE_BUFFER_SIZE); this.webSocketClient = new StandardWebSocketClient(container); } @Override public void afterConnectionEstablished(WebSocketSession frontendSession) { - log("FRONTEND CONNECTED", frontendSession); - System.out.println("[VNC-GATEWAY] Frontend URI: " + frontendSession.getUri()); + log.info( + "VNC frontend websocket connected. frontendSession={}, uri={}", + frontendSession.getId(), + frontendSession.getUri() + ); try { String token = extractAccessToken(frontendSession); if (token == null || token.isBlank()) { - System.out.println("[VNC-GATEWAY] Missing access_token"); + log.warn("VNC frontend websocket rejected. reason=missing_token frontendSession={}", frontendSession.getId()); sendError(frontendSession, "Invalid or missing access token"); closeSafe(frontendSession, "missing token"); return; } - System.out.println("[VNC-GATEWAY] Token received. length=" + token.length()); - if (!jwtService.isValid(token)) { - System.out.println("[VNC-GATEWAY] Token invalid"); + log.warn("VNC frontend websocket rejected. reason=invalid_token frontendSession={}", frontendSession.getId()); sendError(frontendSession, "Invalid or missing access token"); closeSafe(frontendSession, "invalid token"); return; } - String username = jwtService.extractUsername(token); Long userId = jwtService.extractUserId(token); Long clientId = jwtService.extractClientId(token); + String username = jwtService.extractUsername(token); String role = jwtService.extractRole(token); - System.out.println("[VNC-GATEWAY] Token valid:"); - System.out.println("[VNC-GATEWAY] username=" + username); - System.out.println("[VNC-GATEWAY] userId=" + userId); - System.out.println("[VNC-GATEWAY] clientId=" + clientId); - System.out.println("[VNC-GATEWAY] role=" + role); - ClientEntity client = clientRepository.findByIdAndEnabledTrue(clientId) .orElseThrow(() -> new RuntimeException("Client not found or disabled")); - System.out.println("[VNC-GATEWAY] Client resolved:"); - System.out.println("[VNC-GATEWAY] id=" + client.getId()); - System.out.println("[VNC-GATEWAY] name=" + client.getName()); - System.out.println("[VNC-GATEWAY] backendBaseUrl=" + client.getBackendBaseUrl()); - String backendWebSocketUrl = toBackendVncWebSocketUrl(client.getBackendBaseUrl()); - System.out.println("[VNC-GATEWAY] Backend WS target: " + backendWebSocketUrl); + log.info( + "Opening VNC backend websocket. frontendSession={}, userId={}, username={}, role={}, clientId={}, clientName={}, backendUrl={}", + frontendSession.getId(), + userId, + username, + role, + client.getId(), + client.getName(), + backendWebSocketUrl + ); webSocketClient.execute( new BackendVncBridgeHandler(frontendSession), backendWebSocketUrl ).whenComplete((backendSession, error) -> { if (error != null) { - System.out.println("[VNC-GATEWAY] Backend WS connection failed"); - System.out.println("[VNC-GATEWAY] Error type: " + error.getClass().getName()); - System.out.println("[VNC-GATEWAY] Error message: " + error.getMessage()); - error.printStackTrace(); + log.error( + "VNC backend websocket connection failed. frontendSession={}, clientId={}, backendUrl={}", + frontendSession.getId(), + client.getId(), + backendWebSocketUrl, + error + ); sendError(frontendSession, "Could not connect to backend VNC websocket"); - closeSafe(frontendSession, "backend ws connection failed"); + closeSafe(frontendSession, "backend websocket connection failed"); return; } - System.out.println("[VNC-GATEWAY] Backend WS connected"); - System.out.println("[VNC-GATEWAY] Backend session id=" + backendSession.getId()); - System.out.println("[VNC-GATEWAY] Backend URI=" + backendSession.getUri()); - backendSessions.put(frontendSession.getId(), backendSession); - Queue> queue = - pendingMessages.remove(frontendSession.getId()); + log.info( + "VNC backend websocket connected. frontendSession={}, backendSession={}, backendUri={}", + frontendSession.getId(), + backendSession.getId(), + backendSession.getUri() + ); - if (queue == null || queue.isEmpty()) { - System.out.println("[VNC-GATEWAY] No queued frontend messages to flush"); - return; - } - - System.out.println("[VNC-GATEWAY] Flushing queued messages: " + queue.size()); - - int index = 0; - for (WebSocketMessage pendingMessage : queue) { - index++; - System.out.println("[VNC-GATEWAY] Flushing queued message #" + index - + " type=" + messageType(pendingMessage) - + " size=" + payloadLength(pendingMessage)); - - sendSafe(backendSession, pendingMessage, "flush queued frontend -> backend"); - } + flushPendingMessages(frontendSession, backendSession); }); } catch (Exception error) { - System.out.println("[VNC-GATEWAY] Frontend connection setup failed"); - System.out.println("[VNC-GATEWAY] Error type: " + error.getClass().getName()); - System.out.println("[VNC-GATEWAY] Error message: " + error.getMessage()); - error.printStackTrace(); + log.error( + "VNC frontend websocket setup failed. frontendSession={}", + frontendSession.getId(), + error + ); sendError(frontendSession, error.getMessage()); closeSafe(frontendSession, "setup exception"); @@ -145,10 +148,11 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { WebSocketSession frontendSession, TextMessage message ) { - System.out.println("[VNC-GATEWAY] Frontend TEXT message:" - + " session=" + frontendSession.getId() - + " size=" + message.getPayloadLength() - + " payload=" + truncate(message.getPayload())); + log.debug( + "VNC frontend text message received. frontendSession={}, size={}", + frontendSession.getId(), + message.getPayloadLength() + ); forwardOrQueue(frontendSession, message); } @@ -158,48 +162,25 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { WebSocketSession frontendSession, BinaryMessage message ) { - System.out.println("[VNC-GATEWAY] Frontend BINARY message:" - + " session=" + frontendSession.getId() - + " size=" + message.getPayloadLength()); + log.debug( + "VNC frontend binary message received. frontendSession={}, size={}", + frontendSession.getId(), + message.getPayloadLength() + ); forwardOrQueue(frontendSession, message); } - private void forwardOrQueue( - WebSocketSession frontendSession, - WebSocketMessage message - ) { - WebSocketSession backendSession = backendSessions.get(frontendSession.getId()); - - if (backendSession != null && backendSession.isOpen()) { - System.out.println("[VNC-GATEWAY] Forwarding frontend -> backend:" - + " type=" + messageType(message) - + " size=" + payloadLength(message)); - - sendSafe(backendSession, message, "frontend -> backend"); - return; - } - - Queue> queue = pendingMessages - .computeIfAbsent(frontendSession.getId(), id -> new ConcurrentLinkedQueue<>()); - - queue.add(message); - - System.out.println("[VNC-GATEWAY] Backend not ready. Queued frontend message:" - + " session=" + frontendSession.getId() - + " queueSize=" + queue.size() - + " type=" + messageType(message) - + " size=" + payloadLength(message)); - } - @Override public void afterConnectionClosed( WebSocketSession frontendSession, CloseStatus status ) { - System.out.println("[VNC-GATEWAY] Frontend WS closed:" - + " session=" + frontendSession.getId() - + " status=" + status); + log.info( + "VNC frontend websocket closed. frontendSession={}, status={}", + frontendSession.getId(), + status + ); pendingMessages.remove(frontendSession.getId()); @@ -215,32 +196,85 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { WebSocketSession frontendSession, Throwable exception ) { - System.out.println("[VNC-GATEWAY] Frontend transport error:" - + " session=" + frontendSession.getId() - + " error=" + exception.getClass().getName() - + ": " + exception.getMessage()); - - exception.printStackTrace(); + log.error( + "VNC frontend websocket transport error. frontendSession={}", + frontendSession.getId(), + exception + ); pendingMessages.remove(frontendSession.getId()); WebSocketSession backendSession = backendSessions.remove(frontendSession.getId()); + closeSafe(backendSession, "frontend transport error"); closeSafe(frontendSession, "frontend transport error"); } + private void forwardOrQueue( + WebSocketSession frontendSession, + WebSocketMessage message + ) { + WebSocketSession backendSession = backendSessions.get(frontendSession.getId()); + + if (backendSession != null && backendSession.isOpen()) { + log.debug( + "Forwarding VNC message frontend -> backend. frontendSession={}, backendSession={}, type={}, size={}", + frontendSession.getId(), + backendSession.getId(), + messageType(message), + payloadLength(message) + ); + + sendSafe(backendSession, message, "frontend -> backend"); + return; + } + + Queue> queue = pendingMessages + .computeIfAbsent(frontendSession.getId(), id -> new ConcurrentLinkedQueue<>()); + + queue.add(message); + + log.debug( + "Queued VNC frontend message because backend is not ready. frontendSession={}, queueSize={}, type={}, size={}", + frontendSession.getId(), + queue.size(), + messageType(message), + payloadLength(message) + ); + } + + private void flushPendingMessages( + WebSocketSession frontendSession, + WebSocketSession backendSession + ) { + Queue> queue = pendingMessages.remove(frontendSession.getId()); + + if (queue == null || queue.isEmpty()) { + return; + } + + log.info( + "Flushing queued VNC frontend messages. frontendSession={}, backendSession={}, count={}", + frontendSession.getId(), + backendSession.getId(), + queue.size() + ); + + for (WebSocketMessage pendingMessage : queue) { + sendSafe(backendSession, pendingMessage, "flush queued frontend -> backend"); + } + } + private String extractAccessToken(WebSocketSession session) { URI uri = session.getUri(); if (uri == null) { - System.out.println("[VNC-GATEWAY] Session URI is null"); return null; } String query = uri.getQuery(); if (query == null || query.isBlank()) { - System.out.println("[VNC-GATEWAY] Session query is empty"); return null; } @@ -250,10 +284,7 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { String[] parts = param.split("=", 2); if (parts.length == 2 && parts[0].equals("access_token")) { - return java.net.URLDecoder.decode( - parts[1], - java.nio.charset.StandardCharsets.UTF_8 - ); + return URLDecoder.decode(parts[1], StandardCharsets.UTF_8); } } @@ -261,7 +292,11 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { } private String toBackendVncWebSocketUrl(String backendBaseUrl) { - String wsBaseUrl = backendBaseUrl + String normalizedBaseUrl = backendBaseUrl.endsWith("/") + ? backendBaseUrl.substring(0, backendBaseUrl.length() - 1) + : backendBaseUrl; + + String wsBaseUrl = normalizedBaseUrl .replaceFirst("^http://", "ws://") .replaceFirst("^https://", "wss://"); @@ -270,7 +305,6 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { private void sendError(WebSocketSession session, String message) { if (session == null || !session.isOpen()) { - System.out.println("[VNC-GATEWAY] Cannot send error, session closed/null: " + message); return; } @@ -279,8 +313,6 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { safeJson(message == null ? "Unknown websocket error" : message) + "\"}"; - System.out.println("[VNC-GATEWAY] Sending error to frontend: " + payload); - sendSafe(session, new TextMessage(payload), "send error"); } @@ -297,14 +329,16 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { ) { try { if (session == null) { - System.out.println("[VNC-GATEWAY] sendSafe skipped, session null. direction=" + direction); + log.debug("VNC websocket send skipped because session is null. direction={}", direction); return; } if (!session.isOpen()) { - System.out.println("[VNC-GATEWAY] sendSafe skipped, session closed." - + " direction=" + direction - + " session=" + session.getId()); + log.debug( + "VNC websocket send skipped because session is closed. direction={}, session={}", + direction, + session.getId() + ); return; } @@ -312,49 +346,50 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { session.sendMessage(message); } - System.out.println("[VNC-GATEWAY] Sent message:" - + " direction=" + direction - + " session=" + session.getId() - + " type=" + messageType(message) - + " size=" + payloadLength(message)); + log.debug( + "VNC websocket message sent. direction={}, session={}, type={}, size={}", + direction, + session.getId(), + messageType(message), + payloadLength(message) + ); } catch (IllegalStateException closed) { - System.out.println("[VNC-GATEWAY] send skipped, session already closed. direction=" + direction); + log.debug("VNC websocket send skipped because session was already closed. direction={}", direction); } catch (Exception error) { - System.out.println("[VNC-GATEWAY] send failed: " + direction + " " + error.getMessage()); + log.warn( + "VNC websocket send failed. direction={}, session={}", + direction, + session == null ? "null" : session.getId(), + error + ); } } private void closeSafe(WebSocketSession session, String reason) { try { - if (session == null) { + if (session == null || !session.isOpen()) { return; } - if (!session.isOpen()) { - return; - } - - System.out.println("[VNC-GATEWAY] Closing session:" - + " session=" + session.getId() - + " reason=" + reason); + log.debug( + "Closing VNC websocket session. session={}, reason={}", + session.getId(), + reason + ); session.close(); } catch (Exception error) { - System.out.println("[VNC-GATEWAY] closeSafe failed:" - + " reason=" + reason - + " error=" + error.getClass().getName() - + ": " + error.getMessage()); + log.warn( + "Failed to close VNC websocket session. reason={}, session={}", + reason, + session == null ? "null" : session.getId(), + error + ); } } - private void log(String event, WebSocketSession session) { - System.out.println("[VNC-GATEWAY] " + event - + " session=" + (session == null ? "null" : session.getId()) - + " open=" + (session != null && session.isOpen())); - } - private String messageType(WebSocketMessage message) { if (message instanceof TextMessage) { return "TEXT"; @@ -383,20 +418,6 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { } } - private String truncate(String value) { - if (value == null) { - return "null"; - } - - int max = 300; - - if (value.length() <= max) { - return value; - } - - return value.substring(0, max) + "..."; - } - private class BackendVncBridgeHandler extends BinaryWebSocketHandler { private final WebSocketSession frontendSession; @@ -407,10 +428,12 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { @Override public void afterConnectionEstablished(WebSocketSession backendSession) { - System.out.println("[VNC-GATEWAY] Backend handler established:" - + " backendSession=" + backendSession.getId() - + " frontendSession=" + frontendSession.getId() - + " backendUri=" + backendSession.getUri()); + log.debug( + "VNC backend bridge handler established. frontendSession={}, backendSession={}, backendUri={}", + frontendSession.getId(), + backendSession.getId(), + backendSession.getUri() + ); } @Override @@ -418,10 +441,12 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { WebSocketSession backendSession, TextMessage message ) { - System.out.println("[VNC-GATEWAY] Backend TEXT message:" - + " backendSession=" + backendSession.getId() - + " size=" + message.getPayloadLength() - + " payload=" + truncate(message.getPayload())); + log.debug( + "VNC backend text message received. backendSession={}, frontendSession={}, size={}", + backendSession.getId(), + frontendSession.getId(), + message.getPayloadLength() + ); sendSafe(frontendSession, message, "backend -> frontend"); } @@ -431,9 +456,12 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { WebSocketSession backendSession, BinaryMessage message ) { - System.out.println("[VNC-GATEWAY] Backend BINARY message:" - + " backendSession=" + backendSession.getId() - + " size=" + message.getPayloadLength()); + log.debug( + "VNC backend binary message received. backendSession={}, frontendSession={}, size={}", + backendSession.getId(), + frontendSession.getId(), + message.getPayloadLength() + ); sendSafe(frontendSession, message, "backend -> frontend"); } @@ -443,10 +471,12 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { WebSocketSession backendSession, CloseStatus status ) { - System.out.println("[VNC-GATEWAY] Backend WS closed:" - + " backendSession=" + backendSession.getId() - + " frontendSession=" + frontendSession.getId() - + " status=" + status); + log.info( + "VNC backend websocket closed. backendSession={}, frontendSession={}, status={}", + backendSession.getId(), + frontendSession.getId(), + status + ); backendSessions.remove(frontendSession.getId()); pendingMessages.remove(frontendSession.getId()); @@ -459,13 +489,12 @@ public class VncGatewayWebSocketHandler extends BinaryWebSocketHandler { WebSocketSession backendSession, Throwable exception ) { - System.out.println("[VNC-GATEWAY] Backend transport error:" - + " backendSession=" + backendSession.getId() - + " frontendSession=" + frontendSession.getId() - + " error=" + exception.getClass().getName() - + ": " + exception.getMessage()); - - exception.printStackTrace(); + log.error( + "VNC backend websocket transport error. backendSession={}, frontendSession={}", + backendSession.getId(), + frontendSession.getId(), + exception + ); sendError(frontendSession, "Backend websocket transport error");