Implement configurable filter pipeline (global + per‑route filters)#11
Hidden character warning
Implement configurable filter pipeline (global + per‑route filters)#11Ericthilen wants to merge 52 commits intomainfrom
Conversation
Add ci workflow for github actions.
* build: configure pom.xml with needed plugin/tools. Setup Java 25 environment with JUnit 5, Mockito, JaCoCo, Pitest, and Spotless * fix: add missing test scope for awaitility
* Dockerfile that builds an image in a docker container then runs in another docker container Current implementation uses Temporary App.class reference before relevant file is created to start server. * Fixed unverified commit * Set up non-root user and updated Dockerfile to use user. Fixed file path to use /app/ instead of /app/org/example to prevent unnessary nesting of packages.
* added parser class for headers * refactoring * refactoring * refactoring, added getHeadersMap * refactoring * refactoring code, adding debug boolean * added unit tests for HttpParser.java class * refactoring and all green tests for class * refactoring the code * refactoring the bufferedReader * refactoring with coderabbit feedback for header put to merge * refactoring with coderabbit feedback for header put to merge, changed headersmap * refactoring from reviewer input * refactoring for code rabbit comments
* Add HttpResponseBuilder and its tests Signed-off-by: JohanHiths <johan.hietala@iths.se> * Implement minimal HTTP response builder Signed-off-by: JohanHiths <johan.hietala@iths.se> * update coderabbit * more coderabbit fixes Signed-off-by: JohanHiths <johan.hietala@iths.se> * Bytte från httptest till http efter rekommendationer från Xeutos * code rabbit fix * fixed more code rabbit problems --------- Signed-off-by: JohanHiths <johan.hietala@iths.se>
* added parser class for headers * refactoring * refactoring * refactoring, added getHeadersMap * refactoring * adding class for parsing requestLine (method, path, version). Has getters/setters for the three. * refactoring code, private setters * added tests for throwing error * refactoring the bufferedReader * refactoring, adding check for requestLineArray.length() is less or equals to two with added unit test * refactoring, adding check for requestLineArray.length() is less or equals to two with added unit test * refactoring, adding test for null value of input stream * refactoring from coderabbit input * refactoring test class. * refactoring, adding debug boolean flag for logger lines * refactoring and testing buffered reader stream * refactoring, changed class HttpParser.java to extend HttpParseRequestLine.java and making them share the same buffered reader. Added method to set the reader and then methods to call the HttpParser * refactoring, changed class HttpParser.java to extend HttpParseRequestLine.java and making them share the same buffered reader. Added method to set the reader and then methods to call the HttpParser
* Add Bucket4j dependency in pom file * Extract Bucket4j version into a property and update version in dependency for consistency
* Add support for serving static files - Introduced `StaticFileHandler` to serve static files from the `www` directory. - Enhanced `HttpResponseBuilder` to support byte array bodies. - Made `setReader` method public in `HttpParser` to improve flexibility. - Code tested via Insomnia by coding a temporary method for multithreads in TcpServer.java, this is now removed before future implementations of ConnectionHandler Co-authored-by: Daniel Fahlén xsz300@gmail.com * Fix typo in `HttpResponseBuilder` (contentLength) and refine body handling logic Co-authored-by: Daniel Fahlén xsz300@gmail.com * Add basic HTML structure to `index.html` in `www` directory * Make `handleGetRequest` method private in `StaticFileHandler` to encapsulate functionality
* Updates pom.xml, with jackson-dependencies, for config file * Updates pom.xml, removes jackson-annotations:2.20, because it was apparently unnecessary.
* * Move HTTP handling logic from TcpServer to a dedicated ConnectionTask class * Implement virtual thread execution for better scalability * Add regex-based URI routing to support clean URLs and default to index.html * Ensure sockets are properly closed Co-authored-by: Caroline Nordbrandt <caroline_nordbradt@hotmail.com> * change thrown exception to runtime instead with appropiet message in tcpServer. --------- Co-authored-by: Caroline Nordbrandt <caroline_nordbradt@hotmail.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds Java 25 tooling and CI/CD workflows; introduces an HTTP server stack (parser, response builder, connection/tcp server, static file handler), a configurable filter pipeline with routing and ordering, configuration loader/records, many unit tests, Dockerfile and static web assets; removes the previous App entrypoint and its simple tests. Changes
Sequence DiagramsequenceDiagram
participant Client as Client
participant Pipeline as ConfigurableFilterPipeline
participant Filters as HttpFilter(s)
participant Handler as TerminalHandler
Client->>Pipeline: execute(request, handler)
Pipeline->>Pipeline: partition registrations (global / route)
Pipeline->>Pipeline: sort by order & build FilterChain
Pipeline->>Filters: invoke first.filter.handle(request, chain)
Filters->>Filters: filter may call chain.next(request) or return response
Filters->>Handler: chain.next(request) when end reached
Handler->>Handler: handle(request) -> HttpResponse
Handler-->>Pipeline: HttpResponse (via chain returns)
Pipeline-->>Client: HttpResponse
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.
In @.github/workflows/ci.yml:
- Around line 28-32: CI currently only runs mvn -B compile and mvn -B test so
the spotless-maven-plugin (bound to the verify phase) isn't executed; update the
workflow to enforce formatting by either adding a dedicated step that runs mvn
spotless:check (e.g., name "Spotless check" run mvn -B spotless:check) before
tests, or change the existing test step to run mvn -B verify (replacing mvn -B
test) so the plugin runs during the build; reference the mvn commands and
spotless-maven-plugin when making the change.
- Around line 16-19: The "Get Java Version" step invokes mvn (mvn help:evaluate)
before any JDK is guaranteed to be installed; add a preceding setup-java step
(e.g., using actions/setup-java with a known LTS like 21) to install the JDK
before running the "Get Java Version" step, or alternatively hardcode the
JAVA_VERSION value instead of calling mvn; update the workflow so setup-java
runs before the step that executes mvn help:evaluate to ensure a stable JDK
environment.
In @.github/workflows/release.yml:
- Line 26: Replace the hardcoded container image value under the images key
("ghcr.io/ithsjava25/webserver") with a repository-derived expression using the
GitHub Actions context (e.g., construct the image name from github.repository)
so the workflow becomes portable when the repo is forked or renamed; update the
images entry to use the derived value (ghcr.io/${{ github.repository }} or
similar) and ensure any tag/version suffix remains appended as needed.
In `@pom.xml`:
- Around line 44-49: Extract the hardcoded Awaitility version 4.3.0 into a Maven
property (e.g., awaitility.version) and update the <version> element of the
org.awaitility:awaitility dependency to reference that property
(${awaitility.version}) so it matches how JUnit/AssertJ/Mockito versions are
managed; add the new property to the existing <properties> block to centralize
test dependency versions.
In `@src/main/java/org/example/App.java`:
- Line 5: Replace the hardcoded port 8080 passed to new TcpServer(8080).start()
in App.main with a configurable value: read the port from args (e.g., args[0])
or from an environment variable (e.g., PORT) and parse/validate it, falling back
to a sensible default (e.g., 8080) if missing or invalid; ensure you use the
TcpServer(...) constructor and call start() with that resolved integer and add
error handling for NumberFormatException or missing values.
In `@src/main/java/org/example/server/ConfigurableFilterPipeline.java`:
- Around line 90-97: The getOrder(HttpFilter) method in
ConfigurableFilterPipeline is unreliable because it uses identity comparison
(reg.filter() == filter) against FilterRegistration entries; remove this method
and update its call sites to obtain the order from the corresponding
FilterRegistration directly (e.g., use the registration.order() for the
registration you already resolved) or use equality-based matching when matching
by filter is truly required; if the upstream refactor that avoids needing this
lookup is applied, delete getOrder and any references to it and rely on the new
registration-based flow instead.
- Around line 16-50: The execute() method should sort the FilterRegistration
objects (the registrations collection) by their order and then extract
HttpFilter instances, instead of placing HttpFilter into
globalFilters/routeFilters and calling getOrder() during comparisons; change the
logic in execute() to: collect matching FilterRegistration into two lists
(globalRegs and routeRegs) based on reg.isGlobal() and
matchesAny(reg.routePatterns(), request.path()), sort those lists with
Comparator.comparingInt(FilterRegistration::order) (or equivalent), then map
them to HttpFilter via reg.filter() and combine; remove reliance on getOrder()
for sorting so duplicate filter instances with different registrations preserve
their respective orders and sort complexity improves, leaving
buildChain(allFilters, handler).next(request) untouched.
- Around line 12-13: The constructor
ConfigurableFilterPipeline(List<FilterRegistration> registrations) stores the
incoming list directly, making the internal field registrations vulnerable to
external mutation; change the constructor to defensively copy and null-check the
argument (e.g., use List.copyOf(registrations) after Objects.requireNonNull) so
the pipeline holds an immutable snapshot of registrations and cannot be affected
by caller mutations.
- Around line 100-146: The file contains multiple top-level types that should be
split into their own files; extract HttpRequest, HttpResponse, HttpFilter,
FilterChain, and TerminalHandler into separate public classes/interfaces (create
HttpRequest.java, HttpResponse.java, HttpFilter.java, FilterChain.java,
TerminalHandler.java) preserving their constructors and method signatures, keep
them in the same package, remove those definitions from
ConfigurableFilterPipeline.java, and update any imports/usages (e.g., references
to HttpRequest.path(), HttpRequest.getMethod(), HttpResponse.getStatusCode(),
HttpResponse.getBody(), and the handle/next methods) across the codebase so
compilation and behavior remain unchanged.
In `@src/main/java/org/example/server/FilterRegistration.java`:
- Around line 5-13: The record FilterRegistration accepts a mutable List<String>
routePatterns which can be modified after construction; add a compact
constructor for FilterRegistration that replaces the incoming routePatterns with
an immutable copy using List.copyOf(routePatterns) while preserving the existing
null semantic (i.e., if routePatterns is null keep it null, otherwise assign
List.copyOf(routePatterns)); this ensures immutability of the routePatterns
field and retains behavior of isGlobal().
In `@src/main/java/org/example/TcpServer.java`:
- Around line 19-23: The accept loop in TcpServer currently calls
serverSocket.accept() and then clientSocket.close() outside any
try-with-resources, risking a socket leak if handling code throws; change the
loop to acquire the accepted Socket in a try-with-resources (e.g., try (Socket
clientSocket = serverSocket.accept()) { ... }) so the socket is always closed,
move the System.out.println and any request-handling logic inside that try
block, and leave serverSocket management unchanged; update the loop in the
TcpServer class (the accept/close section) accordingly.
- Around line 15-27: The start() method currently accepts connections serially
using ServerSocket.accept() and handles them inline, which will block other
clients; modify TcpServer.start() to hand each accepted Socket (clientSocket)
off to a worker thread or an ExecutorService (e.g., create a fixed/ cached
ExecutorService field) and submit a Runnable/Callable that processes the
connection and closes clientSocket, instead of processing/closing it on the
accept thread; ensure proper shutdown of the ExecutorService and that exceptions
from the worker are logged/handled.
- Line 18: The ServerSocket instantiation in TcpServer (new ServerSocket(port))
is flagged for unencrypted cleartext; update the code to support TLS before
production by replacing or wrapping ServerSocket creation with an
SSLServerSocket from SSLServerSocketFactory (or ensure TLS is terminated
upstream via a reverse proxy) — modify the TcpServer socket-creation logic
(where ServerSocket is constructed) to accept an
SSLServerSocketFactory/SSLContext or to document/validate that only
non-production/local use is allowed so TLS termination is enforced when exposed
to real traffic.
In `@src/test/java/org/example/ConfigurableFilterPipelineTest.java`:
- Line 1: The test class ConfigurableFilterPipelineTest is declared in package
org.example.server but resides in src/test/java/org/example/..., causing a
package/path mismatch and preventing access to package-private types like
HttpFilter, FilterChain, and FilterRegistration; fix by either updating the
package declaration in ConfigurableFilterPipelineTest to match its directory
(e.g., package org.example;) or move the file into the server package directory
so the package declaration org.example.server matches the file path and the test
can access the package-private types.
- Around line 26-29: The test currently discards the return value of
pipeline.execute; update the global_filter_runs test to capture the HttpResponse
returned by pipeline.execute (call to pipeline.execute(new HttpRequest(...), new
TestHandler(events))) and add an assertion that response.getStatusCode() == 200
(or assertEquals(200, response.getStatusCode())) to verify the handler responded
successfully.
- Around line 12-58: Add unit tests to cover route-specific filter behavior for
ConfigurableFilterPipeline: create tests that register FilterRegistration
entries with a non-null route pattern and assert that HttpFilter/TestFilter runs
only when HttpRequest path matches and is skipped when it doesn't; add a test
with multiple FilterRegistration entries having different order values to verify
execution order of filters; add a test combining global (route == null) and
route-specific filters to assert globals run before matching route filters; use
pipeline.execute(HttpRequest, TestHandler) and assert events list and
HttpResponse status codes as in existing tests to validate behavior.
🧹 Nitpick comments (11)
🤖 Fix all nitpicks with AI agents
Before applying any fix, first verify the finding against the current code and decide whether a code change is actually needed. If the finding is not valid or no change is required, do not modify code for that item and briefly explain why it was skipped. In @.github/workflows/ci.yml: - Around line 16-19: The "Get Java Version" step invokes mvn (mvn help:evaluate) before any JDK is guaranteed to be installed; add a preceding setup-java step (e.g., using actions/setup-java with a known LTS like 21) to install the JDK before running the "Get Java Version" step, or alternatively hardcode the JAVA_VERSION value instead of calling mvn; update the workflow so setup-java runs before the step that executes mvn help:evaluate to ensure a stable JDK environment. In @.github/workflows/release.yml: - Line 26: Replace the hardcoded container image value under the images key ("ghcr.io/ithsjava25/webserver") with a repository-derived expression using the GitHub Actions context (e.g., construct the image name from github.repository) so the workflow becomes portable when the repo is forked or renamed; update the images entry to use the derived value (ghcr.io/${{ github.repository }} or similar) and ensure any tag/version suffix remains appended as needed. In `@pom.xml`: - Around line 44-49: Extract the hardcoded Awaitility version 4.3.0 into a Maven property (e.g., awaitility.version) and update the <version> element of the org.awaitility:awaitility dependency to reference that property (${awaitility.version}) so it matches how JUnit/AssertJ/Mockito versions are managed; add the new property to the existing <properties> block to centralize test dependency versions. In `@src/main/java/org/example/App.java`: - Line 5: Replace the hardcoded port 8080 passed to new TcpServer(8080).start() in App.main with a configurable value: read the port from args (e.g., args[0]) or from an environment variable (e.g., PORT) and parse/validate it, falling back to a sensible default (e.g., 8080) if missing or invalid; ensure you use the TcpServer(...) constructor and call start() with that resolved integer and add error handling for NumberFormatException or missing values. In `@src/main/java/org/example/server/ConfigurableFilterPipeline.java`: - Around line 12-13: The constructor ConfigurableFilterPipeline(List<FilterRegistration> registrations) stores the incoming list directly, making the internal field registrations vulnerable to external mutation; change the constructor to defensively copy and null-check the argument (e.g., use List.copyOf(registrations) after Objects.requireNonNull) so the pipeline holds an immutable snapshot of registrations and cannot be affected by caller mutations. - Around line 100-146: The file contains multiple top-level types that should be split into their own files; extract HttpRequest, HttpResponse, HttpFilter, FilterChain, and TerminalHandler into separate public classes/interfaces (create HttpRequest.java, HttpResponse.java, HttpFilter.java, FilterChain.java, TerminalHandler.java) preserving their constructors and method signatures, keep them in the same package, remove those definitions from ConfigurableFilterPipeline.java, and update any imports/usages (e.g., references to HttpRequest.path(), HttpRequest.getMethod(), HttpResponse.getStatusCode(), HttpResponse.getBody(), and the handle/next methods) across the codebase so compilation and behavior remain unchanged. In `@src/main/java/org/example/server/FilterRegistration.java`: - Around line 5-13: The record FilterRegistration accepts a mutable List<String> routePatterns which can be modified after construction; add a compact constructor for FilterRegistration that replaces the incoming routePatterns with an immutable copy using List.copyOf(routePatterns) while preserving the existing null semantic (i.e., if routePatterns is null keep it null, otherwise assign List.copyOf(routePatterns)); this ensures immutability of the routePatterns field and retains behavior of isGlobal(). In `@src/main/java/org/example/TcpServer.java`: - Around line 15-27: The start() method currently accepts connections serially using ServerSocket.accept() and handles them inline, which will block other clients; modify TcpServer.start() to hand each accepted Socket (clientSocket) off to a worker thread or an ExecutorService (e.g., create a fixed/ cached ExecutorService field) and submit a Runnable/Callable that processes the connection and closes clientSocket, instead of processing/closing it on the accept thread; ensure proper shutdown of the ExecutorService and that exceptions from the worker are logged/handled. - Line 18: The ServerSocket instantiation in TcpServer (new ServerSocket(port)) is flagged for unencrypted cleartext; update the code to support TLS before production by replacing or wrapping ServerSocket creation with an SSLServerSocket from SSLServerSocketFactory (or ensure TLS is terminated upstream via a reverse proxy) — modify the TcpServer socket-creation logic (where ServerSocket is constructed) to accept an SSLServerSocketFactory/SSLContext or to document/validate that only non-production/local use is allowed so TLS termination is enforced when exposed to real traffic. In `@src/test/java/org/example/ConfigurableFilterPipelineTest.java`: - Around line 26-29: The test currently discards the return value of pipeline.execute; update the global_filter_runs test to capture the HttpResponse returned by pipeline.execute (call to pipeline.execute(new HttpRequest(...), new TestHandler(events))) and add an assertion that response.getStatusCode() == 200 (or assertEquals(200, response.getStatusCode())) to verify the handler responded successfully. - Around line 12-58: Add unit tests to cover route-specific filter behavior for ConfigurableFilterPipeline: create tests that register FilterRegistration entries with a non-null route pattern and assert that HttpFilter/TestFilter runs only when HttpRequest path matches and is skipped when it doesn't; add a test with multiple FilterRegistration entries having different order values to verify execution order of filters; add a test combining global (route == null) and route-specific filters to assert globals run before matching route filters; use pipeline.execute(HttpRequest, TestHandler) and assert events list and HttpResponse status codes as in existing tests to validate behavior.src/main/java/org/example/TcpServer.java (2)
15-27: Single-threaded accept loop will serialize all connections.Given the PR objective is to implement a filter pipeline for an HTTP server, each accepted connection should be dispatched to a separate thread (or handed to an
ExecutorService) so that one slow client doesn't block all others.🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and decide whether a code change is actually needed. If the finding is not valid or no change is required, do not modify code for that item and briefly explain why it was skipped. In `@src/main/java/org/example/TcpServer.java` around lines 15 - 27, The start() method currently accepts connections serially using ServerSocket.accept() and handles them inline, which will block other clients; modify TcpServer.start() to hand each accepted Socket (clientSocket) off to a worker thread or an ExecutorService (e.g., create a fixed/ cached ExecutorService field) and submit a Runnable/Callable that processes the connection and closes clientSocket, instead of processing/closing it on the accept thread; ensure proper shutdown of the ExecutorService and that exceptions from the worker are logged/handled.
18-18: Note on unencrypted socket (static analysis flag).The static analysis tool flags
new ServerSocket(port)for cleartext transmission (CWE-319). This is fine for local development, but keep in mind that TLS termination (viaSSLServerSocketFactoryor a reverse proxy) will be needed before exposing this to any real traffic.🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and decide whether a code change is actually needed. If the finding is not valid or no change is required, do not modify code for that item and briefly explain why it was skipped. In `@src/main/java/org/example/TcpServer.java` at line 18, The ServerSocket instantiation in TcpServer (new ServerSocket(port)) is flagged for unencrypted cleartext; update the code to support TLS before production by replacing or wrapping ServerSocket creation with an SSLServerSocket from SSLServerSocketFactory (or ensure TLS is terminated upstream via a reverse proxy) — modify the TcpServer socket-creation logic (where ServerSocket is constructed) to accept an SSLServerSocketFactory/SSLContext or to document/validate that only non-production/local use is allowed so TLS termination is enforced when exposed to real traffic.src/main/java/org/example/App.java (1)
5-5: Consider making the port configurable.The port is hardcoded to
8080. Since the PR is about introducing configurability, consider reading it fromargs, an environment variable, or a config file to stay consistent with that theme.🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and decide whether a code change is actually needed. If the finding is not valid or no change is required, do not modify code for that item and briefly explain why it was skipped. In `@src/main/java/org/example/App.java` at line 5, Replace the hardcoded port 8080 passed to new TcpServer(8080).start() in App.main with a configurable value: read the port from args (e.g., args[0]) or from an environment variable (e.g., PORT) and parse/validate it, falling back to a sensible default (e.g., 8080) if missing or invalid; ensure you use the TcpServer(...) constructor and call start() with that resolved integer and add error handling for NumberFormatException or missing values..github/workflows/release.yml (1)
26-26: Consider deriving the image name from the repository.The image name is hardcoded. Using
ghcr.io/${{ github.repository }}makes the workflow portable if the repo is forked or renamed.Suggested change
- images: ghcr.io/ithsjava25/webserver + images: ghcr.io/${{ github.repository }}🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and decide whether a code change is actually needed. If the finding is not valid or no change is required, do not modify code for that item and briefly explain why it was skipped. In @.github/workflows/release.yml at line 26, Replace the hardcoded container image value under the images key ("ghcr.io/ithsjava25/webserver") with a repository-derived expression using the GitHub Actions context (e.g., construct the image name from github.repository) so the workflow becomes portable when the repo is forked or renamed; update the images entry to use the derived value (ghcr.io/${{ github.repository }} or similar) and ensure any tag/version suffix remains appended as needed..github/workflows/ci.yml (1)
16-19:mvnis invoked beforesetup-javainstalls the target JDK.This step relies on the JDK pre-installed on the GitHub runner image. It works today because
mvn help:evaluateonly reads the POM property without compiling, but if the runner image changes (e.g., removes Maven or the default JDK), this step will break silently. Consider adding a bootstrapsetup-javastep with a known LTS version (e.g., 21) before this step, or hardcode/duplicate the version.🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and decide whether a code change is actually needed. If the finding is not valid or no change is required, do not modify code for that item and briefly explain why it was skipped. In @.github/workflows/ci.yml around lines 16 - 19, The "Get Java Version" step invokes mvn (mvn help:evaluate) before any JDK is guaranteed to be installed; add a preceding setup-java step (e.g., using actions/setup-java with a known LTS like 21) to install the JDK before running the "Get Java Version" step, or alternatively hardcode the JAVA_VERSION value instead of calling mvn; update the workflow so setup-java runs before the step that executes mvn help:evaluate to ensure a stable JDK environment.pom.xml (1)
44-49: Minor:awaitilityversion is hardcoded unlike other test dependency versions.Other test dependencies (JUnit, AssertJ, Mockito) use
<properties>for version management. Consider extracting4.3.0into a property for consistency.🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and decide whether a code change is actually needed. If the finding is not valid or no change is required, do not modify code for that item and briefly explain why it was skipped. In `@pom.xml` around lines 44 - 49, Extract the hardcoded Awaitility version 4.3.0 into a Maven property (e.g., awaitility.version) and update the <version> element of the org.awaitility:awaitility dependency to reference that property (${awaitility.version}) so it matches how JUnit/AssertJ/Mockito versions are managed; add the new property to the existing <properties> block to centralize test dependency versions.src/test/java/org/example/ConfigurableFilterPipelineTest.java (2)
26-29: Return value ofexecuteis discarded — consider asserting the response.In
global_filter_runs, theHttpResponseis not captured. AssertingstatusCode == 200would strengthen the test.♻️ Suggested fix
- pipeline.execute( + HttpResponse response = pipeline.execute( new HttpRequest("GET", "/home"), new TestHandler(events) ); assertEquals( List.of("filter", "handler"), events ); + assertEquals(200, response.getStatusCode());🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and decide whether a code change is actually needed. If the finding is not valid or no change is required, do not modify code for that item and briefly explain why it was skipped. In `@src/test/java/org/example/ConfigurableFilterPipelineTest.java` around lines 26 - 29, The test currently discards the return value of pipeline.execute; update the global_filter_runs test to capture the HttpResponse returned by pipeline.execute (call to pipeline.execute(new HttpRequest(...), new TestHandler(events))) and add an assertion that response.getStatusCode() == 200 (or assertEquals(200, response.getStatusCode())) to verify the handler responded successfully.
12-58: Test coverage is thin — no route-specific filter tests.The PR title is "Implement configurable filter pipeline (global + per‑route filters)" but tests only exercise global filters. Consider adding tests for:
- Route-specific filters that match the request path
- Route-specific filters that do not match (should be skipped)
- Multiple filters with different
ordervalues (verify execution order)- Mixed global + route filters (verify globals run before route filters)
🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and decide whether a code change is actually needed. If the finding is not valid or no change is required, do not modify code for that item and briefly explain why it was skipped. In `@src/test/java/org/example/ConfigurableFilterPipelineTest.java` around lines 12 - 58, Add unit tests to cover route-specific filter behavior for ConfigurableFilterPipeline: create tests that register FilterRegistration entries with a non-null route pattern and assert that HttpFilter/TestFilter runs only when HttpRequest path matches and is skipped when it doesn't; add a test with multiple FilterRegistration entries having different order values to verify execution order of filters; add a test combining global (route == null) and route-specific filters to assert globals run before matching route filters; use pipeline.execute(HttpRequest, TestHandler) and assert events list and HttpResponse status codes as in existing tests to validate behavior.src/main/java/org/example/server/FilterRegistration.java (1)
5-13: Consider making a defensive copy ofroutePatternsto preserve immutability.Records are value-based types, but the
List<String>parameter can be mutated after construction. A compact constructor withList.copyOf()(which also rejectsnullelements) would enforce true immutability. Sincenullis used to signal "global" in the current design, you'd need to handle that:♻️ Suggested defensive copy
public record FilterRegistration( HttpFilter filter, int order, List<String> routePatterns ) { + public FilterRegistration { + routePatterns = routePatterns == null ? null : List.copyOf(routePatterns); + } + public boolean isGlobal() { return routePatterns == null || routePatterns.isEmpty(); } }🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and decide whether a code change is actually needed. If the finding is not valid or no change is required, do not modify code for that item and briefly explain why it was skipped. In `@src/main/java/org/example/server/FilterRegistration.java` around lines 5 - 13, The record FilterRegistration accepts a mutable List<String> routePatterns which can be modified after construction; add a compact constructor for FilterRegistration that replaces the incoming routePatterns with an immutable copy using List.copyOf(routePatterns) while preserving the existing null semantic (i.e., if routePatterns is null keep it null, otherwise assign List.copyOf(routePatterns)); this ensures immutability of the routePatterns field and retains behavior of isGlobal().src/main/java/org/example/server/ConfigurableFilterPipeline.java (2)
12-13: Constructor does not make a defensive copy ofregistrations.If the caller mutates the list after constructing the pipeline, behavior becomes unpredictable. Consider
List.copyOf(registrations)for safety.♻️ Suggested fix
public ConfigurableFilterPipeline(List<FilterRegistration> registrations) { - this.registrations = registrations; + this.registrations = List.copyOf(registrations); }🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and decide whether a code change is actually needed. If the finding is not valid or no change is required, do not modify code for that item and briefly explain why it was skipped. In `@src/main/java/org/example/server/ConfigurableFilterPipeline.java` around lines 12 - 13, The constructor ConfigurableFilterPipeline(List<FilterRegistration> registrations) stores the incoming list directly, making the internal field registrations vulnerable to external mutation; change the constructor to defensively copy and null-check the argument (e.g., use List.copyOf(registrations) after Objects.requireNonNull) so the pipeline holds an immutable snapshot of registrations and cannot be affected by caller mutations.
100-146: Multiple top-level types in a single file hinders discoverability and IDE navigation.
HttpRequest,HttpResponse,HttpFilter,FilterChain, andTerminalHandlerare all foundational types that will be imported across the codebase. Placing each in its own file (consistent with the existingFilterRegistration.javaandRoutePattern.javapattern) improves discoverability and follows standard Java conventions.🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and decide whether a code change is actually needed. If the finding is not valid or no change is required, do not modify code for that item and briefly explain why it was skipped. In `@src/main/java/org/example/server/ConfigurableFilterPipeline.java` around lines 100 - 146, The file contains multiple top-level types that should be split into their own files; extract HttpRequest, HttpResponse, HttpFilter, FilterChain, and TerminalHandler into separate public classes/interfaces (create HttpRequest.java, HttpResponse.java, HttpFilter.java, FilterChain.java, TerminalHandler.java) preserving their constructors and method signatures, keep them in the same package, remove those definitions from ConfigurableFilterPipeline.java, and update any imports/usages (e.g., references to HttpRequest.path(), HttpRequest.getMethod(), HttpResponse.getStatusCode(), HttpResponse.getBody(), and the handle/next methods) across the codebase so compilation and behavior remain unchanged.
| - name: Compile with Maven | ||
| run: mvn -B compile --file pom.xml | ||
|
|
||
| - name: Test with Maven | ||
| run: mvn -B test --file pom.xml No newline at end of file |
There was a problem hiding this comment.
Spotless format check is not enforced in CI.
The spotless-maven-plugin check is bound to the verify phase in pom.xml, but CI only runs compile and test. Formatting violations will go undetected. Either add a separate mvn spotless:check step or change the test step to run mvn -B verify.
Proposed fix: add a Spotless check step
- name: Test with Maven
run: mvn -B test --file pom.xml
+
+ - name: Check formatting with Spotless
+ run: mvn -B spotless:check --file pom.xml📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Compile with Maven | |
| run: mvn -B compile --file pom.xml | |
| - name: Test with Maven | |
| run: mvn -B test --file pom.xml | |
| - name: Compile with Maven | |
| run: mvn -B compile --file pom.xml | |
| - name: Test with Maven | |
| run: mvn -B test --file pom.xml | |
| - name: Check formatting with Spotless | |
| run: mvn -B spotless:check --file pom.xml |
🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.
In @.github/workflows/ci.yml around lines 28 - 32, CI currently only runs mvn -B
compile and mvn -B test so the spotless-maven-plugin (bound to the verify phase)
isn't executed; update the workflow to enforce formatting by either adding a
dedicated step that runs mvn spotless:check (e.g., name "Spotless check" run mvn
-B spotless:check) before tests, or change the existing test step to run mvn -B
verify (replacing mvn -B test) so the plugin runs during the build; reference
the mvn commands and spotless-maven-plugin when making the change.
src/main/java/org/example/server/ConfigurableFilterPipeline.java
Outdated
Show resolved
Hide resolved
src/main/java/org/example/server/ConfigurableFilterPipeline.java
Outdated
Show resolved
Hide resolved
| while (true) { | ||
| Socket clientSocket = serverSocket.accept(); // block | ||
| System.out.println("Client connected: " + clientSocket.getRemoteSocketAddress()); | ||
| clientSocket.close(); | ||
| } |
There was a problem hiding this comment.
clientSocket is not protected by try-with-resources — potential resource leak.
If any code between accept() and close() throws (or when you later add request handling logic between them), the socket will leak. Wrap it in its own try-with-resources.
🐛 Proposed fix
while (true) {
- Socket clientSocket = serverSocket.accept(); // block
- System.out.println("Client connected: " + clientSocket.getRemoteSocketAddress());
- clientSocket.close();
+ try (Socket clientSocket = serverSocket.accept()) {
+ System.out.println("Client connected: " + clientSocket.getRemoteSocketAddress());
+ // TODO: handle request / response
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| while (true) { | |
| Socket clientSocket = serverSocket.accept(); // block | |
| System.out.println("Client connected: " + clientSocket.getRemoteSocketAddress()); | |
| clientSocket.close(); | |
| } | |
| while (true) { | |
| try (Socket clientSocket = serverSocket.accept()) { | |
| System.out.println("Client connected: " + clientSocket.getRemoteSocketAddress()); | |
| // TODO: handle request / response | |
| } | |
| } |
🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.
In `@src/main/java/org/example/TcpServer.java` around lines 19 - 23, The accept
loop in TcpServer currently calls serverSocket.accept() and then
clientSocket.close() outside any try-with-resources, risking a socket leak if
handling code throws; change the loop to acquire the accepted Socket in a
try-with-resources (e.g., try (Socket clientSocket = serverSocket.accept()) {
... }) so the socket is always closed, move the System.out.println and any
request-handling logic inside that try block, and leave serverSocket management
unchanged; update the loop in the TcpServer class (the accept/close section)
accordingly.
| @@ -0,0 +1,101 @@ | |||
| package org.example.server; | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the file path vs package declaration
echo "=== Finding ConfigurableFilterPipelineTest.java ==="
fd "ConfigurableFilterPipelineTest.java"
echo ""
echo "=== Package declaration in the file ==="
fd "ConfigurableFilterPipelineTest.java" --exec head -5 {}Repository: ithsjava25/ithsjava25-java-project-webserver-maven-java-template
Length of output: 355
🏁 Script executed:
#!/bin/bash
# Check the full test file to see imports and class usage
echo "=== Full ConfigurableFilterPipelineTest.java ==="
cat -n src/test/java/org/example/ConfigurableFilterPipelineTest.java
echo ""
echo "=== Check if org.example.server exists with relevant classes ==="
fd "ConfigurableFilterPipeline\.java"
echo ""
fd "HttpFilter\.java"
echo ""
fd "FilterChain\.java"Repository: ithsjava25/ithsjava25-java-project-webserver-maven-java-template
Length of output: 3465
🏁 Script executed:
#!/bin/bash
# Check the directory structure in test source
echo "=== Directory structure in src/test/java/org/example/ ==="
ls -la src/test/java/org/example/
echo ""
echo "=== Check if src/test/java/org/example/server/ exists ==="
ls -la src/test/java/org/example/server/ 2>&1 || echo "Directory does not exist"
echo ""
echo "=== List all Java files in test directory ==="
find src/test/java -name "*.java"Repository: ithsjava25/ithsjava25-java-project-webserver-maven-java-template
Length of output: 834
Package declaration does not match the file path — this code will not compile.
The file is located at src/test/java/org/example/ConfigurableFilterPipelineTest.java but declares package org.example.server;. In Java, the package declaration must match the directory structure relative to the source root. Move this file to src/test/java/org/example/server/ConfigurableFilterPipelineTest.java to match the declared package, which is required since the test uses unimported types (HttpFilter, FilterChain, FilterRegistration, etc.) that are package-private or default-access from the same package.
🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.
In `@src/test/java/org/example/ConfigurableFilterPipelineTest.java` at line 1, The
test class ConfigurableFilterPipelineTest is declared in package
org.example.server but resides in src/test/java/org/example/..., causing a
package/path mismatch and preventing access to package-private types like
HttpFilter, FilterChain, and FilterRegistration; fix by either updating the
package declaration in ConfigurableFilterPipelineTest to match its directory
(e.g., package org.example;) or move the file into the server package directory
so the package declaration org.example.server matches the file path and the test
can access the package-private types.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/test/java/org/example/server/ConfigurableFilterPipelineTest.java`:
- Around line 12-58: Add tests that exercise per-route registrations, ordering,
and short-circuiting: create FilterRegistration instances with non-null
RoutePattern matching and non-matching routes and assert only matching per-route
filters run (use TestFilter and TestHandler with pipeline.execute), add multiple
FilterRegistration entries with differing priority values to verify
ConfigurableFilterPipeline orders filters by priority, and add a stopping
TestFilter followed by another filter to assert the second filter is never
invoked and the pipeline returns the stopping filter's response status;
reference FilterRegistration, RoutePattern, HttpFilter/TestFilter,
ConfigurableFilterPipeline, TestHandler, and pipeline.execute when adding these
new test cases.
- Around line 26-29: The test currently ignores the return value of
pipeline.execute; capture its HttpResponse (the result of pipeline.execute when
invoked with new HttpRequest("GET", "/home") and new TestHandler(events)) and
add an assertion that response.getStatusCode() (or equivalent status accessor on
the returned object) equals 200 to ensure TestHandler was executed; update the
test method global_filter_runs to assign the result to a variable and assert
status == 200 alongside existing event checks.
---
Duplicate comments:
In `@src/test/java/org/example/server/ConfigurableFilterPipelineTest.java`:
- Line 1: The package declaration in ConfigurableFilterPipelineTest.java should
match its directory; ensure the file begins with "package org.example.server;"
(as shown) so the class ConfigurableFilterPipelineTest compiles under
src/test/java/org/example/server; if not present, add or update that package
line and re-run mvn/gradle tests to confirm the prior compilation error is
resolved.
| pipeline.execute( | ||
| new HttpRequest("GET", "/home"), | ||
| new TestHandler(events) | ||
| ); |
There was a problem hiding this comment.
global_filter_runs discards the pipeline response — missing assertion on status code.
The return value of pipeline.execute() is ignored. You should assert the response is 200 to verify the handler was actually reached and produced the correct result, not just that events were recorded.
Proposed fix
- pipeline.execute(
+ HttpResponse response = pipeline.execute(
new HttpRequest("GET", "/home"),
new TestHandler(events)
);
+ assertEquals(200, response.getStatusCode());
assertEquals(
List.of("filter", "handler"),
events
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pipeline.execute( | |
| new HttpRequest("GET", "/home"), | |
| new TestHandler(events) | |
| ); | |
| HttpResponse response = pipeline.execute( | |
| new HttpRequest("GET", "/home"), | |
| new TestHandler(events) | |
| ); | |
| assertEquals(200, response.getStatusCode()); | |
| assertEquals( | |
| List.of("filter", "handler"), | |
| events | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/test/java/org/example/server/ConfigurableFilterPipelineTest.java` around
lines 26 - 29, The test currently ignores the return value of pipeline.execute;
capture its HttpResponse (the result of pipeline.execute when invoked with new
HttpRequest("GET", "/home") and new TestHandler(events)) and add an assertion
that response.getStatusCode() (or equivalent status accessor on the returned
object) equals 200 to ensure TestHandler was executed; update the test method
global_filter_runs to assign the result to a variable and assert status == 200
alongside existing event checks.
Add missing tests for configurable filter pipeline
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/test/java/org/example/server/ConfigurableFilterPipelineTest.java`:
- Around line 102-123: The test
mixed_pipeline_runs_global_then_route_then_handler relies on stable ordering for
equal order values; make it deterministic by assigning distinct order values to
the FilterRegistration entries instead of both using 1: keep the global
TestFilter/FilterRegistration at order 1 and change the route
TestFilter/FilterRegistration to a higher order (e.g. 2) so
ConfigurableFilterPipeline (constructed from regs) will always execute "g1"
before "r1" and produce the asserted events list; update only the order argument
in the List.of(...) registrations for the TestFilter instances referenced in
this test.
---
Duplicate comments:
In `@src/test/java/org/example/server/ConfigurableFilterPipelineTest.java`:
- Around line 26-29: The tests currently discard the return value of
pipeline.execute (called with new HttpRequest("GET", "/home") and new
TestHandler(events)), so they don't verify the response status; capture the
response from pipeline.execute in each affected test (global_filter_runs,
route_specific_filter_runs_when_path_matches,
route_specific_filter_is_skipped_when_path_does_not_match,
mixed_pipeline_runs_global_then_route_then_handler, ordering_is_by_order_field)
and add an assertion that response.getStatus() (or the appropriate status
accessor on the returned response object) equals the expected status code (e.g.,
200) to ensure the pipeline didn't return an error response while still
recording events.
| @Test | ||
| void mixed_pipeline_runs_global_then_route_then_handler() { | ||
| List<String> events = new ArrayList<>(); | ||
|
|
||
| HttpFilter global = new TestFilter("g1", events, false); | ||
| HttpFilter route = new TestFilter("r1", events, false); | ||
|
|
||
| List<FilterRegistration> regs = List.of( | ||
| new FilterRegistration(global, 1, null), | ||
| new FilterRegistration(route, 1, List.of("/api/*")) | ||
| ); | ||
|
|
||
| ConfigurableFilterPipeline pipeline = | ||
| new ConfigurableFilterPipeline(regs); | ||
|
|
||
| pipeline.execute( | ||
| new HttpRequest("GET", "/api/users"), | ||
| new TestHandler(events) | ||
| ); | ||
|
|
||
| assertEquals(List.of("g1", "r1", "handler"), events); | ||
| } |
There was a problem hiding this comment.
mixed_pipeline test relies on insertion order when both filters share the same order value.
Both the global and route filter are registered with order 1 (lines 110–111). The test asserts ["g1", "r1", "handler"], which implicitly assumes that filters with equal order preserve their registration order (stable sort). If ConfigurableFilterPipeline uses an unstable sort, this test becomes flaky. Either document this as an intentional contract (stable ordering) or give the two filters distinct order values to make the test deterministic regardless of sort stability.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/test/java/org/example/server/ConfigurableFilterPipelineTest.java` around
lines 102 - 123, The test mixed_pipeline_runs_global_then_route_then_handler
relies on stable ordering for equal order values; make it deterministic by
assigning distinct order values to the FilterRegistration entries instead of
both using 1: keep the global TestFilter/FilterRegistration at order 1 and
change the route TestFilter/FilterRegistration to a higher order (e.g. 2) so
ConfigurableFilterPipeline (constructed from regs) will always execute "g1"
before "r1" and produce the asserted events list; update only the order argument
in the List.of(...) registrations for the TestFilter instances referenced in
this test.
* Added basic YAML config-file. * Added class ConfigLoader with static classes for encapsulation * Added static metod loadOnce and step one of static method load * Added static method createMapperFor that checks for YAML or JSON-files before creating an ObjectMapper object. * implement ConfigLoader refs #13 * Added AppConfig.java record for config after coderabbit feedback * Updated ConfigLoader to use AppConfig record and jackson 3 * Added tests for ConfigLoader and reset cached method in ConfigLoader to ensure test isolation with static cache * Removed unused dependency. Minor readability tweaks in AppConfig. * Added check for illegal port numbers to withDefaultsApplied-method. * Added test for illegal port numbers.
* Add 404 error handling to `StaticFileHandler` with fallback page * Add test coverage for `StaticFileHandler` and improve constructor flexibility - Introduced a new constructor in `StaticFileHandler` to support custom web root paths, improving testability. - Added `StaticFileHandlerTest` to validate static file serving and error handling logic. * Add test for 404 handling in `StaticFileHandler` - Added a test to ensure nonexistent files return a 404 response. - Utilized a temporary directory and fallback file for improved test isolation. * Verify `Content-Type` header in `StaticFileHandlerTest` after running Pittest, mutations survived where setHeaders could be removed without test failure. * Remove unused `.btn` styles from `pageNotFound.html` * Improve 404 handling in `StaticFileHandler`: add fallback to plain text response if `pageNotFound.html` is missing, and fix typo in `pageNotFound.html`, after comments from CodeRabbit.
There was a problem hiding this comment.
Actionable comments posted: 16
🧹 Nitpick comments (16)
src/main/java/org/example/config/ConfigLoader.java (1)
38-53:load()is public and does not apply defaults — document the distinction.
loadOnce()callswithDefaultsApplied()butload()does not. This asymmetry is a subtle footgun for callers. Consider adding a brief Javadoc toload()stating that it returns the raw parsed config and that callers must callwithDefaultsApplied()themselves if needed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/config/ConfigLoader.java` around lines 38 - 53, Add a brief Javadoc to the public load(Path) method explaining it returns the raw parsed AppConfig (or AppConfig.defaults() when file missing/null) and does not apply defaults; explicitly state callers must invoke AppConfig.withDefaultsApplied() themselves if they need defaults applied. Reference the related methods in the comment—loadOnce() and withDefaultsApplied()—so callers understand the asymmetry and when to use load() vs loadOnce().src/main/java/org/example/httpparser/HttpParseRequestLine.java (3)
14-15: Redundant local variable.
readeris just an alias for the parameterbrand serves no purpose. Usebrdirectly.Proposed fix
public void parseHttpRequest(BufferedReader br) throws IOException { - BufferedReader reader = br; - String requestLine = reader.readLine(); + String requestLine = br.readLine();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/httpparser/HttpParseRequestLine.java` around lines 14 - 15, In parseHttpRequest (class HttpParseRequestLine) remove the redundant local alias "reader" (which is assigned from the parameter br) and use the method parameter br directly; update all references of "reader" inside parseHttpRequest to "br" and delete the unused BufferedReader reader declaration to eliminate the needless variable.
30-31: No validation on URI or HTTP version.
uriandversionare set without any validation. Consider at minimum validating thatversionmatches the expectedHTTP/x.xpattern to reject malformed requests early, and sanitizing the URI against path traversal (e.g., rejecting..segments) before it reaches downstream handlers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/httpparser/HttpParseRequestLine.java` around lines 30 - 31, HttpParseRequestLine currently calls setUri(requestLineArray[1]) and setVersion(requestLineArray[2]) without validation; update the parsing logic in HttpParseRequestLine to validate the version with a strict pattern (e.g., match ^HTTP/\d+\.\d+$) and reject or throw a parse error for non-matching values, and sanitize/validate the URI passed to setUri by normalizing the path and rejecting paths containing path-traversal segments like ".." (or percent-encoded equivalents) before accepting it; ensure you use the existing setUri and setVersion entrypoints so downstream code continues to work.
11-11:debugis alwaysfalse— dead code.The
debugfield is initialized tofalsewith no setter or constructor parameter to enable it. The logging block on lines 34–38 is unreachable. Either remove the dead code or expose a way to toggle debug mode.Also applies to: 34-38
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/httpparser/HttpParseRequestLine.java` at line 11, The debug field in class HttpParseRequestLine is dead code because it’s always false; either remove the field and the logging block that references it (the unreachable logging statements) or make it configurable by adding a constructor parameter or a public setter (e.g., add HttpParseRequestLine(boolean debug) or setDebug(boolean debug)) and use that value when deciding whether to execute the logging block; alternatively replace the ad-hoc boolean with a proper logger check (logger.isDebugEnabled()) if a logging framework is available. Ensure you update any callers to pass the debug flag if you add a constructor, or add the setter usage where instances are created if you choose the setter approach.pom.xml (1)
130-136: JaCoCo coverage is disabled (<skip>true</skip>).Was this intentional? If coverage reporting is desired in CI, this skip flag will suppress it entirely.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pom.xml` around lines 130 - 136, The jacoco configuration currently sets <skip>true</skip> in the jacoco-maven-plugin which disables coverage; remove or change that setting so coverage runs in CI — update the jacoco-maven-plugin configuration (artifactId: jacoco-maven-plugin, version: 0.8.14) to either remove the <skip> flag or set <skip>false</skip> and ensure the plugin's report and prepare-agent goals are enabled for the build profile used in CI.src/test/java/org/example/http/HttpResponseBuilderTest.java (2)
16-44: Test coverage is minimal — consider expanding.Only the happy path (string body with 200) and UTF-8 length are tested. Consider adding tests for:
setBody(byte[])path- Custom status codes (e.g., 404) and their reason phrases
- Custom headers via
setHeaders()- Interaction between the two
setBodyoverloads (string then byte[], and vice versa)These would catch the mutual-exclusion and idempotency bugs noted in the production code review.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/java/org/example/http/HttpResponseBuilderTest.java` around lines 16 - 44, Add unit tests for HttpResponseBuilder to cover the missing paths: write tests that call setBody(byte[]) (in addition to setBody(String)) and verify build() uses the provided raw bytes for Content-Length and body bytes; add tests that set a non-200 status (e.g., 404 with its reason phrase) and assert the status line in build(); add tests that call setHeaders() with custom headers and assert they appear (and that Content-Length/header precedence is correct); and add tests exercising both orders of setBody overloads (String then byte[] and byte[] then String) to ensure mutual exclusion/idempotency between setBody(String) and setBody(byte[]).
7-7: Minor: Extra leading whitespace on class declaration.Line 7 has extra indentation before
class. Since Spotless is configured in the build, this may be caught automatically, but worth fixing for consistency.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/java/org/example/http/HttpResponseBuilderTest.java` at line 7, Remove the extra leading whitespace before the class declaration for HttpResponseBuilderTest so the line reads "class HttpResponseBuilderTest {" with normal indentation; update the file's indentation to match the project's style (and reformat or run Spotless if configured) to ensure the change passes formatting checks.src/main/java/org/example/httpparser/HttpParser.java (2)
40-45: Dead debug code usingSystem.out.println— inconsistent with parent'sLogger.Same
debug = falsewith no setter pattern as the parent class. Also, the parent usesjava.util.logging.Loggerwhile this class usesSystem.out.println. Remove or unify the debug approach.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/httpparser/HttpParser.java` around lines 40 - 45, The debug block in HttpParser using a local boolean "debug" and System.out.println is dead/inconsistent; remove the prints and either remove the "debug" flag or replace it with java.util.logging.Logger usage: obtain a Logger (java.util.logging.Logger) in HttpParser, replace System.out.println calls that reference headersMap and "Host" with logger.log(Level.FINE, ...) (or Level.INFO if appropriate), and guard with logger.isLoggable(Level.FINE) instead of the debug flag so logging behavior matches the parent's logging approach.
53-55:getHeadersMap()exposes mutable internal state.Callers can add, remove, or modify headers in the returned map, breaking encapsulation. Return an unmodifiable view if mutation is not intended.
Proposed fix
public Map<String, String> getHeadersMap() { - return headersMap; + return java.util.Collections.unmodifiableMap(headersMap); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/httpparser/HttpParser.java` around lines 53 - 55, getHeadersMap() currently returns the internal mutable headersMap from HttpParser, exposing and allowing external mutation; change it to return an unmodifiable view or immutable copy instead (e.g., return Collections.unmodifiableMap(new HashMap<>(headersMap))) and add the necessary import (java.util.Collections and java.util.HashMap) so callers cannot modify internal state while preserving current signature; update any unit tests that relied on mutating the returned map.src/test/java/org/example/httpparser/HttpParserTest.java (1)
12-53: Consider adding tests for duplicate header merging andparseRequest()flow.Current tests cover basic header parsing, empty input, and invalid lines. Missing scenarios include:
- Duplicate headers (verifying the
,concatenation inheadersMap.merge)- Full request flow:
parseRequest()followed byparseHttp(), then asserting both method/URI/version and headers- Calling
parseHttp()when reader is not set (expected failure)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/java/org/example/httpparser/HttpParserTest.java` around lines 12 - 53, Add unit tests in HttpParserTest to cover duplicate-header merging and full request parsing: create a test that feeds input with repeated header names (e.g., "X-Test: a\r\nX-Test: b\r\n\r\n") to HttpParser.parseHttp() and assert that HttpParser.getHeadersMap() contains "X-Test" with the concatenated value matching the merge behavior (", " separator); add a test that exercises parseRequest() followed by parseHttp() using a full request line (method, URI, version) plus headers and assert the parsed method/URI/version (from parseRequest()/getMethod()/getUri()/getVersion() or equivalent) and headers map; and add a test that calls parseHttp() when no reader is set and assert the expected failure/exception to ensure proper error handling.Dockerfile (1)
10-11:USERdirective should precedeENTRYPOINT.While Docker applies
USERat container start regardless of declaration order, the convention is to placeUSERbeforeENTRYPOINT/CMDso readers immediately see the security context. Also, anyRUN/COPYafterUSERwould execute as that user, so ordering matters for consistency.Proposed fix
+USER appuser ENTRYPOINT ["java", "-classpath", "/app", "org.example.App"] -USER appuser🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Dockerfile` around lines 10 - 11, The Dockerfile currently declares ENTRYPOINT ["java", "-classpath", "/app", "org.example.App"] before USER appuser; move the USER appuser line to appear before the ENTRYPOINT line so the USER directive precedes ENTRYPOINT (ensure no RUN/COPY that must run as root are placed after the new USER line), preserving the same ENTRYPOINT value and order of other instructions.src/main/java/org/example/ConnectionHandler.java (1)
10-11: Fields should beprivate.
clientandurihave package-private access. Since they aren't accessed from other classes in the package, make themprivateto enforce encapsulation.♻️ Proposed fix
- Socket client; - String uri; + private Socket client; + private String uri;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/ConnectionHandler.java` around lines 10 - 11, Make the fields in ConnectionHandler private to enforce encapsulation: change the visibility of the Socket client and String uri fields to private (i.e., update the declarations for client and uri in class ConnectionHandler) so they are not package-private; if other class usage exists, provide private getters/setters instead of leaving them non-private.src/main/java/org/example/StaticFileHandler.java (2)
13-13:WEB_ROOTnaming — usecamelCasefor instance fields.Java convention reserves
ALL_CAPSforstatic finalconstants. Since this is an instance field, rename towebRoot.♻️ Proposed fix
- private final String WEB_ROOT; + private final String webRoot;(Update all references in the class accordingly.)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/StaticFileHandler.java` at line 13, The instance field WEB_ROOT in StaticFileHandler should follow Java camelCase conventions; rename the field to webRoot and update all references within the class (constructor, methods, any usages) to use webRoot instead of WEB_ROOT; ensure any assignments, getters/setters or usages in methods like the constructor and serve/static file path resolution are updated accordingly and the field remains non-static and final.
14-15: Mutable instance fieldsfileBytesandstatusCodemake the class stateful.These fields are used only to pass data from
handleGetRequesttosendGetRequest. This couples the two methods via side-effects and would break if the class were ever shared across threads. Consider returning a response record/object fromhandleGetRequestinstead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/StaticFileHandler.java` around lines 14 - 15, The class currently uses mutable instance fields fileBytes and statusCode to pass data between handleGetRequest and sendGetRequest, creating statefulness and thread-safety issues; refactor handleGetRequest to return an immutable response object or record (e.g., FileResponse { byte[] bytes; int status }) and change sendGetRequest to accept that FileResponse as a parameter, remove the fileBytes and statusCode fields, and update callers to use the returned FileResponse from handleGetRequest when invoking sendGetRequest.src/test/java/org/example/httpparser/HttpParseRequestLineTest.java (1)
8-10: Mixing AssertJ and JUnit assertion styles.Tests inconsistently use
assertThatThrownBy(AssertJ) in one test andassertThrows(JUnit) in others. Pick one style for consistency—preferably AssertJ throughout since you're already importing it.♻️ Example: unify on AssertJ
- `@Test` - void testParserThrowErrorWhenEmpty(){ - InputStream in = new ByteArrayInputStream("".getBytes()); - httpParseRequestLine.setReader(in); - Exception exception = assertThrows( - IOException.class, () -> httpParseRequestLine.parseRequest() - ); - - assertThat(exception.getMessage()).isEqualTo("HTTP Request Line is Null or Empty"); - } + `@Test` + void testParserThrowErrorWhenEmpty(){ + InputStream in = new ByteArrayInputStream("".getBytes()); + httpParseRequestLine.setReader(in); + assertThatThrownBy(() -> httpParseRequestLine.parseRequest()) + .isInstanceOf(IOException.class) + .hasMessage("HTTP Request Line is Null or Empty"); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/java/org/example/httpparser/HttpParseRequestLineTest.java` around lines 8 - 10, The tests in HttpParseRequestLineTest mix JUnit and AssertJ assertions; unify to AssertJ by replacing usages of JUnit's assertThrows/assertEquals/assertTrue/etc. with AssertJ equivalents (e.g., use assertThatThrownBy(...) for exceptions and assertThat(...) for value checks) in the test methods and remove the static import of org.junit.jupiter.api.Assertions; update any test methods referencing assertThrows to use assertThatThrownBy and convert other JUnit assertions to assertThat calls so all assertions consistently use AssertJ.src/main/java/org/example/TcpServer.java (1)
18-26: UnencryptedServerSocket— acceptable for local dev, but note for production.Static analysis flagged the use of a plain
ServerSocket(CWE-319). For a learning/demo server this is fine, but for any production use you'd wantSSLServerSocketFactoryor a TLS-terminating reverse proxy in front.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/example/TcpServer.java` around lines 18 - 26, The code uses an unencrypted ServerSocket in the TCP server (in the block creating ServerSocket and spawning threads that call handleClient), which is fine for local demos but not production; update the server to use an SSLServerSocket created from SSLServerSocketFactory (or clearly document and enforce use of a TLS-terminating proxy) by replacing ServerSocket instantiation with an SSLServerSocket from SSLServerSocketFactory.getDefault()/custom SSLContext, load server key material/keystore, and accept client connections from that SSLServerSocket so handleClient receives encrypted sockets; ensure exception handling and logging remain intact for SSL setup failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@Dockerfile`:
- Around line 1-5: The Dockerfile only runs "mvn compile" so third‑party JARs
are not produced/copied and runtime will throw ClassNotFoundException; also the
repo's www/ static assets are never copied. Replace the compile step with a
package step that produces a runnable artifact (e.g., RUN mvn package with a
shade/assembly plugin configured to create an uber/fat JAR), or alternatively
copy dependency JARs from the build Maven repository and set the runtime
classpath to include target/classes plus those jars (refer to the existing RUN
mvn compile and java -classpath /app org.example.App in the diff). Finally add a
COPY www/ /app/www/ (or similar) so StaticFileHandler can find
index.html/pageNotFound.html and ensure the final image runs the fat JAR (or
uses the adjusted classpath).
In `@pom.xml`:
- Around line 22-26: Remove the unused Bucket4j dependency block (groupId
com.bucket4j, artifactId bucket4j_jdk17-core, version ${bucket4j.version}) from
the pom so it is not declared as a compile dependency; if you intend to add
rate-limiting later instead change it to an appropriate optional/test scope or
add a clear TODO comment linking to the feature ticket, but do not leave the
compile-scoped dependency in the pom unused.
In `@src/main/java/org/example/config/AppConfig.java`:
- Around line 3-4: The AppConfig class is importing Jackson 2.x annotations
(com.fasterxml.jackson.annotation.*) which are incompatible with the project
Jackson 3.x dependency and the ObjectMapper in ConfigLoader; replace those
imports so AppConfig uses the Jackson 3.x annotations (tools.jackson.annotations
or the correct tools.jackson package equivalents) for `@JsonProperty` and
`@JsonIgnoreProperties` to ensure the ObjectMapper in ConfigLoader recognizes them
during deserialization. Locate AppConfig and update its import statements for
JsonProperty and JsonIgnoreProperties to the matching tools.jackson namespace
(keeping the annotation usage and field names unchanged), then run tests to
confirm mapping at the deserialization call in ConfigLoader works as expected.
In `@src/main/java/org/example/config/ConfigLoader.java`:
- Around line 55-66: createMapperFor: the code incorrectly calls
YAMLMapper.builder(new YAMLFactory()) (Jackson 3.x YAMLMapper.builder() takes no
factory) and silently falls back to YAML for unknown extensions; remove the
YAMLFactory argument from both YAMLMapper.builder() calls and instead of
returning a YAML mapper for unrecognized file extensions throw an
IllegalArgumentException with a clear message; update createMapperFor to use
YAMLMapper.builder().build() for .yml/.yaml, JsonMapper.builder().build() for
.json, and throw new IllegalArgumentException("Unsupported config file
extension: "+ name) for other cases.
In `@src/main/java/org/example/ConnectionHandler.java`:
- Around line 27-36: The resolveTargetFile method currently appends ".html" to
any URI that doesn't end with ".html", which incorrectly rewrites asset requests
like "/style.css" to "/style.css.html"; update resolveTargetFile to (1) treat
"/" or "" as "index.html", (2) detect if the URI already has a file extension
using a regex like "\\.[^/]+$" (hasExtension) and leave it unchanged, and (3)
only append ".html" when the URI has no extension (i.e., not hasExtension).
Reference: method resolveTargetFile and the field this.uri.
In `@src/main/java/org/example/http/HttpResponseBuilder.java`:
- Line 49: Replace the misleading hardcoded default "OK" used when looking up
reason phrases in REASON_PHRASES; in HttpResponseBuilder change the getOrDefault
call to return a neutral default like "Unknown" (or ensure REASON_PHRASES
contains the full set of standard status codes) so that unmapped status codes
(e.g., 301, 403, 405) do not incorrectly show "OK" — update the line that
references REASON_PHRASES and the local variable reason accordingly.
- Around line 21-27: The two overloads setBody(String) and setBody(byte[]) leave
the other representation intact, so build() (which checks body.isEmpty()) can
pick the wrong payload; modify setBody(String) to clear bytebody (set bytebody =
null) and modify setBody(byte[]) to clear body (set body = "") so they are
mutually exclusive, ensuring build() uses the intended branch when either
setBody(String) or setBody(byte[]) is called.
- Around line 38-46: The build() method mutates internal state by calling
setBody(new String(bytebody,...)) and can corrupt binary data; make build()
side-effect-free by not modifying fields (do not call setBody or change
body/bytebody). Instead, compute contentLength from bytebody when bytebody !=
null (use bytebody.length) or from body.getBytes(UTF_8). Assemble the response
as bytes (use a ByteArrayOutputStream or similar) writing the headers as UTF_8
bytes and then writing bytebody directly when present, otherwise write body
bytes; ensure build() returns the final serialized bytes/string without altering
the HttpResponseBuilder's body or bytebody fields.
In `@src/main/java/org/example/httpparser/HttpParser.java`:
- Around line 16-20: The setReader method in HttpParser currently ignores
subsequent calls because of the if (this.reader == null) guard; change setReader
to either throw an IllegalStateException if reader is already set (fail-fast) or
(preferably) allow reassignment by always replacing this.reader with a new
BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)), close the
previous reader if non-null to avoid resource leaks, and reset any parsing state
such as headersMap (clear or reinitialize headersMap) so the parser is ready for
a new connection; update references to reader and headersMap accordingly in the
HttpParser class.
- Around line 22-25: parseHttp() and parseRequest() currently dereference the
instance field reader without ensuring it's initialized; add a precondition
check at the start of both methods (parseHttp() and parseRequest()) that
validates reader != null and throws a clear unchecked exception such as
IllegalStateException with a descriptive message (e.g., "reader not set; call
setReader(...) before parsing"), or alternatively initialize reader eagerly in
the constructor/field declaration if appropriate; use the setReader(...) method
name in the message to guide callers.
In `@src/main/java/org/example/StaticFileHandler.java`:
- Around line 27-42: The handleGetRequest method currently constructs File file
= new File(WEB_ROOT, uri) without sanitizing uri, enabling path traversal;
change this to canonicalize/normalize both WEB_ROOT and the resolved requested
path (e.g., use Path#toRealPath or File#getCanonicalPath on WEB_ROOT and the
resolved path for the File created from WEB_ROOT + uri) and verify the requested
path startsWith the canonical WEB_ROOT before reading bytes; if the check fails,
treat as not found or forbidden (set statusCode = 404 or 403 and use
pageNotFound.html or a fixed message) and avoid reading the fileBytes from
outside WEB_ROOT. Ensure this logic is applied in handleGetRequest and uses the
same canonical paths for comparisons to prevent symlink bypass.
- Around line 44-54: The response currently hardcodes "Content-Type: text/html"
in sendGetRequest which mislabels all files; update sendGetRequest to determine
the MIME type dynamically (e.g., use Files.probeContentType(path) or a small
extension→MIME map) after handleGetRequest has populated fileBytes and the file
path/filename, then call response.setHeaders(Map.of("Content-Type", mimeType));
ensure you fall back to "application/octet-stream" if probeContentType returns
null and keep using HttpResponseBuilder, statusCode, and fileBytes as before.
In `@src/main/java/org/example/TcpServer.java`:
- Around line 29-35: The catch in handleClient currently concatenates the
exception into a string and loses the original stack trace; change the throw to
use the two-argument RuntimeException constructor so the original exception is
preserved as the cause (i.e., throw new RuntimeException("Error handling client
connection", e)); update the block around ConnectionHandler and
runConnectionHandler to rethrow with the cause rather than stringifying the
exception.
In `@src/test/java/org/example/config/ConfigLoaderTest.java`:
- Around line 117-126: Replace the Swedish inline comment in the test method
invalid_yaml_fails with English for consistency: change "// saknar ':' efter
port" to an English comment like "// missing ':' after port" in the test that
writes "server:\n port 8080\n" (located near the creation of Path configFile
and the Files.writeString call) so the test and surrounding codebase use a
single language.
- Around line 128-140: Rename the test method
invalid_port_should_Throw_Exception in ConfigLoaderTest to follow consistent
snake_case and remove the extra spaces before the parentheses; for example,
change it to invalid_port_should_throw_exception() (keep the `@Test` and
`@DisplayName` unchanged) so the method name uses lowercase for "throw" and
"exception" and has no trailing whitespace before "()".
In `@src/test/java/org/example/httpparser/HttpParserTest.java`:
- Around line 15-28: The test TestHttpParserForHeaders currently feeds a full
HTTP request (including the request line) into httpParser.parseHttp(), which
lets the request line be ignored by accident; either change the test to call
httpParser.parseRequest() before httpParser.parseHttp() to consume the request
line, or alter testInput to start at the headers only (remove the "GET /...
HTTP/1.1\r\n" line) so parseHttp() receives only header lines; also rename the
test method to camelCase (e.g., testHttpParserForHeaders) to follow Java test
naming conventions.
---
Duplicate comments:
In `@src/main/java/org/example/TcpServer.java`:
- Around line 20-22: No change required: the accepted client socket from
serverSocket.accept() is correctly passed into handleClient(...) which uses
ConnectionHandler (implements AutoCloseable) to manage and close the socket, so
leave the Thread.ofVirtual().start(() -> handleClient(clientSocket)) call as-is
and ensure ConnectionHandler continues to close the socket in its close()
implementation.
---
Nitpick comments:
In `@Dockerfile`:
- Around line 10-11: The Dockerfile currently declares ENTRYPOINT ["java",
"-classpath", "/app", "org.example.App"] before USER appuser; move the USER
appuser line to appear before the ENTRYPOINT line so the USER directive precedes
ENTRYPOINT (ensure no RUN/COPY that must run as root are placed after the new
USER line), preserving the same ENTRYPOINT value and order of other
instructions.
In `@pom.xml`:
- Around line 130-136: The jacoco configuration currently sets <skip>true</skip>
in the jacoco-maven-plugin which disables coverage; remove or change that
setting so coverage runs in CI — update the jacoco-maven-plugin configuration
(artifactId: jacoco-maven-plugin, version: 0.8.14) to either remove the <skip>
flag or set <skip>false</skip> and ensure the plugin's report and prepare-agent
goals are enabled for the build profile used in CI.
In `@src/main/java/org/example/config/ConfigLoader.java`:
- Around line 38-53: Add a brief Javadoc to the public load(Path) method
explaining it returns the raw parsed AppConfig (or AppConfig.defaults() when
file missing/null) and does not apply defaults; explicitly state callers must
invoke AppConfig.withDefaultsApplied() themselves if they need defaults applied.
Reference the related methods in the comment—loadOnce() and
withDefaultsApplied()—so callers understand the asymmetry and when to use load()
vs loadOnce().
In `@src/main/java/org/example/ConnectionHandler.java`:
- Around line 10-11: Make the fields in ConnectionHandler private to enforce
encapsulation: change the visibility of the Socket client and String uri fields
to private (i.e., update the declarations for client and uri in class
ConnectionHandler) so they are not package-private; if other class usage exists,
provide private getters/setters instead of leaving them non-private.
In `@src/main/java/org/example/httpparser/HttpParser.java`:
- Around line 40-45: The debug block in HttpParser using a local boolean "debug"
and System.out.println is dead/inconsistent; remove the prints and either remove
the "debug" flag or replace it with java.util.logging.Logger usage: obtain a
Logger (java.util.logging.Logger) in HttpParser, replace System.out.println
calls that reference headersMap and "Host" with logger.log(Level.FINE, ...) (or
Level.INFO if appropriate), and guard with logger.isLoggable(Level.FINE) instead
of the debug flag so logging behavior matches the parent's logging approach.
- Around line 53-55: getHeadersMap() currently returns the internal mutable
headersMap from HttpParser, exposing and allowing external mutation; change it
to return an unmodifiable view or immutable copy instead (e.g., return
Collections.unmodifiableMap(new HashMap<>(headersMap))) and add the necessary
import (java.util.Collections and java.util.HashMap) so callers cannot modify
internal state while preserving current signature; update any unit tests that
relied on mutating the returned map.
In `@src/main/java/org/example/httpparser/HttpParseRequestLine.java`:
- Around line 14-15: In parseHttpRequest (class HttpParseRequestLine) remove the
redundant local alias "reader" (which is assigned from the parameter br) and use
the method parameter br directly; update all references of "reader" inside
parseHttpRequest to "br" and delete the unused BufferedReader reader declaration
to eliminate the needless variable.
- Around line 30-31: HttpParseRequestLine currently calls
setUri(requestLineArray[1]) and setVersion(requestLineArray[2]) without
validation; update the parsing logic in HttpParseRequestLine to validate the
version with a strict pattern (e.g., match ^HTTP/\d+\.\d+$) and reject or throw
a parse error for non-matching values, and sanitize/validate the URI passed to
setUri by normalizing the path and rejecting paths containing path-traversal
segments like ".." (or percent-encoded equivalents) before accepting it; ensure
you use the existing setUri and setVersion entrypoints so downstream code
continues to work.
- Line 11: The debug field in class HttpParseRequestLine is dead code because
it’s always false; either remove the field and the logging block that references
it (the unreachable logging statements) or make it configurable by adding a
constructor parameter or a public setter (e.g., add HttpParseRequestLine(boolean
debug) or setDebug(boolean debug)) and use that value when deciding whether to
execute the logging block; alternatively replace the ad-hoc boolean with a
proper logger check (logger.isDebugEnabled()) if a logging framework is
available. Ensure you update any callers to pass the debug flag if you add a
constructor, or add the setter usage where instances are created if you choose
the setter approach.
In `@src/main/java/org/example/StaticFileHandler.java`:
- Line 13: The instance field WEB_ROOT in StaticFileHandler should follow Java
camelCase conventions; rename the field to webRoot and update all references
within the class (constructor, methods, any usages) to use webRoot instead of
WEB_ROOT; ensure any assignments, getters/setters or usages in methods like the
constructor and serve/static file path resolution are updated accordingly and
the field remains non-static and final.
- Around line 14-15: The class currently uses mutable instance fields fileBytes
and statusCode to pass data between handleGetRequest and sendGetRequest,
creating statefulness and thread-safety issues; refactor handleGetRequest to
return an immutable response object or record (e.g., FileResponse { byte[]
bytes; int status }) and change sendGetRequest to accept that FileResponse as a
parameter, remove the fileBytes and statusCode fields, and update callers to use
the returned FileResponse from handleGetRequest when invoking sendGetRequest.
In `@src/main/java/org/example/TcpServer.java`:
- Around line 18-26: The code uses an unencrypted ServerSocket in the TCP server
(in the block creating ServerSocket and spawning threads that call
handleClient), which is fine for local demos but not production; update the
server to use an SSLServerSocket created from SSLServerSocketFactory (or clearly
document and enforce use of a TLS-terminating proxy) by replacing ServerSocket
instantiation with an SSLServerSocket from
SSLServerSocketFactory.getDefault()/custom SSLContext, load server key
material/keystore, and accept client connections from that SSLServerSocket so
handleClient receives encrypted sockets; ensure exception handling and logging
remain intact for SSL setup failures.
In `@src/test/java/org/example/http/HttpResponseBuilderTest.java`:
- Around line 16-44: Add unit tests for HttpResponseBuilder to cover the missing
paths: write tests that call setBody(byte[]) (in addition to setBody(String))
and verify build() uses the provided raw bytes for Content-Length and body
bytes; add tests that set a non-200 status (e.g., 404 with its reason phrase)
and assert the status line in build(); add tests that call setHeaders() with
custom headers and assert they appear (and that Content-Length/header precedence
is correct); and add tests exercising both orders of setBody overloads (String
then byte[] and byte[] then String) to ensure mutual exclusion/idempotency
between setBody(String) and setBody(byte[]).
- Line 7: Remove the extra leading whitespace before the class declaration for
HttpResponseBuilderTest so the line reads "class HttpResponseBuilderTest {" with
normal indentation; update the file's indentation to match the project's style
(and reformat or run Spotless if configured) to ensure the change passes
formatting checks.
In `@src/test/java/org/example/httpparser/HttpParseRequestLineTest.java`:
- Around line 8-10: The tests in HttpParseRequestLineTest mix JUnit and AssertJ
assertions; unify to AssertJ by replacing usages of JUnit's
assertThrows/assertEquals/assertTrue/etc. with AssertJ equivalents (e.g., use
assertThatThrownBy(...) for exceptions and assertThat(...) for value checks) in
the test methods and remove the static import of
org.junit.jupiter.api.Assertions; update any test methods referencing
assertThrows to use assertThatThrownBy and convert other JUnit assertions to
assertThat calls so all assertions consistently use AssertJ.
In `@src/test/java/org/example/httpparser/HttpParserTest.java`:
- Around line 12-53: Add unit tests in HttpParserTest to cover duplicate-header
merging and full request parsing: create a test that feeds input with repeated
header names (e.g., "X-Test: a\r\nX-Test: b\r\n\r\n") to HttpParser.parseHttp()
and assert that HttpParser.getHeadersMap() contains "X-Test" with the
concatenated value matching the merge behavior (", " separator); add a test that
exercises parseRequest() followed by parseHttp() using a full request line
(method, URI, version) plus headers and assert the parsed method/URI/version
(from parseRequest()/getMethod()/getUri()/getVersion() or equivalent) and
headers map; and add a test that calls parseHttp() when no reader is set and
assert the expected failure/exception to ensure proper error handling.
| FROM maven:3-eclipse-temurin-25-alpine AS build | ||
| WORKDIR /build | ||
| COPY src/ src/ | ||
| COPY pom.xml pom.xml | ||
| RUN mvn compile |
There was a problem hiding this comment.
Runtime dependencies are missing from the final image.
mvn compile only compiles source code into target/classes/. Third-party dependency JARs (Jackson, Bucket4j, etc.) are not included. At runtime, java -classpath /app org.example.App will fail with ClassNotFoundException for any library class.
Additionally, the www/ directory (containing static web assets like index.html and pageNotFound.html) lives at the repo root and is never copied into the build context or the final image, so the StaticFileHandler won't find its web root.
Consider using mvn package to produce a fat/uber JAR (with a shade or assembly plugin), or at minimum copy dependency JARs and adjust the classpath. Also add COPY www/ /app/www/ (or similar) to include static assets.
Proposed sketch (dependency-copy approach)
FROM maven:3-eclipse-temurin-25-alpine AS build
WORKDIR /build
COPY src/ src/
COPY pom.xml pom.xml
-RUN mvn compile
+COPY www/ www/
+RUN mvn dependency:copy-dependencies -DoutputDirectory=target/libs && mvn compile
FROM eclipse-temurin:25-jre-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
-COPY --from=build /build/target/classes/ /app/
-ENTRYPOINT ["java", "-classpath", "/app", "org.example.App"]
USER appuser
+COPY --from=build /build/target/classes/ /app/classes/
+COPY --from=build /build/target/libs/ /app/libs/
+COPY --from=build /build/www/ /app/www/
+ENTRYPOINT ["java", "-classpath", "/app/classes:/app/libs/*", "org.example.App"]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Dockerfile` around lines 1 - 5, The Dockerfile only runs "mvn compile" so
third‑party JARs are not produced/copied and runtime will throw
ClassNotFoundException; also the repo's www/ static assets are never copied.
Replace the compile step with a package step that produces a runnable artifact
(e.g., RUN mvn package with a shade/assembly plugin configured to create an
uber/fat JAR), or alternatively copy dependency JARs from the build Maven
repository and set the runtime classpath to include target/classes plus those
jars (refer to the existing RUN mvn compile and java -classpath /app
org.example.App in the diff). Finally add a COPY www/ /app/www/ (or similar) so
StaticFileHandler can find index.html/pageNotFound.html and ensure the final
image runs the fat JAR (or uses the adjusted classpath).
| <dependency> | ||
| <groupId>com.bucket4j</groupId> | ||
| <artifactId>bucket4j_jdk17-core</artifactId> | ||
| <version>${bucket4j.version}</version> | ||
| </dependency> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for bucket4j imports and usage in Java source files
echo "=== Searching for bucket4j imports and usage ==="
rg -i "bucket4j|import.*bucket" --type java -n
echo ""
echo "=== Checking pom.xml for Bucket4j references ==="
grep -n "bucket4j" pom.xml
echo ""
echo "=== Searching in all file types for bucket4j mentions ==="
rg -i "bucket4j" -n --max-count=20Repository: ithsjava25/ithsjava25-java-project-webserver-maven-java-template
Length of output: 729
Remove unused Bucket4j dependency from pom.xml.
Bucket4j is declared as a compile-scoped dependency but is not imported or used anywhere in the codebase. Either remove the dependency if it's not needed, or defer adding it until the rate-limiting functionality is actually implemented.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pom.xml` around lines 22 - 26, Remove the unused Bucket4j dependency block
(groupId com.bucket4j, artifactId bucket4j_jdk17-core, version
${bucket4j.version}) from the pom so it is not declared as a compile dependency;
if you intend to add rate-limiting later instead change it to an appropriate
optional/test scope or add a clear TODO comment linking to the feature ticket,
but do not leave the compile-scoped dependency in the pom unused.
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
| import com.fasterxml.jackson.annotation.JsonProperty; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check which Jackson packages are declared as dependencies
echo "=== pom.xml dependencies ==="
fd pom.xml --exec cat {} \; 2>/dev/null | grep -A2 -B2 -i "jackson" | head -50
echo ""
echo "=== build.gradle dependencies ==="
fd build.gradle --exec cat {} \; 2>/dev/null | grep -i "jackson" | head -30
echo ""
echo "=== All Jackson imports in Java files ==="
rg -n "import (com\.fasterxml\.jackson|tools\.jackson)" --type=java
echo ""
echo "=== List of Java files in config directory ==="
fd "\.java$" src/main/java/org/example/config --exec cat -n {} \; 2>/dev/null | head -100Repository: ithsjava25/ithsjava25-java-project-webserver-maven-java-template
Length of output: 5188
Critical: Jackson namespace mismatch — annotations won't be recognized by the mapper.
AppConfig imports from com.fasterxml.jackson (Jackson 2.x), but the project declares Jackson 3.x as a dependency (tools.jackson.core:jackson-databind:3.0.3). ConfigLoader correctly uses tools.jackson, but its ObjectMapper will not recognize @JsonProperty and @JsonIgnoreProperties annotations from the incompatible com.fasterxml.jackson package. Deserialization of AppConfig at line 48 will silently fail to apply field mappings.
Fix
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import tools.jackson.annotation.JsonIgnoreProperties;
+import tools.jackson.annotation.JsonProperty;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | |
| import com.fasterxml.jackson.annotation.JsonProperty; | |
| import tools.jackson.annotation.JsonIgnoreProperties; | |
| import tools.jackson.annotation.JsonProperty; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/config/AppConfig.java` around lines 3 - 4, The
AppConfig class is importing Jackson 2.x annotations
(com.fasterxml.jackson.annotation.*) which are incompatible with the project
Jackson 3.x dependency and the ObjectMapper in ConfigLoader; replace those
imports so AppConfig uses the Jackson 3.x annotations (tools.jackson.annotations
or the correct tools.jackson package equivalents) for `@JsonProperty` and
`@JsonIgnoreProperties` to ensure the ObjectMapper in ConfigLoader recognizes them
during deserialization. Locate AppConfig and update its import statements for
JsonProperty and JsonIgnoreProperties to the matching tools.jackson namespace
(keeping the annotation usage and field names unchanged), then run tests to
confirm mapping at the deserialization call in ConfigLoader works as expected.
| private static ObjectMapper createMapperFor(Path configPath) { | ||
| String name = configPath.getFileName().toString().toLowerCase(); | ||
|
|
||
| if (name.endsWith(".yml") || name.endsWith(".yaml")) { | ||
| return YAMLMapper.builder(new YAMLFactory()).build(); | ||
|
|
||
| } else if (name.endsWith(".json")) { | ||
| return JsonMapper.builder().build(); | ||
| } else { | ||
| return YAMLMapper.builder(new YAMLFactory()).build(); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "ConfigLoader.java" | head -5Repository: ithsjava25/ithsjava25-java-project-webserver-maven-java-template
Length of output: 160
🏁 Script executed:
find . -type f \( -name "pom.xml" -o -name "build.gradle" -o -name "build.gradle.kts" \) | head -5Repository: ithsjava25/ithsjava25-java-project-webserver-maven-java-template
Length of output: 117
🌐 Web query:
Jackson 3.x tools.jackson YAMLMapper builder API constructor
💡 Result:
In Jackson 3.x (the tools.jackson.* packages), the recommended way to create a YAML mapper is via the builder (mappers are intended to be configured immutably and then build()-ed):
import tools.jackson.dataformat.yaml.YAMLMapper;
YAMLMapper mapper = YAMLMapper.builder()
// .enable(...) / .disable(...) / .configure(...)
.build();This works because YAMLMapper exposes a YAMLMapper.Builder (since 3.0) that extends the shared MapperBuilder API, whose purpose is to construct configured mapper instances in Jackson 3.x. [1][2]
You’ll also see real-world Jackson 3 migrations switching to exactly YAMLMapper.builder().build() with tools.jackson.dataformat.yaml.YAMLMapper. [3]
Note: the YAMLMapper.Builder constructor you may see in Javadoc (Builder(YAMLMapper m)) is for the builder implementation itself; normal usage is YAMLMapper.builder() rather than instantiating the builder directly. [2]
Sources:
[1] https://javadoc.io/static/tools.jackson.core/jackson-databind/3.0.0/tools.jackson.databind/tools/jackson/databind/cfg/MapperBuilder.html
[2] https://fasterxml.github.io/jackson-dataformats-text/javadoc/yaml/2.10/com/fasterxml/jackson/dataformat/yaml/YAMLMapper.Builder.html
[3] https://www.mail-archive.com/commits%40logging.apache.org/msg52345.html
🏁 Script executed:
cat -n ./src/main/java/org/example/config/ConfigLoader.javaRepository: ithsjava25/ithsjava25-java-project-webserver-maven-java-template
Length of output: 2737
🏁 Script executed:
cat ./pom.xml | grep -A 5 -B 5 "jackson"Repository: ithsjava25/ithsjava25-java-project-webserver-maven-java-template
Length of output: 613
Fix Jackson 3.x API usage: YAMLMapper.builder() does not take YAMLFactory argument.
In Jackson 3.0.3 (tools.jackson), YAMLMapper manages its factory internally. Remove the YAMLFactory argument from both builder() calls on lines 59 and 64. Additionally, the silent fallback to YAML for unrecognized extensions (line 63–64) masks misconfiguration; throw IllegalArgumentException instead to surface the error early.
Proposed fix
private static ObjectMapper createMapperFor(Path configPath) {
String name = configPath.getFileName().toString().toLowerCase();
if (name.endsWith(".yml") || name.endsWith(".yaml")) {
- return YAMLMapper.builder(new YAMLFactory()).build();
+ return YAMLMapper.builder().build();
} else if (name.endsWith(".json")) {
return JsonMapper.builder().build();
- } else {
- return YAMLMapper.builder(new YAMLFactory()).build();
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported config file format: " + name + ". Use .yml, .yaml, or .json");
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/config/ConfigLoader.java` around lines 55 - 66,
createMapperFor: the code incorrectly calls YAMLMapper.builder(new
YAMLFactory()) (Jackson 3.x YAMLMapper.builder() takes no factory) and silently
falls back to YAML for unknown extensions; remove the YAMLFactory argument from
both YAMLMapper.builder() calls and instead of returning a YAML mapper for
unrecognized file extensions throw an IllegalArgumentException with a clear
message; update createMapperFor to use YAMLMapper.builder().build() for
.yml/.yaml, JsonMapper.builder().build() for .json, and throw new
IllegalArgumentException("Unsupported config file extension: "+ name) for other
cases.
| private void resolveTargetFile(String uri) { | ||
| if (uri.matches("/$")) { //matches(/) | ||
| this.uri = "index.html"; | ||
| } else if (uri.matches("^(?!.*\\.html$).*$")) { | ||
| this.uri = uri.concat(".html"); | ||
| } else { | ||
| this.uri = uri; | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Regex on line 30 appends .html to all non-HTML URIs, including CSS, JS, images, etc.
The negative lookahead ^(?!.*\\.html$).*$ matches any URI that doesn't end in .html. Requests for /style.css, /script.js, or /image.png would be rewritten to /style.css.html, /script.js.html, etc., resulting in 404s for all non-HTML static assets.
Consider checking whether the URI already has an extension instead:
🐛 Proposed fix — only append .html to extension-less URIs
private void resolveTargetFile(String uri) {
- if (uri.matches("/$")) { //matches(/)
+ if (uri.equals("/")) {
this.uri = "index.html";
- } else if (uri.matches("^(?!.*\\.html$).*$")) {
- this.uri = uri.concat(".html");
+ } else if (!uri.contains(".")) {
+ this.uri = uri.substring(1).concat(".html");
} else {
- this.uri = uri;
+ this.uri = uri.substring(1);
}
-
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private void resolveTargetFile(String uri) { | |
| if (uri.matches("/$")) { //matches(/) | |
| this.uri = "index.html"; | |
| } else if (uri.matches("^(?!.*\\.html$).*$")) { | |
| this.uri = uri.concat(".html"); | |
| } else { | |
| this.uri = uri; | |
| } | |
| } | |
| private void resolveTargetFile(String uri) { | |
| if (uri.equals("/")) { | |
| this.uri = "index.html"; | |
| } else if (!uri.contains(".")) { | |
| this.uri = uri.substring(1).concat(".html"); | |
| } else { | |
| this.uri = uri.substring(1); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/ConnectionHandler.java` around lines 27 - 36, The
resolveTargetFile method currently appends ".html" to any URI that doesn't end
with ".html", which incorrectly rewrites asset requests like "/style.css" to
"/style.css.html"; update resolveTargetFile to (1) treat "/" or "" as
"index.html", (2) detect if the URI already has a file extension using a regex
like "\\.[^/]+$" (hasExtension) and leave it unchanged, and (3) only append
".html" when the URI has no extension (i.e., not hasExtension). Reference:
method resolveTargetFile and the field this.uri.
| public void sendGetRequest(OutputStream outputStream, String uri) throws IOException{ | ||
| handleGetRequest(uri); | ||
|
|
||
| HttpResponseBuilder response = new HttpResponseBuilder(); | ||
| response.setStatusCode(statusCode); | ||
| response.setHeaders(Map.of("Content-Type", "text/html; charset=utf-8")); | ||
| response.setBody(fileBytes); | ||
| PrintWriter writer = new PrintWriter(outputStream, true); | ||
| writer.println(response.build()); | ||
|
|
||
| } |
There was a problem hiding this comment.
Hardcoded Content-Type: text/html for all responses.
Every served file—including potential future CSS, JS, or image assets—will be labelled text/html. Use Files.probeContentType() or a simple extension→MIME map to derive the correct type dynamically.
♻️ Proposed fix — derive Content-Type from file extension
+ private String resolveContentType(String fileName) {
+ if (fileName.endsWith(".css")) return "text/css; charset=utf-8";
+ if (fileName.endsWith(".js")) return "application/javascript; charset=utf-8";
+ if (fileName.endsWith(".png")) return "image/png";
+ if (fileName.endsWith(".jpg")) return "image/jpeg";
+ return "text/html; charset=utf-8";
+ }Then in sendGetRequest:
- response.setHeaders(Map.of("Content-Type", "text/html; charset=utf-8"));
+ response.setHeaders(Map.of("Content-Type", resolveContentType(uri)));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public void sendGetRequest(OutputStream outputStream, String uri) throws IOException{ | |
| handleGetRequest(uri); | |
| HttpResponseBuilder response = new HttpResponseBuilder(); | |
| response.setStatusCode(statusCode); | |
| response.setHeaders(Map.of("Content-Type", "text/html; charset=utf-8")); | |
| response.setBody(fileBytes); | |
| PrintWriter writer = new PrintWriter(outputStream, true); | |
| writer.println(response.build()); | |
| } | |
| private String resolveContentType(String fileName) { | |
| if (fileName.endsWith(".css")) return "text/css; charset=utf-8"; | |
| if (fileName.endsWith(".js")) return "application/javascript; charset=utf-8"; | |
| if (fileName.endsWith(".png")) return "image/png"; | |
| if (fileName.endsWith(".jpg")) return "image/jpeg"; | |
| return "text/html; charset=utf-8"; | |
| } | |
| public void sendGetRequest(OutputStream outputStream, String uri) throws IOException{ | |
| handleGetRequest(uri); | |
| HttpResponseBuilder response = new HttpResponseBuilder(); | |
| response.setStatusCode(statusCode); | |
| response.setHeaders(Map.of("Content-Type", resolveContentType(uri))); | |
| response.setBody(fileBytes); | |
| PrintWriter writer = new PrintWriter(outputStream, true); | |
| writer.println(response.build()); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/StaticFileHandler.java` around lines 44 - 54, The
response currently hardcodes "Content-Type: text/html" in sendGetRequest which
mislabels all files; update sendGetRequest to determine the MIME type
dynamically (e.g., use Files.probeContentType(path) or a small extension→MIME
map) after handleGetRequest has populated fileBytes and the file path/filename,
then call response.setHeaders(Map.of("Content-Type", mimeType)); ensure you fall
back to "application/octet-stream" if probeContentType returns null and keep
using HttpResponseBuilder, statusCode, and fileBytes as before.
| private void handleClient(Socket client) { | ||
| try (ConnectionHandler connectionHandler = new ConnectionHandler(client)) { | ||
| connectionHandler.runConnectionHandler(); | ||
| } catch (Exception e) { | ||
| throw new RuntimeException("Error handling client connection " + e); | ||
| } | ||
| } |
There was a problem hiding this comment.
Exception cause is lost — use the two-arg RuntimeException constructor.
"Error handling client connection " + e stringifies the exception, discarding the original stack trace. Pass e as the cause parameter so callers/loggers can inspect the full chain.
🐛 Proposed fix
- throw new RuntimeException("Error handling client connection " + e);
+ throw new RuntimeException("Error handling client connection", e);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private void handleClient(Socket client) { | |
| try (ConnectionHandler connectionHandler = new ConnectionHandler(client)) { | |
| connectionHandler.runConnectionHandler(); | |
| } catch (Exception e) { | |
| throw new RuntimeException("Error handling client connection " + e); | |
| } | |
| } | |
| private void handleClient(Socket client) { | |
| try (ConnectionHandler connectionHandler = new ConnectionHandler(client)) { | |
| connectionHandler.runConnectionHandler(); | |
| } catch (Exception e) { | |
| throw new RuntimeException("Error handling client connection", e); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/example/TcpServer.java` around lines 29 - 35, The catch in
handleClient currently concatenates the exception into a string and loses the
original stack trace; change the throw to use the two-argument RuntimeException
constructor so the original exception is preserved as the cause (i.e., throw new
RuntimeException("Error handling client connection", e)); update the block
around ConnectionHandler and runConnectionHandler to rethrow with the cause
rather than stringifying the exception.
| @Test | ||
| @DisplayName("Should fail when configuration file is invalid") | ||
| void invalid_yaml_fails() throws Exception { | ||
| Path configFile = tempDir.resolve("broken.yml"); | ||
| Files.writeString(configFile, "server:\n port 8080\n"); // saknar ':' efter port | ||
|
|
||
| assertThatThrownBy(() -> ConfigLoader.load(configFile)) | ||
| .isInstanceOf(IllegalStateException.class) | ||
| .hasMessageContaining("failed to read config file"); | ||
| } |
There was a problem hiding this comment.
Non-English comment on Line 121.
// saknar ':' efter port is Swedish. Use English for consistency across the codebase, e.g., // missing ':' after port.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/test/java/org/example/config/ConfigLoaderTest.java` around lines 117 -
126, Replace the Swedish inline comment in the test method invalid_yaml_fails
with English for consistency: change "// saknar ':' efter port" to an English
comment like "// missing ':' after port" in the test that writes "server:\n
port 8080\n" (located near the creation of Path configFile and the
Files.writeString call) so the test and surrounding codebase use a single
language.
| @Test | ||
| @DisplayName("Should fail when port is out of range") | ||
| void invalid_port_should_Throw_Exception () throws Exception { | ||
| Path configFile = tempDir.resolve("application.yml"); | ||
|
|
||
| Files.writeString(configFile, """ | ||
| server: | ||
| port: 70000 | ||
| """); | ||
|
|
||
| assertThatThrownBy(() -> ConfigLoader.loadOnce(configFile)) | ||
| .isInstanceOf(IllegalArgumentException.class).hasMessageContaining("Invalid port number"); | ||
| } |
There was a problem hiding this comment.
Fix method name formatting and casing.
Line 130: invalid_port_should_Throw_Exception () has excessive whitespace before the parentheses and mixes camelCase (Throw, Exception) into an otherwise snake_case convention. Suggest:
- void invalid_port_should_Throw_Exception () throws Exception {
+ void invalid_port_should_throw_exception() throws Exception {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/test/java/org/example/config/ConfigLoaderTest.java` around lines 128 -
140, Rename the test method invalid_port_should_Throw_Exception in
ConfigLoaderTest to follow consistent snake_case and remove the extra spaces
before the parentheses; for example, change it to
invalid_port_should_throw_exception() (keep the `@Test` and `@DisplayName`
unchanged) so the method name uses lowercase for "throw" and "exception" and has
no trailing whitespace before "()".
| @Test | ||
| void TestHttpParserForHeaders() throws IOException { | ||
| String testInput = "GET /index.html HTTP/1.1\r\nHost: localhost\r\nContent-Type: text/plain\r\nUser-Agent: JUnit5\r\n\r\n"; | ||
| InputStream in = new ByteArrayInputStream(testInput.getBytes(StandardCharsets.UTF_8)); | ||
|
|
||
| httpParser.setReader(in); | ||
| httpParser.parseHttp(); | ||
|
|
||
| assertNotNull(httpParser.getHeadersMap()); | ||
| assertThat(httpParser.getHeadersMap().size()).isEqualTo(3); | ||
| assertThat(httpParser.getHeadersMap().get("Host")).contains("localhost"); | ||
| assertThat(httpParser.getHeadersMap().get("Content-Type")).contains("text/plain"); | ||
| assertThat(httpParser.getHeadersMap().get("User-Agent")).contains("JUnit5"); | ||
| } |
There was a problem hiding this comment.
Test feeds request line through the header parser — works by accident.
TestHttpParserForHeaders passes the full HTTP request (GET /index.html HTTP/1.1\r\n...) directly to parseHttp() without first calling parseRequest(). The request line is silently discarded because it contains no colon. The test passes, but it doesn't reflect real usage and is fragile — if parsing logic changes to be stricter, this test will break for the wrong reason.
Either call parseRequest() first (to consume the request line), or strip the request line from the test input.
Proposed fix (strip request line from input)
- String testInput = "GET /index.html HTTP/1.1\r\nHost: localhost\r\nContent-Type: text/plain\r\nUser-Agent: JUnit5\r\n\r\n";
+ String testInput = "Host: localhost\r\nContent-Type: text/plain\r\nUser-Agent: JUnit5\r\n\r\n";Also: the method name TestHttpParserForHeaders uses PascalCase — Java convention for test methods is camelCase (e.g., testHttpParserForHeaders).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Test | |
| void TestHttpParserForHeaders() throws IOException { | |
| String testInput = "GET /index.html HTTP/1.1\r\nHost: localhost\r\nContent-Type: text/plain\r\nUser-Agent: JUnit5\r\n\r\n"; | |
| InputStream in = new ByteArrayInputStream(testInput.getBytes(StandardCharsets.UTF_8)); | |
| httpParser.setReader(in); | |
| httpParser.parseHttp(); | |
| assertNotNull(httpParser.getHeadersMap()); | |
| assertThat(httpParser.getHeadersMap().size()).isEqualTo(3); | |
| assertThat(httpParser.getHeadersMap().get("Host")).contains("localhost"); | |
| assertThat(httpParser.getHeadersMap().get("Content-Type")).contains("text/plain"); | |
| assertThat(httpParser.getHeadersMap().get("User-Agent")).contains("JUnit5"); | |
| } | |
| `@Test` | |
| void TestHttpParserForHeaders() throws IOException { | |
| String testInput = "Host: localhost\r\nContent-Type: text/plain\r\nUser-Agent: JUnit5\r\n\r\n"; | |
| InputStream in = new ByteArrayInputStream(testInput.getBytes(StandardCharsets.UTF_8)); | |
| httpParser.setReader(in); | |
| httpParser.parseHttp(); | |
| assertNotNull(httpParser.getHeadersMap()); | |
| assertThat(httpParser.getHeadersMap().size()).isEqualTo(3); | |
| assertThat(httpParser.getHeadersMap().get("Host")).contains("localhost"); | |
| assertThat(httpParser.getHeadersMap().get("Content-Type")).contains("text/plain"); | |
| assertThat(httpParser.getHeadersMap().get("User-Agent")).contains("JUnit5"); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/test/java/org/example/httpparser/HttpParserTest.java` around lines 15 -
28, The test TestHttpParserForHeaders currently feeds a full HTTP request
(including the request line) into httpParser.parseHttp(), which lets the request
line be ignored by accident; either change the test to call
httpParser.parseRequest() before httpParser.parseHttp() to consume the request
line, or alter testInput to start at the headers only (remove the "GET /...
HTTP/1.1\r\n" line) so parseHttp() receives only header lines; also rename the
test method to camelCase (e.g., testHttpParserForHeaders) to follow Java test
naming conventions.
* Implements configuration loading and ensures that ConfigLoader is invoked during application startup (App.main). * Minor formating.
* Bump org.apache.maven.plugins:maven-dependency-plugin (#6) Bumps the maven-deps group with 1 update: [org.apache.maven.plugins:maven-dependency-plugin](https://github.com/apache/maven-dependency-plugin). Updates `org.apache.maven.plugins:maven-dependency-plugin` from 3.9.0 to 3.10.0 - [Release notes](https://github.com/apache/maven-dependency-plugin/releases) - [Commits](apache/maven-dependency-plugin@maven-dependency-plugin-3.9.0...maven-dependency-plugin-3.10.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-dependency-plugin dependency-version: 3.10.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: maven-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump org.junit.jupiter:junit-jupiter in the maven-deps group (#13) Bumps the maven-deps group with 1 update: [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit-framework). Updates `org.junit.jupiter:junit-jupiter` from 6.0.2 to 6.0.3 - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](junit-team/junit-framework@r6.0.2...r6.0.3) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter dependency-version: 6.0.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: maven-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* initial commit, added interfaces Filter and FilterChain * Added HttpRequest class, groups together all information about a request that the server needs and easier to handle by future filters * added interfaces Filter and FilterChain with Java Servlet Filter architecture. * added FilterChainImpl * Corrected imports from JDKS HttpRequest, to projects HttpRequest class * Changed, params for FilterChain * Updated HttpRequest with attributes, * Revert "Updated HttpRequest with attributes," This reverts commit 0fd490e.
* Added MIME detector class and test class * Added logic for Mime detector class * Added Unit tests * Added logic in HttpResponseBuilder and tests to try it out * Solves duplicate header issue * Removed a file for another issue * Changed hashmap to Treemap per code rabbits suggestion * Corrected logic error that was failing tests as per P2P review * Added more reason phrases and unit testing, also applied code rabbits suggestions! * Added changes to Responsebuilder to make merging easier * Changed back to earlier commit to hande byte corruption new PR * Added StaticFileHandler from main * Added staticFileHandler with binary-safe writing * Fix: Normalize Content-Type charset to uppercase UTF-8 Changed 'charset=utf-8' to 'charset=UTF-8' in StaticFileHandlerTest to match MimeTypeDetector output and align with HTTP RFC standard. Uppercase UTF-8 is the correct format per RFC 2616/7231.
* Update Dockerfile Dockerfiles now copies www folder aswell * Added building and copying of dependency jar files * Fix dependency path in Dockerfile and update classpath in ENTRYPOINT configuration. * Fixed typo in Entrypoint configuration * Expose port 8080 in Dockerfile and changed appuser to come before ENTRYPOINT configuration. * Adjust Dockerfile paths for classes and dependencies, update `COPY` targets accordingly.
* Added comprehensive README.MD * Added formatting recommendations, clearer info
* Prevent path traversal and sanitize URI in StaticFileHandler. * Add test for path traversal protection and support 403 responses. * Add tests for URI sanitization and handling of encoded/special URLs. * Add test for null byte injection prevention in StaticFileHandler * Improve StaticFileHandler path traversal handling and test coverage * Improve URI sanitization and add test for 404 response handling in StaticFileHandler
* Resolve port: CLI > config > default * Wire port resolution to AppConfig/ConfigLoader and update docs/tests * Update PortConfigurationGuide.md * Update PortConfigurationGuide.md * Remove ServerPortResolver; use ConfigLoader for port * Update PortConfigurationGuide.md * Update PortConfigurationGuide.md * may be done
* refactor: add predefined HTTP status code constants to HttpResponseBuilder * refactor: use status code constants in StaticFileHandler * test: refactor StaticFileHandlerTest to use status code constants * test: refactor HttpResponseBuilderTest to use status code constants and explicit assertations
…e-Filter-Pipeline-(Global-+-Per‑Route-Filters)
* Fix path in Dockerfile for `www` directory copy operation * Correct relative path for `www` directory in Dockerfile copy operation
* Add IpFilter and corresponding test skeleton Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Extend IpFilter with blocklist mode and add unit tests Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Enhance IpFilter to return 403 for blocked IPs and add corresponding test case Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Extend IpFilter with allowlist mode and add corresponding unit tests Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Refactor IpFilter to support both allowlist and blocklist modes, update logic, and add unit tests for allowlist mode Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Handle missing client IP in IpFilter, return 400 response, and add corresponding test case Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Refactor tests in `IpFilterTest` to use `assertAll` for improved assertion grouping and readability Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Refactor `IpFilter` to use `HttpResponse` instead of `HttpResponseBuilder` and update tests accordingly Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Add unit tests for edge cases in `IpFilter` allowlist and blocklist modes Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Refactor `IpFilter` and tests to use `HttpResponseBuilder` instead of `HttpResponse` Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Handle empty client IP in `IpFilter`, return 400 response, and add corresponding test case Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Add comments to `IpFilter` empty methods, clarifying no action is required Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Fix typos in comments within `IpFilterTest` Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Add Javadoc comments to `IpFilter` and `IpFilterTest` for improved clarity and documentation * Refactor `IpFilter` to use thread-safe collections and normalize IP addresses * Make `mode` field in `IpFilter` volatile to ensure thread safety * Ensure UTF-8 encoding for response string in `IpFilterTest` and add attribute management to `HttpRequest` * Ensure UTF-8 encoding for response string in `IpFilterTest` and add attribute management to `HttpRequest` Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Integrate IP filtering into `ConnectionHandler` and update configuration to support filter settings Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Refactor IP filter check in `ConnectionHandler` to use `Boolean.TRUE.equals` for improved null safety Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * Validate null inputs for allowed/blocked IPs in `IpFilter`, enhance test coverage, and fix typographical error in comments Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> * refactor: extract applyFilters() method using FilterChainImpl Co-authored-by: Andreas Kaiberger <andreas.kaiberger@gmail.com> * refactor: cache filter list at construction time Co-authored-by: Andreas Kaiberger <andreas.kaiberger@gmail.com> * refactor: cache filter list at construction time Co-authored-by: Andreas Kaiberger <andreas.kaiberger@gmail.com> * test: verify GPG signing * Replace hardcoded status codes in `IpFilter` and `ConnectionHandler` with constants from `HttpResponseBuilder` for better maintainability Co-authored-by: Rickard Ankar <rickard.ankar@iths.se> --------- Co-authored-by: Rickard Ankar <rickard.ankar@iths.se>
* Re-commit LocaleFilter + tests to clean branch for PR * Update LocaleFilter to handle quality weights and improve javadoc * Fix: rename test method to reflect actual headers scenario * Fix: ensure resolveLocale never returns empty string; strip quality weights
* Add LoggFilter and update HttpResponseBuilder with method getStatusCode() in order to use it in logging * Refactor code * Change magic number to constans in httpresponsebuilder * Add LoggFilter and update HttpResponseBuilder with method getStatusCode() in order to use it in logging * Refactor code * Added tests -> LoggingFilterTest * Trigger rebuild * Git is hallucinating. Trying to fix problem * Final version (hopefully)
* Add error handling for when client request fail -> return internal server error 500 * Create ConnectionFactory in order to be able to test the TcpServer. Implement factory into TcpServer and App. * Create test to check whether handleClient throws internal server error when there is an exception * Modify test * Update logic -> seperate handleClient and processRequest in order to have open socket when handling internal server error * Exchange Printwriter to Outputstream in handleInternalServerError * Add error handling for when client request fail -> return internal server error 500 * Create ConnectionFactory in order to be able to test the TcpServer. Implement factory into TcpServer and App. * Create test to check whether handleClient throws internal server error when there is an exception * Modify test * Update logic -> seperate handleClient and processRequest in order to have open socket when handling internal server error * Exchange Printwriter to Outputstream in handleInternalServerError * Rebase main onto bransch. Update App -> add Connectionhandler::new Also update handleInternalServerError
* Re-commit LocaleFilterWithCookie + tests to clean branch for PR * Fix: make header lookups case-insensitive to avoid incorrect default locale fallback.
* Remove .html concat to support all files * Add test for jpg handling in ConnectionHandler (TDD:RED), related to #69 * Make ConnectionHandler testable with webroot parameter * Fix: Strip leading slash from URI to prevent absolute path (coderabbit)
…e-Filter-Pipeline-(Global-+-Per‑Route-Filters)
Summary by CodeRabbit