From 843b34ce798f9b4b075c000fecf96fe6c0226c47 Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Tue, 17 Feb 2026 00:00:56 +0100 Subject: [PATCH 01/21] initial commit --- .../org/example/http/HttpCachingHeaders.java | 54 ++++++++++++ .../example/http/HttpCachingHeadersTest.java | 85 +++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 src/main/java/org/example/http/HttpCachingHeaders.java create mode 100644 src/test/java/org/example/http/HttpCachingHeadersTest.java diff --git a/src/main/java/org/example/http/HttpCachingHeaders.java b/src/main/java/org/example/http/HttpCachingHeaders.java new file mode 100644 index 00000000..d70f0f6e --- /dev/null +++ b/src/main/java/org/example/http/HttpCachingHeaders.java @@ -0,0 +1,54 @@ +package org.example.http; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.LinkedHashMap; +import java.util.Map; + +public class HttpCachingHeaders { + + private static final String CACHE_CONTROL = "Cache-Control"; + private static final String LAST_MODIFIED = "Last-Modified"; + private static final String ETAG = "ETag"; + + private static final DateTimeFormatter HTTP_DATE_FORMATTER = + DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC); + + + private final Map headers = new LinkedHashMap<>(); + + + public void setHeader(String name, String value) { + headers.put(name, value); + } + + public void addETagHeader(String etag) { + setHeader(ETAG, etag); + } + + public void setCacheControl(String cacheControl) { + setHeader(CACHE_CONTROL, cacheControl); + } + + public void setLastModified(Instant instant){ + setHeader(LAST_MODIFIED, HTTP_DATE_FORMATTER.format(instant)); + } + + + + public void applyTo(Map target){ + target.putAll(headers); + } + + public Map getHeaders() { + return new LinkedHashMap<>(headers); + } + + public void setDefaultCacheControlStatic(){ + setCacheControl("public, max-age=3600"); + } + + + +} diff --git a/src/test/java/org/example/http/HttpCachingHeadersTest.java b/src/test/java/org/example/http/HttpCachingHeadersTest.java new file mode 100644 index 00000000..c6a937ce --- /dev/null +++ b/src/test/java/org/example/http/HttpCachingHeadersTest.java @@ -0,0 +1,85 @@ +package org.example.http; + +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class HttpCachingHeadersTest { + + + @Test + void shouldStoreEtagValue() { + + HttpCachingHeaders cachingHeaders = new HttpCachingHeaders(); + + cachingHeaders.addETagHeader("123456789"); + + String etagValue = cachingHeaders.getHeaders().get("ETag"); + + assertThat(etagValue).isEqualTo("123456789"); + + } + + + + @Test + void CacheControlReturnsCorrectValue() { + HttpCachingHeaders cachingHeaders = new HttpCachingHeaders(); + cachingHeaders.setCacheControl("public, max-age=3600"); + + String etagValue = cachingHeaders.getHeaders().get("Cache-Control"); + + assertThat(etagValue).isEqualTo("public, max-age=3600"); + + } + + @Test + void setDefaultCacheControlStatic_shouldSetPublicMaxAge3600(){ + HttpCachingHeaders cachingHeaders = new HttpCachingHeaders(); + + cachingHeaders.setDefaultCacheControlStatic(); + + String etagValue = cachingHeaders.getHeaders().get("Cache-Control"); + + assertThat(etagValue).isEqualTo("public, max-age=3600"); + + } + + // Verifies that applyTo() copies all configured caching headers + // into the provided target map. + @Test + void applyToResponse(){ + Map target = new LinkedHashMap<>(); + HttpCachingHeaders cachingHeaders = new HttpCachingHeaders(); + cachingHeaders.addETagHeader("123456789"); + cachingHeaders.applyTo(target); + + assertThat(target).containsEntry("ETag", "123456789"); + } + + // Verifies that getHeaders() returns a defensive copy + // so external modifications do not affect internal state. + @Test + void getHeaders_shouldReturnDefensiveCopy(){ + + HttpCachingHeaders cachingHeaders = new HttpCachingHeaders(); + + cachingHeaders.addETagHeader("123"); + + Map returnedMap = cachingHeaders.getHeaders(); + + returnedMap.put("Etag", "hacked"); + + Map returnedMap2 = cachingHeaders.getHeaders(); + + assertThat(returnedMap.get("Etag")).isEqualTo("hacked"); + + assertThat(cachingHeaders.getHeaders().get("ETag")).isEqualTo("123"); + + } +} From 2844d6e4b6576eafaa7f6dc1b78ae23202869847 Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Tue, 17 Feb 2026 14:30:45 +0100 Subject: [PATCH 02/21] Add `setNoCache` method to configure no-cache headers --- src/main/java/org/example/http/HttpCachingHeaders.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/example/http/HttpCachingHeaders.java b/src/main/java/org/example/http/HttpCachingHeaders.java index d70f0f6e..c8ceabf0 100644 --- a/src/main/java/org/example/http/HttpCachingHeaders.java +++ b/src/main/java/org/example/http/HttpCachingHeaders.java @@ -35,6 +35,10 @@ public void setLastModified(Instant instant){ setHeader(LAST_MODIFIED, HTTP_DATE_FORMATTER.format(instant)); } + public void setNoCache() { + setCacheControl("no-store, no-cache, must-revalidate"); + } + public void applyTo(Map target){ From 0048a056bd389e0e6b241c45af91f4f06545a349 Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Wed, 18 Feb 2026 15:34:25 +0100 Subject: [PATCH 03/21] Improve Javadocs for `HttpCachingHeaders` and `HttpCachingHeadersTest` --- .../org/example/http/HttpCachingHeaders.java | 51 ++++++++++++++++++- .../example/http/HttpCachingHeadersTest.java | 18 +++++-- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/example/http/HttpCachingHeaders.java b/src/main/java/org/example/http/HttpCachingHeaders.java index c8ceabf0..cfd8e0f2 100644 --- a/src/main/java/org/example/http/HttpCachingHeaders.java +++ b/src/main/java/org/example/http/HttpCachingHeaders.java @@ -6,8 +6,21 @@ import java.util.LinkedHashMap; import java.util.Map; +/** + * Helper class for building HTTP response headers + * Lets the client reuse cached responses + * Reduces bandwidth + * Reduces latency + * Reduces load on your server + * Ensures webserver and proxies understand caching instructions + */ public class HttpCachingHeaders { + /** + * Cache Control helps manage servers and browsers by settings rules + * ETag helps cache be more efficient and not needing to send a full resend assuming the content has not changed + * Last-Modified + */ private static final String CACHE_CONTROL = "Cache-Control"; private static final String LAST_MODIFIED = "Last-Modified"; private static final String ETAG = "ETag"; @@ -19,36 +32,70 @@ public class HttpCachingHeaders { private final Map headers = new LinkedHashMap<>(); + /** + * Sets a header + * @param name Header name eg. Cache-Control + * @param value Header value eg. public, max-age=3600 + */ public void setHeader(String name, String value) { headers.put(name, value); } + /** + * Helper method for setting ETag header value + * ETag values must be enclosed in double quotes "123" not 123 + * @param etag ETag value as quouted per RFC 7232 + * + */ public void addETagHeader(String etag) { - setHeader(ETAG, etag); + setHeader(ETAG, "\"" + etag + "\""); } + /** + * Sets Cache-Control header value + * @param cacheControl sets rules eg. public, max-age=3600 + */ public void setCacheControl(String cacheControl) { setHeader(CACHE_CONTROL, cacheControl); } + /** + * Helper method for setting Last-Modified header value + * Formates and sets Last modified based on instant + * @param instant Timestamp of last modification + */ public void setLastModified(Instant instant){ setHeader(LAST_MODIFIED, HTTP_DATE_FORMATTER.format(instant)); } + + /** + * Incase of errors or unexpected behaviour, cache should be disabled and no data should be saved + */ public void setNoCache() { setCacheControl("no-store, no-cache, must-revalidate"); } - + /** + * Copies all configured caching headers into the provided target map eg. HttpReponseBuilder. + * @param target Map should return generated headers + */ public void applyTo(Map target){ target.putAll(headers); } + /** + * Maps all configured caching headers into a new map + * @return A map which includes all caching headers + */ public Map getHeaders() { return new LinkedHashMap<>(headers); } + /** + * Standard settings for caching, 1 hour + */ public void setDefaultCacheControlStatic(){ setCacheControl("public, max-age=3600"); } diff --git a/src/test/java/org/example/http/HttpCachingHeadersTest.java b/src/test/java/org/example/http/HttpCachingHeadersTest.java index c6a937ce..b9c84f26 100644 --- a/src/test/java/org/example/http/HttpCachingHeadersTest.java +++ b/src/test/java/org/example/http/HttpCachingHeadersTest.java @@ -9,6 +9,9 @@ import static org.assertj.core.api.Assertions.assertThat; +/** + * + */ public class HttpCachingHeadersTest { @@ -26,7 +29,9 @@ void shouldStoreEtagValue() { } - + /** + * Verifies that Cache-Control header is correctly set + */ @Test void CacheControlReturnsCorrectValue() { HttpCachingHeaders cachingHeaders = new HttpCachingHeaders(); @@ -62,8 +67,11 @@ void applyToResponse(){ assertThat(target).containsEntry("ETag", "123456789"); } - // Verifies that getHeaders() returns a defensive copy - // so external modifications do not affect internal state. +/** + Verifies that getHeaders() returns a defensive copy + so external modifications do not affect internal state. + */ + @Test void getHeaders_shouldReturnDefensiveCopy(){ @@ -73,11 +81,11 @@ void getHeaders_shouldReturnDefensiveCopy(){ Map returnedMap = cachingHeaders.getHeaders(); - returnedMap.put("Etag", "hacked"); + returnedMap.put("ETag", "hacked"); Map returnedMap2 = cachingHeaders.getHeaders(); - assertThat(returnedMap.get("Etag")).isEqualTo("hacked"); + assertThat(returnedMap.get("ETag")).isEqualTo("hacked"); assertThat(cachingHeaders.getHeaders().get("ETag")).isEqualTo("123"); From e422463ce5a6c37fbb4348bfac2a2f5d9afec194 Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Wed, 18 Feb 2026 17:14:41 +0100 Subject: [PATCH 04/21] Fix `addETagHeader` with better formatting after coderabbits suggestion made test fail. to handle various ETag formats; improve Javadocs and correct typos in `HttpCachingHeaders` --- .../org/example/http/HttpCachingHeaders.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/example/http/HttpCachingHeaders.java b/src/main/java/org/example/http/HttpCachingHeaders.java index cfd8e0f2..ca5d178b 100644 --- a/src/main/java/org/example/http/HttpCachingHeaders.java +++ b/src/main/java/org/example/http/HttpCachingHeaders.java @@ -48,7 +48,17 @@ public void setHeader(String name, String value) { * */ public void addETagHeader(String etag) { - setHeader(ETAG, "\"" + etag + "\""); + if (etag == null) return; + + String formattedEtag; + if (etag.startsWith("\"") && etag.endsWith("W/\"") ) { + formattedEtag = etag; + } else { + formattedEtag = "\"" + etag + "\""; + } + setHeader(ETAG, etag); + + } /** @@ -61,8 +71,8 @@ public void setCacheControl(String cacheControl) { /** * Helper method for setting Last-Modified header value - * Formates and sets Last modified based on instant - * @param instant Timestamp of last modification + * Formates and sets Last modified based on an instant + * @param instant Timestamp of the last modification */ public void setLastModified(Instant instant){ setHeader(LAST_MODIFIED, HTTP_DATE_FORMATTER.format(instant)); @@ -70,7 +80,7 @@ public void setLastModified(Instant instant){ /** - * Incase of errors or unexpected behaviour, cache should be disabled and no data should be saved + * In case of errors or unexpected behaviour, cache should be disabled and no data should be saved */ public void setNoCache() { setCacheControl("no-store, no-cache, must-revalidate"); From 4b3267dd5885aab30fc0a6e74fd65df753f5aeef Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Wed, 18 Feb 2026 19:03:04 +0100 Subject: [PATCH 05/21] Refactor `addETagHeader` for RFC compliance; update Javadocs and simplify `setNoCache` behavior. --- src/main/java/org/example/http/HttpCachingHeaders.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/example/http/HttpCachingHeaders.java b/src/main/java/org/example/http/HttpCachingHeaders.java index ca5d178b..1e8de96d 100644 --- a/src/main/java/org/example/http/HttpCachingHeaders.java +++ b/src/main/java/org/example/http/HttpCachingHeaders.java @@ -44,8 +44,8 @@ public void setHeader(String name, String value) { /** * Helper method for setting ETag header value * ETag values must be enclosed in double quotes "123" not 123 - * @param etag ETag value as quouted per RFC 7232 - * + * @param etag Raw, unquoted ETag token (e.g. {`@code` abc123}); double quotes + * are added automatically to comply with RFC 7232. */ public void addETagHeader(String etag) { if (etag == null) return; @@ -80,10 +80,10 @@ public void setLastModified(Instant instant){ /** - * In case of errors or unexpected behaviour, cache should be disabled and no data should be saved + * In case of errors or unexpected behaviour, the cache should be disabled and no data should be saved */ public void setNoCache() { - setCacheControl("no-store, no-cache, must-revalidate"); + setCacheControl("no-store, no-cache"); } From 907e58800a7410e74de9252ee6bafcd2ae84eadc Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Sat, 21 Feb 2026 20:18:44 +0100 Subject: [PATCH 06/21] Add `CachingFilter` to enable HTTP caching with ETag and Last-Modified handling --- .../java/org/example/http/CachingFilter.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/main/java/org/example/http/CachingFilter.java diff --git a/src/main/java/org/example/http/CachingFilter.java b/src/main/java/org/example/http/CachingFilter.java new file mode 100644 index 00000000..24acba94 --- /dev/null +++ b/src/main/java/org/example/http/CachingFilter.java @@ -0,0 +1,75 @@ +package org.example.http; + +import org.example.filter.Filter; +import org.example.filter.FilterChain; +import org.example.httpparser.HttpRequest; + +import java.io.File; +import java.net.URI; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Map; + + +public class CachingFilter implements Filter { + + + @Override + public void init() { + + } + + @Override + public void destroy() { + + } + + @Override + public void doFilter(HttpRequest request, HttpResponseBuilder response, FilterChain chain) { + + String path = request.getPath(); + + File file = new File("www", path); + + if(!file.exists()){ + response.setStatusCode(404); + return; + } + + Map headers = request.getHeaders(); + + String modifiedSince = headers.get("If-Modified-Since"); + String ETag = generateEtag(file); + Instant lastModified = + Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(modifiedSince)); + + String ifNoneMatch = headers.get("If-None-Match"); + + + if (ifNoneMatch != null && ifNoneMatch.equals(ETag)) { + response.setStatusCode(304); + return; + } + if (!lastModified.isAfter(Instant.parse(modifiedSince))) { + response.setStatusCode(304); + return; + } + + chain.doFilter(request, response); + + HttpCachingHeaders cachingHeaders = new HttpCachingHeaders(); + cachingHeaders.addETagHeader(ETag); + cachingHeaders.setLastModified(Instant.ofEpochMilli(file.lastModified())); + cachingHeaders.setDefaultCacheControlStatic(); + + response.setHeaders(cachingHeaders.getHeaders()); + + } + + private String generateEtag(File file) { + return String.valueOf(file.lastModified()); + + } +} + + From ec9b987a4059f86bf2e7702969208881b7e37efd Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Sun, 22 Feb 2026 22:24:17 +0100 Subject: [PATCH 07/21] Handle invalid `If-Modified-Since` parsing in `CachingFilter`; add ETag generation logic; refactor `HttpCachingHeaders` and `HttpResponseBuilder` for improved compliance and maintainability. # Conflicts: # src/main/java/org/example/http/CachingFilter.java # src/main/java/org/example/http/HttpResponseBuilder.java --- .../java/org/example/http/HttpCachingHeaders.java | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/example/http/HttpCachingHeaders.java b/src/main/java/org/example/http/HttpCachingHeaders.java index 1e8de96d..96f0fb1c 100644 --- a/src/main/java/org/example/http/HttpCachingHeaders.java +++ b/src/main/java/org/example/http/HttpCachingHeaders.java @@ -21,6 +21,7 @@ public class HttpCachingHeaders { * ETag helps cache be more efficient and not needing to send a full resend assuming the content has not changed * Last-Modified */ + private static final String CACHE_CONTROL = "Cache-Control"; private static final String LAST_MODIFIED = "Last-Modified"; private static final String ETAG = "ETag"; @@ -48,17 +49,7 @@ public void setHeader(String name, String value) { * are added automatically to comply with RFC 7232. */ public void addETagHeader(String etag) { - if (etag == null) return; - - String formattedEtag; - if (etag.startsWith("\"") && etag.endsWith("W/\"") ) { - formattedEtag = etag; - } else { - formattedEtag = "\"" + etag + "\""; - } - setHeader(ETAG, etag); - - + setHeader(ETAG, etag); } /** @@ -103,6 +94,8 @@ public Map getHeaders() { return new LinkedHashMap<>(headers); } + + /** * Standard settings for caching, 1 hour */ From b2ad6ee6e67b0bddce5287c4c008ab55d708804e Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Mon, 23 Feb 2026 14:03:32 +0100 Subject: [PATCH 08/21] Fixing --- src/main/java/org/example/http/CachingFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/example/http/CachingFilter.java b/src/main/java/org/example/http/CachingFilter.java index 24acba94..4a0fa839 100644 --- a/src/main/java/org/example/http/CachingFilter.java +++ b/src/main/java/org/example/http/CachingFilter.java @@ -9,7 +9,7 @@ import java.time.Instant; import java.time.format.DateTimeFormatter; import java.util.Map; - +// public class CachingFilter implements Filter { From 5ea159ece2e51f9c4681a9eb0ece4962c620c975 Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Mon, 23 Feb 2026 14:04:37 +0100 Subject: [PATCH 09/21] Fixing --- src/main/java/org/example/http/CachingFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/example/http/CachingFilter.java b/src/main/java/org/example/http/CachingFilter.java index 4a0fa839..6f0b4110 100644 --- a/src/main/java/org/example/http/CachingFilter.java +++ b/src/main/java/org/example/http/CachingFilter.java @@ -9,7 +9,7 @@ import java.time.Instant; import java.time.format.DateTimeFormatter; import java.util.Map; -// +/// / public class CachingFilter implements Filter { From af7fcdb3bbc4fba356d8074adeec49429dbe794d Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Mon, 23 Feb 2026 14:07:03 +0100 Subject: [PATCH 10/21] Fix --- src/main/java/org/example/http/CachingFilter.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/example/http/CachingFilter.java b/src/main/java/org/example/http/CachingFilter.java index 6f0b4110..a1b22b68 100644 --- a/src/main/java/org/example/http/CachingFilter.java +++ b/src/main/java/org/example/http/CachingFilter.java @@ -2,10 +2,8 @@ import org.example.filter.Filter; import org.example.filter.FilterChain; -import org.example.httpparser.HttpRequest; import java.io.File; -import java.net.URI; import java.time.Instant; import java.time.format.DateTimeFormatter; import java.util.Map; From c27a17d4bb93adf5806be39a55345c1b859bbd00 Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Mon, 23 Feb 2026 14:17:40 +0100 Subject: [PATCH 11/21] restore deleted files --- src/main/java/org/example/filter/Filter.java | 14 +++++++ .../java/org/example/filter/FilterChain.java | 10 +++++ .../org/example/filter/FilterChainImpl.java | 33 +++++++++++++++ .../java/org/example/http/CachingFilter.java | 1 + .../org/example/httpparser/HttpRequest.java | 41 +++++++++++++++++++ 5 files changed, 99 insertions(+) create mode 100644 src/main/java/org/example/filter/Filter.java create mode 100644 src/main/java/org/example/filter/FilterChain.java create mode 100644 src/main/java/org/example/filter/FilterChainImpl.java create mode 100644 src/main/java/org/example/httpparser/HttpRequest.java diff --git a/src/main/java/org/example/filter/Filter.java b/src/main/java/org/example/filter/Filter.java new file mode 100644 index 00000000..5bd4eb1c --- /dev/null +++ b/src/main/java/org/example/filter/Filter.java @@ -0,0 +1,14 @@ +package org.example.filter; + +import org.example.http.HttpResponseBuilder; + +import org.example.httpparser.HttpRequest; + +public interface Filter { + void init(); + + void doFilter(HttpRequest request, HttpResponseBuilder response, FilterChain chain); + + void destroy(); + +} diff --git a/src/main/java/org/example/filter/FilterChain.java b/src/main/java/org/example/filter/FilterChain.java new file mode 100644 index 00000000..942da453 --- /dev/null +++ b/src/main/java/org/example/filter/FilterChain.java @@ -0,0 +1,10 @@ +package org.example.filter; + +import org.example.http.HttpResponseBuilder; +import org.example.httpparser.HttpRequest; + + +public interface FilterChain { + + void doFilter(HttpRequest request, HttpResponseBuilder response); +} diff --git a/src/main/java/org/example/filter/FilterChainImpl.java b/src/main/java/org/example/filter/FilterChainImpl.java new file mode 100644 index 00000000..b6c4509f --- /dev/null +++ b/src/main/java/org/example/filter/FilterChainImpl.java @@ -0,0 +1,33 @@ +package org.example.filter; + +import org.example.http.HttpResponseBuilder; +import org.example.httpparser.HttpRequest; + + +import java.util.List; + +/* +* The default class of FilterChain, +* Contains a list of filters. For each of the filter, will execute the doFilter method. +* + */ + +public class FilterChainImpl implements FilterChain { + + private final List filters; + private int index = 0; + + public FilterChainImpl(List filters) { + this.filters = filters; + } + + @Override + public void doFilter(HttpRequest request, HttpResponseBuilder response) { + if (index < filters.size()) { + Filter next = filters.get(index++); + next.doFilter(request, response, this); + } else { + // TODO: when no more filters, should execute the request + } + } +} diff --git a/src/main/java/org/example/http/CachingFilter.java b/src/main/java/org/example/http/CachingFilter.java index a1b22b68..bbc97805 100644 --- a/src/main/java/org/example/http/CachingFilter.java +++ b/src/main/java/org/example/http/CachingFilter.java @@ -2,6 +2,7 @@ import org.example.filter.Filter; import org.example.filter.FilterChain; +import org.example.httpparser.HttpRequest; import java.io.File; import java.time.Instant; diff --git a/src/main/java/org/example/httpparser/HttpRequest.java b/src/main/java/org/example/httpparser/HttpRequest.java new file mode 100644 index 00000000..ad65d496 --- /dev/null +++ b/src/main/java/org/example/httpparser/HttpRequest.java @@ -0,0 +1,41 @@ +package org.example.httpparser; + +import java.util.Collections; +import java.util.Map; + +/* +* +*This class groups together all information about a request that the server needs + */ + +public class HttpRequest { + + private final String method; + private final String path; + private final String version; + private final Map headers; + private final String body; + + public HttpRequest(String method, + String path, + String version, + Map headers, + String body) { + this.method = method; + this.path = path; + this.version = version; + this.headers = headers != null ? Map.copyOf(headers) : Collections.emptyMap(); + this.body = body; + } + + public String getMethod() { + return method; } + public String getPath() { + return path; } + public String getVersion() { + return version; } + public Map getHeaders() { + return headers; } + public String getBody() { + return body; } + } From 7bbcdd4bb0e9419166d086cdb64f9e495b760155 Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Mon, 23 Feb 2026 14:32:31 +0100 Subject: [PATCH 12/21] Coderabbit review --- src/main/java/org/example/http/HttpCachingHeaders.java | 2 +- src/main/java/org/example/http/HttpResponseBuilder.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/http/HttpCachingHeaders.java b/src/main/java/org/example/http/HttpCachingHeaders.java index 96f0fb1c..518ed880 100644 --- a/src/main/java/org/example/http/HttpCachingHeaders.java +++ b/src/main/java/org/example/http/HttpCachingHeaders.java @@ -5,7 +5,7 @@ import java.time.format.DateTimeFormatter; import java.util.LinkedHashMap; import java.util.Map; - +// /** * Helper class for building HTTP response headers * Lets the client reuse cached responses diff --git a/src/main/java/org/example/http/HttpResponseBuilder.java b/src/main/java/org/example/http/HttpResponseBuilder.java index eb1f77ea..d3ab505a 100644 --- a/src/main/java/org/example/http/HttpResponseBuilder.java +++ b/src/main/java/org/example/http/HttpResponseBuilder.java @@ -1,5 +1,5 @@ package org.example.http; -// +//// import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; import java.util.Map; @@ -47,4 +47,6 @@ public String build(){ } + + } From 5bd97d0eab5f9a825338a1dad2bf69fb36289a34 Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Mon, 23 Feb 2026 14:48:48 +0100 Subject: [PATCH 13/21] Update `CachingFilter` to include ETag in response headers; add `addHeader` method to `HttpResponseBuilder`. --- src/main/java/org/example/http/CachingFilter.java | 3 ++- src/main/java/org/example/http/HttpResponseBuilder.java | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/example/http/CachingFilter.java b/src/main/java/org/example/http/CachingFilter.java index bbc97805..6360c26b 100644 --- a/src/main/java/org/example/http/CachingFilter.java +++ b/src/main/java/org/example/http/CachingFilter.java @@ -61,7 +61,8 @@ public void doFilter(HttpRequest request, HttpResponseBuilder response, FilterCh cachingHeaders.setLastModified(Instant.ofEpochMilli(file.lastModified())); cachingHeaders.setDefaultCacheControlStatic(); - response.setHeaders(cachingHeaders.getHeaders()); + //response.setHeaders(cachingHeaders.getHeaders()); + response.addHeader("ETag", ETag); } diff --git a/src/main/java/org/example/http/HttpResponseBuilder.java b/src/main/java/org/example/http/HttpResponseBuilder.java index d3ab505a..b2239215 100644 --- a/src/main/java/org/example/http/HttpResponseBuilder.java +++ b/src/main/java/org/example/http/HttpResponseBuilder.java @@ -47,6 +47,10 @@ public String build(){ } + public void addHeader(String key, String value){ + this.headers.put(key, value); + } + } From 90b6fc46e775be7d07134c58de7f014689b92253 Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Tue, 24 Feb 2026 21:48:08 +0100 Subject: [PATCH 14/21] Add unit tests for `CachingFilter` functionality; improve path normalization and handle invalid `If-Modified-Since` parsing gracefully. --- .../java/org/example/http/CachingFilter.java | 37 ++++++++--- .../org/example/http/HttpResponseBuilder.java | 6 +- .../org/example/http/CachingFilterTest.java | 64 +++++++++++++++++++ .../org/example/http/TestFilterChain.java | 13 ++++ 4 files changed, 108 insertions(+), 12 deletions(-) create mode 100644 src/test/java/org/example/http/CachingFilterTest.java create mode 100644 src/test/java/org/example/http/TestFilterChain.java diff --git a/src/main/java/org/example/http/CachingFilter.java b/src/main/java/org/example/http/CachingFilter.java index 6360c26b..87b26761 100644 --- a/src/main/java/org/example/http/CachingFilter.java +++ b/src/main/java/org/example/http/CachingFilter.java @@ -28,6 +28,12 @@ public void doFilter(HttpRequest request, HttpResponseBuilder response, FilterCh String path = request.getPath(); + if (path.equals("/")) { + path = "index.html"; + } else { + path = path.substring(1); + } + File file = new File("www", path); if(!file.exists()){ @@ -38,31 +44,42 @@ public void doFilter(HttpRequest request, HttpResponseBuilder response, FilterCh Map headers = request.getHeaders(); String modifiedSince = headers.get("If-Modified-Since"); - String ETag = generateEtag(file); - Instant lastModified = - Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(modifiedSince)); + String eTag = generateEtag(file); + Instant lastModified = Instant.ofEpochMilli(file.lastModified()); String ifNoneMatch = headers.get("If-None-Match"); - if (ifNoneMatch != null && ifNoneMatch.equals(ETag)) { + if (ifNoneMatch != null && ifNoneMatch.equals(eTag)) { response.setStatusCode(304); return; } - if (!lastModified.isAfter(Instant.parse(modifiedSince))) { - response.setStatusCode(304); - return; + + + if (modifiedSince != null) { + try { + Instant ifModifiedSinceInstant = + Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(modifiedSince)); + + if (!lastModified.isAfter(ifModifiedSinceInstant)) { + response.setStatusCode(304); + return; + } + + } catch (Exception e) { + + } } chain.doFilter(request, response); HttpCachingHeaders cachingHeaders = new HttpCachingHeaders(); - cachingHeaders.addETagHeader(ETag); + cachingHeaders.addETagHeader(eTag); cachingHeaders.setLastModified(Instant.ofEpochMilli(file.lastModified())); cachingHeaders.setDefaultCacheControlStatic(); - //response.setHeaders(cachingHeaders.getHeaders()); - response.addHeader("ETag", ETag); + + response.addHeader("ETag", eTag); } diff --git a/src/main/java/org/example/http/HttpResponseBuilder.java b/src/main/java/org/example/http/HttpResponseBuilder.java index b2239215..61aa0771 100644 --- a/src/main/java/org/example/http/HttpResponseBuilder.java +++ b/src/main/java/org/example/http/HttpResponseBuilder.java @@ -1,5 +1,5 @@ package org.example.http; -//// + import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; import java.util.Map; @@ -52,5 +52,7 @@ public void addHeader(String key, String value){ } - + public int getStatusCode() { + return statusCode; + } } diff --git a/src/test/java/org/example/http/CachingFilterTest.java b/src/test/java/org/example/http/CachingFilterTest.java new file mode 100644 index 00000000..d4ad6cb2 --- /dev/null +++ b/src/test/java/org/example/http/CachingFilterTest.java @@ -0,0 +1,64 @@ +package org.example.http; + +import org.example.httpparser.HttpRequest; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.nio.file.Files; +import java.util.Map; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class CachingFilterTest { + + + + @Test + void shouldReturn404WhenFileDoesNotExist() { + + CachingFilter cachingFilter = new CachingFilter(); + + HttpRequest request = new HttpRequest( + "GET", + "/does-not-exist", + "HTTP/1.1", + Map.of(), + null + ); + + HttpResponseBuilder response = new HttpResponseBuilder(); + + TestFilterChain chain = new TestFilterChain(); + + cachingFilter.doFilter(request, response, chain); + + assertThat(response.getStatusCode()).isEqualTo(404); + assertThat(chain.called).isFalse(); + } + + + @Test + void shouldContinueChainWhenNoCachingHeaders() throws Exception { + + CachingFilter cachingFilter = new CachingFilter(); + + File file = new File("www/ok.txt"); + file.getParentFile().mkdirs(); + Files.writeString(file.toPath(), "hello"); + + HttpRequest request = new HttpRequest( + "GET", + "/ok.txt", + "HTTP/1.1", + Map.of(), + null + ); + + HttpResponseBuilder response = new HttpResponseBuilder(); + TestFilterChain chain = new TestFilterChain(); + + cachingFilter.doFilter(request, response, chain); + + assertThat(chain.called).isTrue(); + } +} diff --git a/src/test/java/org/example/http/TestFilterChain.java b/src/test/java/org/example/http/TestFilterChain.java new file mode 100644 index 00000000..a1e3a8f4 --- /dev/null +++ b/src/test/java/org/example/http/TestFilterChain.java @@ -0,0 +1,13 @@ +package org.example.http; + +import org.example.filter.FilterChain; +import org.example.httpparser.HttpRequest; + +class TestFilterChain implements FilterChain { + boolean called = false; + + @Override + public void doFilter(HttpRequest request, HttpResponseBuilder response) { + called = true; + } +} \ No newline at end of file From 5962ecb16cf6e855989d5d60999bd28650504613 Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Tue, 24 Feb 2026 23:16:17 +0100 Subject: [PATCH 15/21] Add unit tests for `CachingFilter` functionality; improve path normalization and handle invalid `If-Modified-Since` parsing gracefully. --- src/main/java/org/example/http/CachingFilter.java | 2 +- src/test/java/org/example/http/CachingFilterTest.java | 2 +- src/test/java/org/example/http/TestFilterChain.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/example/http/CachingFilter.java b/src/main/java/org/example/http/CachingFilter.java index 87b26761..d0e396f8 100644 --- a/src/main/java/org/example/http/CachingFilter.java +++ b/src/main/java/org/example/http/CachingFilter.java @@ -8,7 +8,7 @@ import java.time.Instant; import java.time.format.DateTimeFormatter; import java.util.Map; -/// / +/// /// public class CachingFilter implements Filter { diff --git a/src/test/java/org/example/http/CachingFilterTest.java b/src/test/java/org/example/http/CachingFilterTest.java index d4ad6cb2..db0166ac 100644 --- a/src/test/java/org/example/http/CachingFilterTest.java +++ b/src/test/java/org/example/http/CachingFilterTest.java @@ -6,7 +6,7 @@ import java.io.File; import java.nio.file.Files; import java.util.Map; - +// import static org.assertj.core.api.AssertionsForClassTypes.assertThat; public class CachingFilterTest { diff --git a/src/test/java/org/example/http/TestFilterChain.java b/src/test/java/org/example/http/TestFilterChain.java index a1e3a8f4..7b8e1d5c 100644 --- a/src/test/java/org/example/http/TestFilterChain.java +++ b/src/test/java/org/example/http/TestFilterChain.java @@ -2,7 +2,7 @@ import org.example.filter.FilterChain; import org.example.httpparser.HttpRequest; - +// class TestFilterChain implements FilterChain { boolean called = false; From 456cda3a25bb463ab69101ecfdeff0b7eb0eafb4 Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Tue, 24 Feb 2026 23:17:35 +0100 Subject: [PATCH 16/21] Add unit tests for CachingFilter functionality; improve path normalization and handle invalid If-Modified-Since parsing gracefully. --- www/ok.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 www/ok.txt diff --git a/www/ok.txt b/www/ok.txt new file mode 100644 index 00000000..b6fc4c62 --- /dev/null +++ b/www/ok.txt @@ -0,0 +1 @@ +hello \ No newline at end of file From c1321dd5b1f385c7fd1b9b292277699efa886182 Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Tue, 24 Feb 2026 23:19:07 +0100 Subject: [PATCH 17/21] Add unit tests for `CachingFilter` functionality; improve path normalization and handle invalid `If-Modified-Since` parsing gracefully. --- src/main/java/org/example/http/CachingFilter.java | 2 +- src/test/java/org/example/http/CachingFilterTest.java | 2 +- src/test/java/org/example/http/TestFilterChain.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/example/http/CachingFilter.java b/src/main/java/org/example/http/CachingFilter.java index d0e396f8..c29c5f08 100644 --- a/src/main/java/org/example/http/CachingFilter.java +++ b/src/main/java/org/example/http/CachingFilter.java @@ -8,7 +8,7 @@ import java.time.Instant; import java.time.format.DateTimeFormatter; import java.util.Map; -/// /// +/// ///// public class CachingFilter implements Filter { diff --git a/src/test/java/org/example/http/CachingFilterTest.java b/src/test/java/org/example/http/CachingFilterTest.java index db0166ac..a1ad2871 100644 --- a/src/test/java/org/example/http/CachingFilterTest.java +++ b/src/test/java/org/example/http/CachingFilterTest.java @@ -6,7 +6,7 @@ import java.io.File; import java.nio.file.Files; import java.util.Map; -// +/// // import static org.assertj.core.api.AssertionsForClassTypes.assertThat; public class CachingFilterTest { diff --git a/src/test/java/org/example/http/TestFilterChain.java b/src/test/java/org/example/http/TestFilterChain.java index 7b8e1d5c..5eb7a254 100644 --- a/src/test/java/org/example/http/TestFilterChain.java +++ b/src/test/java/org/example/http/TestFilterChain.java @@ -2,7 +2,7 @@ import org.example.filter.FilterChain; import org.example.httpparser.HttpRequest; -// +/// / class TestFilterChain implements FilterChain { boolean called = false; From 5552e856f296bca39a2dee402bb988db8ce8a936 Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Tue, 24 Feb 2026 23:22:58 +0100 Subject: [PATCH 18/21] Add unit tests for CachingFilter functionality; improve path normalization and handle invalid If-Modified-Since parsing gracefully. --- www/ok.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 www/ok.txt diff --git a/www/ok.txt b/www/ok.txt deleted file mode 100644 index b6fc4c62..00000000 --- a/www/ok.txt +++ /dev/null @@ -1 +0,0 @@ -hello \ No newline at end of file From cbb0743f94b99c27dae536545f63fe846b703dd9 Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Tue, 24 Feb 2026 23:52:47 +0100 Subject: [PATCH 19/21] Remove getStatusCode method from HttpResponseBuilder Removed the getStatusCode method from HttpResponseBuilder. --- src/main/java/org/example/http/HttpResponseBuilder.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/example/http/HttpResponseBuilder.java b/src/main/java/org/example/http/HttpResponseBuilder.java index 3bf343eb..faf25532 100644 --- a/src/main/java/org/example/http/HttpResponseBuilder.java +++ b/src/main/java/org/example/http/HttpResponseBuilder.java @@ -99,8 +99,7 @@ public void addHeader(String key, String value){ } - public int getStatusCode() { - return statusCode; + /* * Builds the complete HTTP response as a byte array and preserves binary content without corruption. * @return Complete HTTP response (headers + body) as byte[] From d121ec8b89d453d033446a4cffccceb858d8dd4e Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Thu, 26 Feb 2026 21:22:45 +0100 Subject: [PATCH 20/21] Update `CachingFilter` to enhance caching logic, centralize header handling, and ensure consistent ETag and caching headers in responses. At the request of Martin Stenhagen and gvaguirres # Conflicts: # src/main/java/org/example/http/CachingFilter.java --- .../java/org/example/http/CachingFilter.java | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/example/http/CachingFilter.java b/src/main/java/org/example/http/CachingFilter.java index c29c5f08..43292435 100644 --- a/src/main/java/org/example/http/CachingFilter.java +++ b/src/main/java/org/example/http/CachingFilter.java @@ -1,14 +1,17 @@ package org.example.http; +import org.example.config.AppConfig; +import org.example.config.ConfigLoader; import org.example.filter.Filter; import org.example.filter.FilterChain; import org.example.httpparser.HttpRequest; import java.io.File; +import java.io.ObjectInputFilter; import java.time.Instant; import java.time.format.DateTimeFormatter; import java.util.Map; -/// ///// + public class CachingFilter implements Filter { @@ -27,6 +30,7 @@ public void destroy() { public void doFilter(HttpRequest request, HttpResponseBuilder response, FilterChain chain) { String path = request.getPath(); + HttpCachingHeaders cachingHeaders = new HttpCachingHeaders(); if (path.equals("/")) { path = "index.html"; @@ -34,10 +38,14 @@ public void doFilter(HttpRequest request, HttpResponseBuilder response, FilterCh path = path.substring(1); } - File file = new File("www", path); + // Ingen mer hårtkodat utan sökväg från ConfigLoader + String rootDir = ConfigLoader.get().server().rootDir(); + File file = new File(rootDir, path); + if(!file.exists()){ - response.setStatusCode(404); + response.setStatusCode(HttpResponseBuilder.SC_NOT_FOUND); + return; } @@ -51,18 +59,19 @@ public void doFilter(HttpRequest request, HttpResponseBuilder response, FilterCh if (ifNoneMatch != null && ifNoneMatch.equals(eTag)) { - response.setStatusCode(304); + response.setStatusCode(HttpResponseBuilder.SC_NOT_MODIFIED); + cachingHeaders.getHeaders().forEach(response::addHeader); return; } - if (modifiedSince != null) { try { Instant ifModifiedSinceInstant = Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(modifiedSince)); if (!lastModified.isAfter(ifModifiedSinceInstant)) { - response.setStatusCode(304); + response.setStatusCode(HttpResponseBuilder.SC_NOT_MODIFIED); + cachingHeaders.getHeaders().forEach(response::addHeader); return; } @@ -71,20 +80,21 @@ public void doFilter(HttpRequest request, HttpResponseBuilder response, FilterCh } } - chain.doFilter(request, response); - HttpCachingHeaders cachingHeaders = new HttpCachingHeaders(); + + cachingHeaders.addETagHeader(eTag); cachingHeaders.setLastModified(Instant.ofEpochMilli(file.lastModified())); cachingHeaders.setDefaultCacheControlStatic(); + chain.doFilter(request, response); + cachingHeaders.getHeaders().forEach(response::addHeader); - response.addHeader("ETag", eTag); } private String generateEtag(File file) { - return String.valueOf(file.lastModified()); + return "\"" + file.lastModified() + "-" + file.length() + "\""; } } From e39dad88d152626846a27104f93a17d6fd675fba Mon Sep 17 00:00:00 2001 From: JohanHiths Date: Fri, 27 Feb 2026 17:21:28 +0100 Subject: [PATCH 21/21] Refactor to improve module structure: - Renamed packages for clarity (`http` to `filter`). - Added `ResolveFileHandler` class placeholder. - Updated `CachingFilter` for better caching header handling (moved and optimized logic). - Added `ConfigLoader` setup in `CachingFilterTest`. Requested by Martin Stenhagen --- .../java/org/example/ResolveFileHandler.java | 9 +++++++++ .../{http => filter}/CachingFilter.java | 20 +++++++------------ .../{http => filter}/CachingFilterTest.java | 14 ++++++++++++- .../{http => filter}/TestFilterChain.java | 4 ++-- 4 files changed, 31 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/example/ResolveFileHandler.java rename src/main/java/org/example/{http => filter}/CachingFilter.java (93%) rename src/test/java/org/example/{http => filter}/CachingFilterTest.java (80%) rename src/test/java/org/example/{http => filter}/TestFilterChain.java (77%) diff --git a/src/main/java/org/example/ResolveFileHandler.java b/src/main/java/org/example/ResolveFileHandler.java new file mode 100644 index 00000000..633cd7f9 --- /dev/null +++ b/src/main/java/org/example/ResolveFileHandler.java @@ -0,0 +1,9 @@ +package org.example; + +public class ResolveFileHandler { + + + public void handleCaching(){ + + } +} diff --git a/src/main/java/org/example/http/CachingFilter.java b/src/main/java/org/example/filter/CachingFilter.java similarity index 93% rename from src/main/java/org/example/http/CachingFilter.java rename to src/main/java/org/example/filter/CachingFilter.java index 43292435..1afb54f9 100644 --- a/src/main/java/org/example/http/CachingFilter.java +++ b/src/main/java/org/example/filter/CachingFilter.java @@ -1,13 +1,11 @@ -package org.example.http; +package org.example.filter; -import org.example.config.AppConfig; import org.example.config.ConfigLoader; -import org.example.filter.Filter; -import org.example.filter.FilterChain; +import org.example.http.HttpCachingHeaders; +import org.example.http.HttpResponseBuilder; import org.example.httpparser.HttpRequest; import java.io.File; -import java.io.ObjectInputFilter; import java.time.Instant; import java.time.format.DateTimeFormatter; import java.util.Map; @@ -57,6 +55,10 @@ public void doFilter(HttpRequest request, HttpResponseBuilder response, FilterCh String ifNoneMatch = headers.get("If-None-Match"); + cachingHeaders.addETagHeader(eTag); + cachingHeaders.setLastModified(Instant.ofEpochMilli(file.lastModified())); + cachingHeaders.setDefaultCacheControlStatic(); + if (ifNoneMatch != null && ifNoneMatch.equals(eTag)) { response.setStatusCode(HttpResponseBuilder.SC_NOT_MODIFIED); @@ -80,13 +82,6 @@ public void doFilter(HttpRequest request, HttpResponseBuilder response, FilterCh } } - - - - cachingHeaders.addETagHeader(eTag); - cachingHeaders.setLastModified(Instant.ofEpochMilli(file.lastModified())); - cachingHeaders.setDefaultCacheControlStatic(); - chain.doFilter(request, response); cachingHeaders.getHeaders().forEach(response::addHeader); @@ -99,4 +94,3 @@ private String generateEtag(File file) { } } - diff --git a/src/test/java/org/example/http/CachingFilterTest.java b/src/test/java/org/example/filter/CachingFilterTest.java similarity index 80% rename from src/test/java/org/example/http/CachingFilterTest.java rename to src/test/java/org/example/filter/CachingFilterTest.java index a1ad2871..abf1c2ed 100644 --- a/src/test/java/org/example/http/CachingFilterTest.java +++ b/src/test/java/org/example/filter/CachingFilterTest.java @@ -1,10 +1,15 @@ -package org.example.http; +package org.example.filter; +import org.example.config.ConfigLoader; +import org.example.http.HttpResponseBuilder; import org.example.httpparser.HttpRequest; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.File; import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Map; /// // import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -12,12 +17,19 @@ public class CachingFilterTest { + @BeforeEach + void setup() { + Path configPath = Paths.get("src/test/resources/test-config.yml"); + ConfigLoader.loadOnce(configPath); + + } @Test void shouldReturn404WhenFileDoesNotExist() { CachingFilter cachingFilter = new CachingFilter(); + HttpRequest request = new HttpRequest( "GET", "/does-not-exist", diff --git a/src/test/java/org/example/http/TestFilterChain.java b/src/test/java/org/example/filter/TestFilterChain.java similarity index 77% rename from src/test/java/org/example/http/TestFilterChain.java rename to src/test/java/org/example/filter/TestFilterChain.java index 5eb7a254..ca1f5896 100644 --- a/src/test/java/org/example/http/TestFilterChain.java +++ b/src/test/java/org/example/filter/TestFilterChain.java @@ -1,6 +1,6 @@ -package org.example.http; +package org.example.filter; -import org.example.filter.FilterChain; +import org.example.http.HttpResponseBuilder; import org.example.httpparser.HttpRequest; /// / class TestFilterChain implements FilterChain {