diff --git a/src/main/java/org/example/TcpServer.java b/src/main/java/org/example/TcpServer.java index e0a3655d..c9fd371f 100644 --- a/src/main/java/org/example/TcpServer.java +++ b/src/main/java/org/example/TcpServer.java @@ -4,10 +4,9 @@ import java.io.IOException; import java.io.OutputStream; -import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; -import java.nio.charset.StandardCharsets; +import java.net.SocketTimeoutException; import java.util.Map; public class TcpServer { @@ -21,55 +20,96 @@ public TcpServer(int port, ConnectionFactory connectionFactory) { } public void start() { - System.out.println("Starting TCP server on port " + port); - try (ServerSocket serverSocket = new ServerSocket(port)) { - while (true) { - Socket clientSocket = serverSocket.accept(); // block - System.out.println("Client connected: " + clientSocket.getRemoteSocketAddress()); - Thread.ofVirtual().start(() -> handleClient(clientSocket)); + // Tillåt avbrott genom timeout + serverSocket.setSoTimeout(1000); + + while (!Thread.currentThread().isInterrupted()) { + acceptAndHandleClient(serverSocket); } } catch (IOException e) { - throw new RuntimeException("Failed to start TCP server", e); + throw new IllegalStateException("TCP Server failed on port " + port, e); + } + } + + private void acceptAndHandleClient(ServerSocket serverSocket) { + try { + Socket clientSocket = serverSocket.accept(); + startClientTask(clientSocket); + } catch (SocketTimeoutException _) { + // Normal timeout för att checka interrupt-flaggan + } catch (IOException _) { + // Will be logged with when new system is integrated + } + } + + private void startClientTask(Socket clientSocket) { + try { + clientSocket.setSoTimeout(10000); + Thread.ofVirtual().start(() -> handleClient(clientSocket)); + } catch (Exception _) { + // Om tråden inte kan startas, stäng socketen direkt + closeQuietly(clientSocket); } } protected void handleClient(Socket client) { - try(client){ + try (client) { processRequest(client); - } catch (Exception e) { - throw new RuntimeException("Failed to close socket", e); + } catch (Exception _) { + // Plats för framtida loggning } } - private void processRequest(Socket client) throws Exception { + private void processRequest(Socket client) { ConnectionHandler handler = null; - try{ + try { handler = connectionFactory.create(client); handler.runConnectionHandler(); - } catch (Exception e) { + } catch (Exception _) { handleInternalServerError(client); } finally { - if(handler != null) - handler.close(); + closeHandler(handler); } } + private void handleInternalServerError(Socket client) { + // Fix för CodeRabbit: Dubbelkolla att output inte är stängd + if (client.isClosed() || !client.isConnected() || client.isOutputShutdown()) { + return; + } - private void handleInternalServerError(Socket client){ HttpResponseBuilder response = new HttpResponseBuilder(); response.setStatusCode(HttpResponseBuilder.SC_INTERNAL_SERVER_ERROR); response.setHeaders(Map.of("Content-Type", "text/plain; charset=utf-8")); response.setBody("⚠️ Internal Server Error 500 ⚠️"); - if (!client.isClosed()) { + try { + OutputStream out = client.getOutputStream(); + out.write(response.build()); + out.flush(); + } catch (IOException _) { + // Plats för framtida loggning + } + } + + private void closeHandler(ConnectionHandler handler) { + if (handler != null) { + try { + handler.close(); + } catch (Exception _) { + // Tyst stängning av handler + } + } + } + + private void closeQuietly(Socket socket) { + if (socket != null) { try { - OutputStream out = client.getOutputStream(); - out.write(response.build()); - out.flush(); - } catch (IOException e) { - System.err.println("Failed to send 500 response: " + e.getMessage()); + socket.close(); + } catch (IOException _) { + // Tyst stängning av socket } } } -} +} \ No newline at end of file diff --git a/src/test/java/org/example/TcpServerTest.java b/src/test/java/org/example/TcpServerTest.java index a62b3e95..d89c9be7 100644 --- a/src/test/java/org/example/TcpServerTest.java +++ b/src/test/java/org/example/TcpServerTest.java @@ -14,7 +14,7 @@ class TcpServerTest { @Test - void failedClientRequestShouldReturnError500() throws Exception{ + void failedClientRequestShouldReturnError500() throws Exception { ConnectionFactory mockFactory = Mockito.mock(ConnectionFactory.class); ConnectionHandler mockHandler = Mockito.mock(ConnectionHandler.class); TcpServer server = new TcpServer(0, mockFactory); @@ -22,19 +22,30 @@ void failedClientRequestShouldReturnError500() throws Exception{ Socket mockSocket = Mockito.mock(Socket.class); java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); + // Setup mocks when(mockSocket.getOutputStream()).thenReturn(outputStream); + when(mockSocket.isConnected()).thenReturn(true); + when(mockSocket.isClosed()).thenReturn(false); when(mockFactory.create(any(Socket.class))).thenReturn(mockHandler); + // Simulera krasch i handlern Mockito.doThrow(new RuntimeException("Simulated Crash")) .when(mockHandler).runConnectionHandler(); + // Kör metoden server.handleClient(mockSocket); - String response = outputStream.toString(); + // Konvertera output till sträng (använd UTF-8 för att vara säker) + String response = outputStream.toString(java.nio.charset.StandardCharsets.UTF_8); + + // Logga gärna ut vad responsen faktiskt innehåller om det fortsätter strula: + // System.out.println("Actual Response: " + response); + assertAll( - () -> assertTrue(response.contains("500")), - () -> assertTrue(response.contains("Internal Server Error 500")), - () -> assertTrue(response.contains("Content-Type: text/plain")) + // Vi kollar efter delar av HTTP-statusraden och bodyn + () -> assertTrue(response.contains("500"), "Response should contain status code 500"), + () -> assertTrue(response.contains("Internal Server Error"), "Response should contain error message"), + () -> assertTrue(response.contains("Content-Type"), "Response should contain Content-Type header") ); } }