diff --git a/src/main/java/org/example/App.java b/src/main/java/org/example/App.java index 966e9563..5b035a1e 100644 --- a/src/main/java/org/example/App.java +++ b/src/main/java/org/example/App.java @@ -17,7 +17,8 @@ public static void main(String[] args) { int port = resolvePort(args, appConfig.server().port()); - new TcpServer(port, ConnectionHandler::new).start(); + FileCache fileCache = new FileCache(10); + new TcpServer(port, socket -> new ConnectionHandler(socket, fileCache)).start(); } static int resolvePort(String[] args, int configPort) { diff --git a/src/main/java/org/example/ConnectionHandler.java b/src/main/java/org/example/ConnectionHandler.java index 1fcc29fb..6f3111a4 100644 --- a/src/main/java/org/example/ConnectionHandler.java +++ b/src/main/java/org/example/ConnectionHandler.java @@ -14,22 +14,23 @@ import java.io.IOException; import java.net.Socket; -public class ConnectionHandler implements AutoCloseable { +import java.io.OutputStream; +import java.nio.file.NoSuchFileException; - Socket client; - String uri; +public class ConnectionHandler implements AutoCloseable { + private final Socket client; private final List filters; - String webRoot; + private final String webRoot; + private final FileCache fileCache; - public ConnectionHandler(Socket client) { - this.client = client; - this.filters = buildFilters(); - this.webRoot = null; + public ConnectionHandler(Socket client, FileCache fileCache) { + this(client, "www", fileCache); } - public ConnectionHandler(Socket client, String webRoot) { + public ConnectionHandler(Socket client, String webRoot, FileCache fileCache) { this.client = client; this.webRoot = webRoot; + this.fileCache = fileCache; this.filters = buildFilters(); } @@ -40,67 +41,73 @@ private List buildFilters() { if (Boolean.TRUE.equals(ipFilterConfig.enabled())) { list.add(createIpFilterFromConfig(ipFilterConfig)); } - // Add more filters here... return list; } public void runConnectionHandler() throws IOException { - StaticFileHandler sfh; + HttpParser parser = parseRequest(); + HttpRequest request = buildHttpRequest(parser); - if (webRoot != null) { - sfh = new StaticFileHandler(webRoot); - } else { - sfh = new StaticFileHandler(); - } + if (isForbiddenByFilters(request)) return; + if (isPathTraversal(parser.getUri())) return; + + serveFile(parser.getUri()); + } + private HttpParser parseRequest() throws IOException { HttpParser parser = new HttpParser(); parser.setReader(client.getInputStream()); parser.parseRequest(); parser.parseHttp(); + return parser; + } + private HttpRequest buildHttpRequest(HttpParser parser) { HttpRequest request = new HttpRequest( - parser.getMethod(), - parser.getUri(), - parser.getVersion(), - parser.getHeadersMap(), - "" + parser.getMethod(), parser.getUri(), parser.getVersion(), + parser.getHeadersMap(), "" ); + request.setAttribute("clientIp", client.getInetAddress().getHostAddress()); + return request; + } - String clientIp = client.getInetAddress().getHostAddress(); - request.setAttribute("clientIp", clientIp); - - HttpResponseBuilder response = applyFilters(request); + private boolean isForbiddenByFilters(HttpRequest request) throws IOException { + HttpResponseBuilder response = new HttpResponseBuilder(); + new FilterChainImpl(filters).doFilter(request, response); - int statusCode = response.getStatusCode(); - if (statusCode == HttpResponseBuilder.SC_FORBIDDEN || - statusCode == HttpResponseBuilder.SC_BAD_REQUEST) { - byte[] responseBytes = response.build(); - client.getOutputStream().write(responseBytes); + int status = response.getStatusCode(); + if (status == HttpResponseBuilder.SC_FORBIDDEN || status == HttpResponseBuilder.SC_BAD_REQUEST) { + client.getOutputStream().write(response.build()); client.getOutputStream().flush(); - return; + return true; } - - resolveTargetFile(parser.getUri()); - sfh.sendGetRequest(client.getOutputStream(), uri); + return false; } - private HttpResponseBuilder applyFilters(HttpRequest request) { - HttpResponseBuilder response = new HttpResponseBuilder(); - - FilterChainImpl chain = new FilterChainImpl(filters); - chain.doFilter(request, response); - - return response; + private boolean isPathTraversal(String uri) throws IOException { + if (uri.contains("..")) { + sendErrorResponse(client.getOutputStream(), 403, "Forbidden"); + return true; + } + return false; } - private void resolveTargetFile(String uri) { - if (uri == null || "/".equals(uri)) { - this.uri = "index.html"; - } else { - this.uri = uri.startsWith("/") ? uri.substring(1) : uri; + private void serveFile(String uri) throws IOException { + StaticFileHandler sfh = new StaticFileHandler(webRoot, fileCache); + try { + sfh.sendGetRequest(client.getOutputStream(), uri); + } catch (NoSuchFileException e) { + sendErrorResponse(client.getOutputStream(), 404, "Not Found"); + } catch (Exception e) { + sendErrorResponse(client.getOutputStream(), 500, "Internal Server Error"); } } + private void sendErrorResponse(OutputStream out, int statusCode, String message) throws IOException { + out.write(HttpResponseBuilder.createErrorResponse(statusCode, message)); + out.flush(); + } + @Override public void close() throws Exception { client.close(); @@ -109,19 +116,16 @@ public void close() throws Exception { private IpFilter createIpFilterFromConfig(AppConfig.IpFilterConfig config) { IpFilter filter = new IpFilter(); - // Set mode if ("ALLOWLIST".equalsIgnoreCase(config.mode())) { filter.setMode(IpFilter.FilterMode.ALLOWLIST); } else { filter.setMode(IpFilter.FilterMode.BLOCKLIST); } - // Add blocked IPs for (String ip : config.blockedIps()) { filter.addBlockedIp(ip); } - // Add allowed IPs for (String ip : config.allowedIps()) { filter.addAllowedIp(ip); } diff --git a/src/main/java/org/example/FileCache.java b/src/main/java/org/example/FileCache.java new file mode 100644 index 00000000..166053d3 --- /dev/null +++ b/src/main/java/org/example/FileCache.java @@ -0,0 +1,24 @@ +package org.example; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +public class FileCache { + private final Map cache; + + public FileCache(int maxSize) { + this.cache = Collections.synchronizedMap(new LinkedHashMap(maxSize, 0.75f, true) { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > maxSize; + } + }); + } + + public byte[] get(String key) { return cache.get(key); } + public void put(String key, byte[] content) { cache.put(key, content); } + public void clear() { cache.clear(); } + public int size() { return cache.size(); } +} + diff --git a/src/main/java/org/example/StaticFileHandler.java b/src/main/java/org/example/StaticFileHandler.java index 8bcda375..e9023a51 100644 --- a/src/main/java/org/example/StaticFileHandler.java +++ b/src/main/java/org/example/StaticFileHandler.java @@ -1,69 +1,58 @@ + package org.example; import org.example.http.HttpResponseBuilder; -import static org.example.http.HttpResponseBuilder.*; - -import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; +import java.nio.file.Path; public class StaticFileHandler { - private final String WEB_ROOT; - private byte[] fileBytes; - private int statusCode; + private static final String DEFAULT_WEB_ROOT = "www"; + private final String webRoot; + private final FileCache fileCache; - // Constructor for production - public StaticFileHandler() { - WEB_ROOT = "www"; + public StaticFileHandler(FileCache fileCache) { + this(DEFAULT_WEB_ROOT, fileCache); } - // Constructor for tests, otherwise the www folder won't be seen - public StaticFileHandler(String webRoot) { - WEB_ROOT = webRoot; + public StaticFileHandler(String webRoot, FileCache fileCache) { + this.webRoot = webRoot; + this.fileCache = fileCache; } - private void handleGetRequest(String uri) throws IOException { - // Sanitize URI - int q = uri.indexOf('?'); - if (q >= 0) uri = uri.substring(0, q); - int h = uri.indexOf('#'); - if (h >= 0) uri = uri.substring(0, h); - uri = uri.replace("\0", ""); - if (uri.startsWith("/")) uri = uri.substring(1); + public void sendGetRequest(OutputStream outputStream, String uri) throws IOException { + String sanitizedUri = sanitizeUri(uri); + Path filePath = Path.of(webRoot, sanitizedUri); - // Path traversal check - File root = new File(WEB_ROOT).getCanonicalFile(); - File file = new File(root, uri).getCanonicalFile(); - if (!file.toPath().startsWith(root.toPath())) { - fileBytes = "403 Forbidden".getBytes(java.nio.charset.StandardCharsets.UTF_8); - statusCode = SC_FORBIDDEN; - return; - } + // Använd fullständig sökväg som cache-nyckel + String cacheKey = filePath.toString(); + byte[] fileBytes = fileCache.get(cacheKey); - // Read file - if (file.isFile()) { - fileBytes = Files.readAllBytes(file.toPath()); - statusCode = SC_OK; - } else { - File errorFile = new File(WEB_ROOT, "pageNotFound.html"); - if (errorFile.isFile()) { - fileBytes = Files.readAllBytes(errorFile.toPath()); - } else { - fileBytes = "404 Not Found".getBytes(java.nio.charset.StandardCharsets.UTF_8); - } - statusCode = SC_NOT_FOUND; + if (fileBytes == null) { + fileBytes = Files.readAllBytes(filePath); + fileCache.put(cacheKey, fileBytes); } - } - public void sendGetRequest(OutputStream outputStream, String uri) throws IOException { - handleGetRequest(uri); HttpResponseBuilder response = new HttpResponseBuilder(); - response.setStatusCode(statusCode); - // Use MimeTypeDetector instead of hardcoded text/html - response.setContentTypeFromFilename(uri); + response.setContentTypeFromFilename(sanitizedUri); response.setBody(fileBytes); outputStream.write(response.build()); outputStream.flush(); } + + private String sanitizeUri(String uri) { + if (uri == null || uri.isEmpty() || uri.equals("/")) return "index.html"; + + // Ta bort query, anchors, start-snedstreck och null-bytes + String cleanUri = uri.split("[?#]")[0] + .replaceAll("^/+", "") + .replace("\0", ""); + + return cleanUri.isEmpty() ? "index.html" : cleanUri; + } + + public void clearCache() { + fileCache.clear(); + } } diff --git a/src/main/java/org/example/TcpServer.java b/src/main/java/org/example/TcpServer.java index e0a3655d..b6cc8cad 100644 --- a/src/main/java/org/example/TcpServer.java +++ b/src/main/java/org/example/TcpServer.java @@ -4,11 +4,8 @@ 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.util.Map; public class TcpServer { @@ -43,29 +40,19 @@ protected void handleClient(Socket client) { } private void processRequest(Socket client) throws Exception { - ConnectionHandler handler = null; - try{ - handler = connectionFactory.create(client); + try (ConnectionHandler handler = connectionFactory.create(client)) { handler.runConnectionHandler(); } catch (Exception e) { handleInternalServerError(client); - } finally { - if(handler != null) - handler.close(); } } 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.write(HttpResponseBuilder.createErrorResponse(500, "Internal Server Error")); out.flush(); } catch (IOException e) { System.err.println("Failed to send 500 response: " + e.getMessage()); diff --git a/src/main/java/org/example/http/HttpResponseBuilder.java b/src/main/java/org/example/http/HttpResponseBuilder.java index 9b6ed2a7..53050526 100644 --- a/src/main/java/org/example/http/HttpResponseBuilder.java +++ b/src/main/java/org/example/http/HttpResponseBuilder.java @@ -93,6 +93,18 @@ public void setContentTypeFromFilename(String filename) { setHeader("Content-Type", mimeType); } + public void setError(int statusCode, String message) { + setStatusCode(statusCode); + setHeader("Content-Type", "text/html; charset=UTF-8"); + setBody(String.format("

%d %s

", statusCode, message)); + } + + public static byte[] createErrorResponse(int statusCode, String message) { + HttpResponseBuilder builder = new HttpResponseBuilder(); + builder.setError(statusCode, message); + return builder.build(); + } + /* * Builds the complete HTTP response as a byte array and preserves binary content without corruption. * @return Complete HTTP response (headers + body) as byte[] diff --git a/src/test/java/org/example/ConnectionHandlerTest.java b/src/test/java/org/example/ConnectionHandlerTest.java index ee366fa2..b55ddca8 100644 --- a/src/test/java/org/example/ConnectionHandlerTest.java +++ b/src/test/java/org/example/ConnectionHandlerTest.java @@ -28,6 +28,8 @@ class ConnectionHandlerTest { @TempDir Path tempDir; + private final FileCache cache = new FileCache(10); + @BeforeAll static void setupConfig() { ConfigLoader.resetForTests(); @@ -49,7 +51,7 @@ void test_jpg_file_should_return_200_not_404() throws Exception { when(socket.getInetAddress()).thenReturn(InetAddress.getByName("127.0.0.1")); // Act - try (ConnectionHandler handler = new ConnectionHandler(socket, tempDir.toString())) { + try (ConnectionHandler handler = new ConnectionHandler(socket, tempDir.toString(), cache)) { handler.runConnectionHandler(); } @@ -74,7 +76,7 @@ void test_root_path_should_serve_index_html() throws Exception { when(socket.getInetAddress()).thenReturn(InetAddress.getByName("127.0.0.1")); // Act - try (ConnectionHandler handler = new ConnectionHandler(socket, tempDir.toString())) { + try (ConnectionHandler handler = new ConnectionHandler(socket, tempDir.toString(), cache)) { handler.runConnectionHandler(); } @@ -96,7 +98,7 @@ void test_missing_file_should_return_404() throws Exception { when(socket.getInetAddress()).thenReturn(InetAddress.getByName("127.0.0.1")); // Act - try (ConnectionHandler handler = new ConnectionHandler(socket, tempDir.toString())) { + try (ConnectionHandler handler = new ConnectionHandler(socket, tempDir.toString(), cache)) { handler.runConnectionHandler(); } @@ -104,4 +106,26 @@ void test_missing_file_should_return_404() throws Exception { String response = outputStream.toString(); assertThat(response).contains("404"); } + + @Test + void test_path_traversal_should_return_403() throws Exception { + // Arrange + String request = "GET /../secret.txt HTTP/1.1\r\nHost: localhost\r\n\r\n"; + ByteArrayInputStream inputStream = new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8)); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + when(socket.getInputStream()).thenReturn(inputStream); + when(socket.getOutputStream()).thenReturn(outputStream); + when(socket.getInetAddress()).thenReturn(InetAddress.getByName("127.0.0.1")); + + // Act + try (ConnectionHandler handler = new ConnectionHandler(socket, tempDir.toString(), cache)) { + handler.runConnectionHandler(); + } + + // Assert + String response = outputStream.toString(); + assertThat(response).contains("403"); + assertThat(response).contains("Forbidden"); + } } \ No newline at end of file diff --git a/src/test/java/org/example/StaticFileHandlerTest.java b/src/test/java/org/example/StaticFileHandlerTest.java index ce6feb7a..0ac47a1f 100644 --- a/src/test/java/org/example/StaticFileHandlerTest.java +++ b/src/test/java/org/example/StaticFileHandlerTest.java @@ -1,16 +1,16 @@ package org.example; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import static org.junit.jupiter.api.Assertions.*; -import static org.example.http.HttpResponseBuilder.*; +import static org.assertj.core.api.Assertions.assertThat; /** * Unit test class for verifying the behavior of the StaticFileHandler class. @@ -26,113 +26,167 @@ */ class StaticFileHandlerTest { - //Junit creates a temporary folder which can be filled with temporary files that gets removed after tests @TempDir Path tempDir; + private FileCache cache; - @Test - void test_file_that_exists_should_return_200() throws IOException { - //Arrange - Path testFile = tempDir.resolve("test.html"); // Defines the path in the temp directory - Files.writeString(testFile, "Hello Test"); // Creates a text in that file + @BeforeEach + void setUp() { + cache = new FileCache(10); + } - //Using the new constructor in StaticFileHandler to reroute so the tests uses the temporary folder instead of the hardcoded www - StaticFileHandler staticFileHandler = new StaticFileHandler(tempDir.toString()); + private StaticFileHandler createHandler(){ + return new StaticFileHandler(tempDir.toString(), cache); + } - //Using ByteArrayOutputStream instead of Outputstream during tests to capture the servers response in memory, fake stream - ByteArrayOutputStream fakeOutput = new ByteArrayOutputStream(); + private String sendRequest(String uri) throws IOException { + StaticFileHandler handler = createHandler(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + handler.sendGetRequest(output, uri); + return output.toString(); + } - //Act - staticFileHandler.sendGetRequest(fakeOutput, "test.html"); //Get test.html and write the answer to fakeOutput + @Test + void testCaching_HitOnSecondRequest() throws IOException { + // Arrange + Files.writeString(tempDir.resolve("cached.html"), "Content"); - //Assert - String response = fakeOutput.toString();//Converts the captured byte stream into a String for verification + // Act + String response1 = sendRequest("cached.html"); + String response2 = sendRequest("cached.html"); - assertTrue(response.contains("HTTP/1.1 " + SC_OK + " OK")); // Assert the status - assertTrue(response.contains("Hello Test")); //Assert the content in the file + // Assert + assertThat(response1).contains("HTTP/1.1 200"); + assertThat(response2).contains("HTTP/1.1 200"); + } - assertTrue(response.contains("Content-Type: text/html; charset=UTF-8")); // Verify the correct Content-type header + @Test + void testSanitization_QueryString() throws IOException { + Files.writeString(tempDir.resolve("index.html"), "Home"); + assertThat(sendRequest("index.html?foo=bar")).contains("HTTP/1.1 200"); } @Test - void test_file_that_does_not_exists_should_return_404() throws IOException { - //Arrange - // Pre-create the mandatory error page in the temp directory to prevent NoSuchFileException - Path testFile = tempDir.resolve("pageNotFound.html"); - Files.writeString(testFile, "Fallback page"); - - //Using the new constructor in StaticFileHandler to reroute so the tests uses the temporary folder instead of the hardcoded www - StaticFileHandler staticFileHandler = new StaticFileHandler(tempDir.toString()); - - //Using ByteArrayOutputStream instead of Outputstream during tests to capture the servers response in memory, fake stream - ByteArrayOutputStream fakeOutput = new ByteArrayOutputStream(); - - //Act - staticFileHandler.sendGetRequest(fakeOutput, "notExistingFile.html"); // Request a file that clearly doesn't exist to trigger the 404 logic - - //Assert - String response = fakeOutput.toString();//Converts the captured byte stream into a String for verification + void testSanitization_LeadingSlash() throws IOException { + Files.writeString(tempDir.resolve("page.html"), "Page"); + assertThat(sendRequest("/page.html")).contains("HTTP/1.1 200"); + } - assertTrue(response.contains("HTTP/1.1 " + SC_NOT_FOUND + " Not Found")); // Assert the status + @Test + void testSanitization_NullBytes() { + assertThrows(NoSuchFileException.class, () -> { + sendRequest("file.html\0../../secret"); + }); + } + @Test + void testConcurrent_MultipleReads() throws InterruptedException, IOException { + // Arrange + Files.writeString(tempDir.resolve("shared.html"), "Data"); + StaticFileHandler handler = new StaticFileHandler(tempDir.toString(), cache); + + handler.sendGetRequest(new ByteArrayOutputStream(), "shared.html"); + + // Act - 10 threads reading same file 50 times each + Thread[] threads = new Thread[10]; + final Exception[] threadError = new Exception[1]; + + for (int i = 0; i < 10; i++) { + threads[i] = new Thread(() -> { + try { + for (int j = 0; j < 50; j++) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + handler.sendGetRequest(out, "shared.html"); + String response = out.toString(); + + if (!response.contains("HTTP/1.1 200") || !response.contains("Data")) { + throw new AssertionError("Unexpected response"); + } + } + } catch (Exception e) { + synchronized (threads) { + threadError[0] = e; + } + } + }); + threads[i].start(); + } + + // Wait for all threads to complete + for (Thread thread : threads) { + thread.join(); } + + // Assert + assertNull(threadError[0], "Thread threw exception: " + (threadError[0] != null ? threadError[0].getMessage() : "")); +} @Test - void test_path_traversal_should_return_403() throws IOException { + void test_file_that_exists_should_return_200() throws IOException { // Arrange - Path secret = tempDir.resolve("secret.txt"); - Files.writeString(secret,"TOP SECRET"); - Path webRoot = tempDir.resolve("www"); - Files.createDirectories(webRoot); - StaticFileHandler handler = new StaticFileHandler(webRoot.toString()); + Path testFile = tempDir.resolve("test.html"); + Files.writeString(testFile, "Hello Test"); + + StaticFileHandler staticFileHandler = new StaticFileHandler(tempDir.toString(), cache); ByteArrayOutputStream fakeOutput = new ByteArrayOutputStream(); // Act - handler.sendGetRequest(fakeOutput, "../secret.txt"); + staticFileHandler.sendGetRequest(fakeOutput, "test.html"); // Assert String response = fakeOutput.toString(); - assertFalse(response.contains("TOP SECRET")); - assertTrue(response.contains("HTTP/1.1 " + SC_FORBIDDEN + " Forbidden")); + assertTrue(response.contains("HTTP/1.1 200 OK")); + assertTrue(response.contains("Hello Test")); + assertTrue(response.contains("Content-Type: text/html; charset=UTF-8")); } - @ParameterizedTest - @CsvSource({ - "index.html?foo=bar", - "index.html#section", - "/index.html" - }) - void sanitized_uris_should_return_200(String uri) throws IOException { + @Test + void test_file_that_does_not_exists_should_throw_exception() { // Arrange - Path webRoot = tempDir.resolve("www"); - Files.createDirectories(webRoot); - Files.writeString(webRoot.resolve("index.html"), "Hello"); - StaticFileHandler handler = new StaticFileHandler(webRoot.toString()); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - - // Act - handler.sendGetRequest(out, uri); + StaticFileHandler staticFileHandler = new StaticFileHandler(tempDir.toString(), cache); + ByteArrayOutputStream fakeOutput = new ByteArrayOutputStream(); - // Assert - assertTrue(out.toString().contains("HTTP/1.1 " + SC_OK + " OK")); + // Act & Assert + assertThrows(NoSuchFileException.class, () -> { + staticFileHandler.sendGetRequest(fakeOutput, "notExistingFile.html"); + }); } @Test - void null_byte_injection_should_not_return_200() throws IOException { + void null_byte_injection_should_throw_exception() throws IOException { // Arrange Path webRoot = tempDir.resolve("www"); Files.createDirectories(webRoot); - StaticFileHandler handler = new StaticFileHandler(webRoot.toString()); + StaticFileHandler handler = new StaticFileHandler(webRoot.toString(), cache); ByteArrayOutputStream out = new ByteArrayOutputStream(); - // Act - handler.sendGetRequest(out, "index.html\0../../etc/passwd"); + // Act & Assert + assertThrows(NoSuchFileException.class, () -> { + handler.sendGetRequest(out, "index.html\0../../etc/passwd"); + }); + } - // Assert - String response = out.toString(); - assertFalse(response.contains("HTTP/1.1 " + SC_OK + " OK")); - assertTrue(response.contains("HTTP/1.1 " + SC_NOT_FOUND + " Not Found")); + @Test + void testMaxCacheSize() throws IOException { + FileCache limitedCache = new FileCache(2); + StaticFileHandler handler = new StaticFileHandler(tempDir.toString(), limitedCache); + + Files.writeString(tempDir.resolve("1.html"), "1"); + Files.writeString(tempDir.resolve("2.html"), "2"); + Files.writeString(tempDir.resolve("3.html"), "3"); + + handler.sendGetRequest(new ByteArrayOutputStream(), "1.html"); + handler.sendGetRequest(new ByteArrayOutputStream(), "2.html"); + assertEquals(2, limitedCache.size()); + + handler.sendGetRequest(new ByteArrayOutputStream(), "3.html"); + assertEquals(2, limitedCache.size()); + + // 1.html bör ha tagits bort eftersom det var äldst (LRU) + assertNull(limitedCache.get(tempDir.resolve("1.html").toString())); + assertNotNull(limitedCache.get(tempDir.resolve("2.html").toString())); + assertNotNull(limitedCache.get(tempDir.resolve("3.html").toString())); } } diff --git a/src/test/java/org/example/TcpServerTest.java b/src/test/java/org/example/TcpServerTest.java index a62b3e95..9b62f19e 100644 --- a/src/test/java/org/example/TcpServerTest.java +++ b/src/test/java/org/example/TcpServerTest.java @@ -33,8 +33,8 @@ void failedClientRequestShouldReturnError500() throws Exception{ String response = outputStream.toString(); assertAll( () -> assertTrue(response.contains("500")), - () -> assertTrue(response.contains("Internal Server Error 500")), - () -> assertTrue(response.contains("Content-Type: text/plain")) + () -> assertTrue(response.contains("Internal Server Error")), + () -> assertTrue(response.contains("Content-Type: text/html")) ); } }