Skip to content
90 changes: 65 additions & 25 deletions src/main/java/org/example/TcpServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
}
Comment thread
gitnes94 marked this conversation as resolved.
} 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
}
}
}
}
}
21 changes: 16 additions & 5 deletions src/test/java/org/example/TcpServerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,38 @@ 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);

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")
);
}
}