Skip to content

Implement static file handler (foundation for #18) - #36

Merged
fmazmz merged 13 commits into
mainfrom
feature/18-static-file-handler
Feb 12, 2026
Merged

Implement static file handler (foundation for #18)#36
fmazmz merged 13 commits into
mainfrom
feature/18-static-file-handler

Conversation

@annikaholmqvist94

@annikaholmqvist94 annikaholmqvist94 commented Feb 11, 2026

Copy link
Copy Markdown

Changes

Foundation work for #18 - GET handling for static files

What's Added

Core Logic

  • StaticFileHandler - Serves files from /resources/static/
  • MimeTypeResolver - Maps file extensions to Content-Type headers

Security Features

  • Path validation prevents directory traversal attacks
  • Blocks suspicious patterns: ../, //, \
  • Returns 403 Forbidden for security violations
  • Comprehensive security tests included

Testing (35+ test cases)

MimeTypeResolverTest (15 tests):

  • HTML, CSS, JS, image formats
  • Edge cases: null, empty, unknown extensions
  • Case-insensitive extension handling
  • Path handling with directories

StaticFileHandlerTest (20+ tests):

  • Successful file serving (200 OK)
  • Correct Content-Type headers
  • 404 for missing files
  • 403 for path traversal attempts
  • 405 for non-GET methods
  • Root path serves index.html

Example Static Files

  • index.html - Demo homepage with modern design
  • styles.css - Professional gradient styling
  • app.js - JavaScript functionality demo

File Structure

src/main/java/org/juv25d/handler/
├── MimeTypeResolver.java          (67 lines)
└── StaticFileHandler.java         (198 lines)

src/main/resources/static/
├── index.html                      (40 lines)
├── css/
│   └── styles.css                  (125 lines)
└── js/
    └── app.js                      (10 lines)

src/test/java/org/juv25d/handler/
├── MimeTypeResolverTest.java      (88 lines)
└── StaticFileHandlerTest.java     (147 lines)

Integration Status

⏸️ Not yet integrated with Server/ConnectionHandler

This PR contains all core logic but avoids modifying
Server/SocketServer/ConnectionHandler to prevent conflicts with PR #28.

Integration Plan (after PR #28 merges):

  1. Rebase this branch on main
  2. Add integration code in ConnectionHandler:
if (request.method().equalsIgnoreCase("GET")) {
    HttpResponse response = StaticFileHandler.handle(request);
    HttpResponseWriter.write(socket.getOutputStream(), response);
}
  1. Request final review
  2. Merge

Architecture

Architecture decisions documented in ADR-001 (see PR #35 ).

Dependencies

Review Focus

  1. Security validation logic - Is path traversal prevention robust?
  2. MIME type mappings - Are common file types covered?
  3. Test coverage - Are edge cases properly tested?
  4. Code quality - Clear naming and documentation?
  5. Architecture - Does it follow ADR-001 decisions?

Notes

  • Static files will be bundled in JAR (SpringBoot style)
  • Currently returns 405 for POST/PUT/DELETE (only GET supported)
  • 404 error page includes styled HTML response
  • Logging integrated with existing ServerLogging class

Summary by CodeRabbit

  • New Features

    • Static file serving with automatic MIME type detection and UTF-8 charset for text types
    • Serves index at root, proper responses for missing files (404), disallowed methods (405), and forbidden paths (403)
    • New landing page with styles and client-side script
    • Plugin integration to enable static content serving in the pipeline
  • Tests

    • Added comprehensive tests for static file serving behavior and MIME type resolution

Core Implementation:
- Add StaticFileHandler for serving files from /resources/static/
- Add MimeTypeResolver for Content-Type detection
- Add security validation to prevent path traversal attacks

Testing:
- Add MimeTypeResolverTest (15 test cases)
- Add StaticFileHandlerTest (20+ test cases)
- All tests passing

Example Files:
- Add index.html demo page with gradient styling
- Add styles.css for professional styling
- Add app.js for JavaScript functionality demo

Note: Integration with Server/ConnectionHandler will be added
after PR #28 merges to avoid conflicts.

Foundation work for #18
@annikaholmqvist94 annikaholmqvist94 added the enhancement New feature or request label Feb 11, 2026
@coderabbitai

coderabbitai Bot commented Feb 11, 2026

Copy link
Copy Markdown

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds static file serving: a MIME type resolver, a GET-only static file handler with path-traversal protection, a Plugin adapter, static web assets (HTML/CSS/JS), and unit tests for MIME resolution and static serving behaviors.

Changes

Cohort / File(s) Summary
MIME Type Resolver
src/main/java/org/juv25d/handler/MimeTypeResolver.java
New utility mapping file extensions to MIME types with public static String getMimeType(String filename); defaults to application/octet-stream.
Static File Handler
src/main/java/org/juv25d/handler/StaticFileHandler.java
New GET-only static asset handler serving resources under /static/, path-traversal validation, resource loading, MIME detection (adds charset for text types), and HTML 403/404/405 responses.
Plugin Integration
src/main/java/org/juv25d/plugin/StaticFilesPlugin.java
New thin Plugin adapter that delegates to StaticFileHandler and copies resulting response fields into pipeline response.
Static Website Assets
src/main/resources/static/index.html, src/main/resources/static/css/styles.css, src/main/resources/static/js/app.js
Adds landing page, responsive CSS, and a small client-side script for diagnostics; referenced by the static handler.
Unit Tests
src/test/java/org/juv25d/handler/MimeTypeResolverTest.java, src/test/java/org/juv25d/handler/StaticFileHandlerTest.java
New tests covering MIME mappings, case-insensitivity, null/empty handling, and static file handler behaviors (200/404/403/405, headers, index serving).
App wiring
src/main/java/org/juv25d/App.java
Replaces HelloPlugin with StaticFilesPlugin in pipeline initialization (import and pipeline.setPlugin change).

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Plugin as StaticFilesPlugin
    participant Handler as StaticFileHandler
    participant Loader as ResourceLoader
    participant Resolver as MimeTypeResolver
    participant Response as HttpResponse

    Client->>Plugin: HTTP GET /about.html
    Plugin->>Handler: handle(request)
    Handler->>Handler: isPathSafe(path)
    alt path unsafe
        Handler->>Response: 403 Forbidden
    else path safe
        Handler->>Loader: loadResource("/static/about.html")
        alt resource found
            Loader-->>Handler: bytes
            Handler->>Resolver: getMimeType("about.html")
            Resolver-->>Handler: "text/html"
            Handler->>Response: 200 OK + Content-Type
        else resource missing
            Loader-->>Handler: null
            Handler->>Response: 404 Not Found
        end
    end
    Handler-->>Plugin: HttpResponse
    Plugin-->>Client: HttpResponse
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • ithsjava25/project-webserver-juv25d issue 54: App.java was updated to wire StaticFilesPlugin into the pipeline — this PR provides that plugin and handler.

Possibly related PRs

  • PR ithsjava25/project-webserver-juv25d PR 35: Implements the static-file-serving components (MimeTypeResolver, StaticFileHandler) described in ADR-001.

Suggested reviewers

  • TatjanaTrajkovic
  • LinusWestling

Poem

🐰 I hop through folders, soft and spry,
Serving pages under open sky,
Types I know, bad paths I thwart,
Bytes delivered, a tiny art,
A rabbit's route to serve your site.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Implement static file handler (foundation for #18)' accurately summarizes the main change in the changeset—adding a new static file handler utility with supporting components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/18-static-file-handler

No actionable comments were generated in the recent review. 🎉

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@TatjanaTrajkovic
TatjanaTrajkovic self-requested a review February 11, 2026 11:25
@TatjanaTrajkovic

Copy link
Copy Markdown

I see only empty classes. Are you still working on that?

… ADR process for the team

                                     - Add TEMPLATE for writing future ADRs
                                      - Add ADR-001 documenting static file serving architecture

                                      Closes #16
Comment thread src/main/java/org/juv25d/handler/StaticFileHandler.java
@LinusWestling

Copy link
Copy Markdown

Gotta change folder structure for this, might be issues when compiling with the files inside /resources/ folder direction
/Martin

@TatjanaTrajkovic TatjanaTrajkovic left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error responses include charset=utf-8, but successful 200 responses do not.
Since the body is encoded using UTF-8, we should append ; charset=utf-8 to text-based MIME types for consistency and better HTTP correctness.

Comment thread src/main/java/org/juv25d/handler/StaticFileHandler.java
@bamsemats
bamsemats self-requested a review February 11, 2026 13:46
LinusWestling
LinusWestling previously approved these changes Feb 11, 2026

@LinusWestling LinusWestling left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should work to run, can do improvements later if needed :)

@fmazmz fmazmz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before merging this PR, we should merge the latest changes from Main and integrate the static fileHandler to the server. That way we dont need a new PR to actually use the changes.

An example / suggestion would be to update HelloPlugin.java or create a new Plugin that creates an HttpResponse from using the StaticFileHandler.handle().

For example:

import org.juv25d.handler.StaticFileHandler;
import org.juv25d.http.HttpRequest;
import org.juv25d.http.HttpResponse;

import java.io.IOException;
import java.util.HashMap;

public class StaticFilesPlugin implements Plugin {

    @Override
    public void handle(HttpRequest req, HttpResponse res) throws IOException {
        HttpResponse staticRes = StaticFileHandler.handle(req);

        res.setStatus(staticRes.statusCode(), staticRes.statusText());
        res.setHeaders(new HashMap<>(staticRes.headers()));
        res.setBody(staticRes.body());
    }
}

then add it to the pipeline in App.java:

        pipeline.setPlugin(new StaticFilesPlugin());

@fmazmz fmazmz linked an issue Feb 11, 2026 that may be closed by this pull request
johanbriger and others added 5 commits February 11, 2026 19:02
…edicated ConnectionHandler. (#28)

* Rename SocketServer to Server
Move HTTP request handling logic to a dedicated ConnectionHandler.

* Convert createSocket to an instance method named start().

* Replace console prints with Logger in ConnectionHandler

* Fixed typo in ConnectionHandler

* refactor Server and ConnectionHandler:
- Inject Logger inside of constructor
- Inject handlerFactory into the Server that handles creation of a new ConnectionHandler on each request
- Remove HttpParser from Server as it is not handling the parsing of a request

* accept ConnectionHandlerFactory and not a specific implementation

* normalize handlerFactory name

---------

Co-authored-by: WHITEROSE <firasmoussa60@gmail.com>
…tests. (#34)

* Add testing for ServerLogging.java. Configure ServerLogging.java to improve its testability.

* Update logger_shouldNotAddDuplicateHandlers test to properly test non-inclusion of duplicates.

* Update ServerLogging.java to guard against invalid level string entries in configure method (logger.parse => setLevel).
* http parser

* Bunny fixes. (only using input stream to recieve requests)

* Bunny review improvements

* Improved http parser ReadLine helper method to eliminate dependency on mark() and reset(). Implemented handleClient() using socket as a try-with-resources to avoid memory leakage in case of exception thrown by httpparser-methods.

* NumberFormatException fix on line 53 -> 60

* Added foundation for Filters and Plugins. Added FilterChain to use created filters, and a Pipeline class to handle the workflow (Client → Filters → Plugin → Response → Client). Modified SocketServer handleClient() to use FilterChain. Added example code in App.java for Pipeline usage. TODO: Initialize HttpResponse class

* Fixing build fail

* add init and destroy to filter interface

* add methods to pipeline class

* Add servlet-style filter pipeline with lifecycle support

* add documentation about temporary server impl

* test: verify filters execute in order and plugin is called last

* test: add coverage for filter blocking, lifecycle init, and empty pipeline

* add loggingfiltertest

* fix: construct HttpResponse with required arguments

* fix: construct HttpResponse with required arguments

* Update FilterChainImpl tests to use full HttpResponse constructor to ensure compatibility with future implementations

* remove duplicated class

---------

Co-authored-by: Kristina M <kristina0x7@gmail.com>
Co-authored-by: WHITEROSE <firasmoussa60@gmail.com>
@annikaholmqvist94

Copy link
Copy Markdown
Author

Integration Complete

Hey @LinusWestling @fmazmz @TatjanaTrajkovic

Following @fmazmz's suggestion, I've integrated StaticFileHandler directly in this PR. I made some architectural decisions I'd like your feedback on.


Files Changed

New:

  • StaticFilesPlugin.java - Integrates StaticFileHandler with Pipeline

Modified:

  • App.java - Use StaticFilesPlugin instead of HelloPlugin
  • HttpResponse.java - Removed final, added setters
  • StaticFileHandler.java - Fixed import (HttpRequest from org.juv25d.http)
  • StaticFileHandlerTest.java - Fixed import

Architecture: Pipeline Integration

ADR-001 suggested direct integration:

if (request.method().equals("GET")) {
    HttpResponse response = StaticFileHandler.handle(request);
    HttpResponseWriter.write(outputStream, response);
}

I used the existing Pipeline pattern instead:

Pipeline → LoggingFilter → StaticFilesPlugin → StaticFileHandler

Why: Follows existing architecture, more extensible, same as Express/Spring/Django middleware.

Question:Is this acceptable, or prefer direct integration?


Breaking Change: HttpResponse Mutability

Removed final from fields and added setters:

// Before
private final int statusCode;

// After  
private int statusCode;
public void setStatusCode(int statusCode) { ... }

Why: Pipeline pattern requires modifying response object. Each request gets own instance (no sharing), single-threaded (no concurrency).
Suggestion: If you prefer immutability, I can create a separate MutableHttpResponse wrapper class instead?

Feedback Needed

  1. Pipeline integration- OK or prefer direct approach?
  2. HttpResponse mutability- Acceptable or create wrapper?

Thanks

Closes #18

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🤖 Fix all issues with AI agents
In `@src/main/java/org/juv25d/ConnectionHandler.java`:
- Around line 27-30: The run() method in ConnectionHandler opens a socket
without a read timeout, allowing slow/malicious clients to hold connections; set
a socket read timeout (e.g., call socket.setSoTimeout(30_000) on the socket
variable before obtaining/using the input stream or before any blocking reads)
and handle the resulting SocketTimeoutException in run() to close the connection
and log/cleanup appropriately; update references in ConnectionHandler.run()
around socket.getInputStream()/socket.getOutputStream() and ensure proper
resource cleanup on timeout.
- Around line 27-49: The run() method currently logs and closes the socket if
httpParser.parse(in) throws, so clients get no HTTP response; update run() to
catch parse failures (exceptions from httpParser.parse(in)) and construct/send a
400 Bad Request HttpResponse via HttpResponseWriter.write(out, response) before
closing the socket, ensuring you obtain the socket output stream (out) even on
parse errors and handle potential IOExceptions from the write; keep the existing
pipeline.createChain()/chain.doFilter path unchanged for successful parses and
reuse HttpResponse class to build the 400 response.

In `@src/main/java/org/juv25d/filter/README.md`:
- Line 27: The README line incorrectly references the plural directory
"src/main/java/.../filters/"; update that line to the correct package path
"src/main/java/org/juv25d/filter/" (singular) and ensure any subsequent
instruction text refers to the package name "filter" and the example package
org.juv25d.filter so readers can create the class in the proper directory.
- Line 32: Update the README entry that registers the filter: replace the
incorrect example path string "src/org.example/App.java" with the actual project
path "src/main/java/org/juv25d/App.java" so the README points to the real
App.java location; edit the README.md line under "Register your filter" to use
the corrected path.

In `@src/main/java/org/juv25d/handler/StaticFileHandler.java`:
- Around line 64-69: The MIME-type charset logic in StaticFileHandler misses XML
types so variable mimeType may lack "; charset=utf-8" for application/xml or
application/xhtml+xml; update the conditional that checks mimeType (the block
where mimeType.startsWith("text/") ... mimeType += "; charset=utf-8";) to also
match XML types (e.g., mimeType.contains("xml") or specifically check for
"application/xml" and "application/xhtml+xml"), and ensure you guard against
appending a charset twice by first verifying mimeType does not already contain a
"charset=" token.
- Around line 151-180: The 404 page in createNotFoundResponse reflects the
untrusted path into HTML causing a reflected XSS; fix by HTML-escaping the path
before embedding it. Add a private helper escapeHtml(String) that replaces &, <,
>, " and ' with their HTML entities (e.g. &amp;, &lt;, &gt;, &quot;, &#39;) and
call it when building the html string (use escapeHtml(path) in place of path).
Ensure the escaped string is used in the formatted HTML passed to the
HttpResponse.
- Around line 89-105: The isPathSafe method is vulnerable to URL-encoded
traversal (e.g., %2e%2e) because it checks only literal patterns; update the
code that calls isPathSafe (or isPathSafe itself) to first URL-decode the
incoming request path (using java.net.URLDecoder with UTF-8) and then validate,
and additionally normalize the decoded path (e.g., via
java.nio.file.Paths.get(decoded).normalize()) before checking for traversal or
absolute escapes; ensure isPathSafe still enforces rejects for "..", "\\" and
double slashes on the normalized/decoded value and that the final check confirms
the normalized path starts with "/" (or is within the intended static root) to
prevent escape after decoding.

In `@src/main/java/org/juv25d/http/HttpResponse.java`:
- Around line 43-45: setStatusText currently assigns statusText without null
checking whereas the constructor uses Objects.requireNonNull; update
setStatusText in class HttpResponse to validate its statusText parameter with
Objects.requireNonNull (using the same error message as the constructor) before
assigning to this.statusText to keep behavior consistent.
- Around line 47-49: The setHeaders method currently assigns a mutable
LinkedHashMap to the headers field, breaking the immutability established by the
constructor and exposing internal state via headers(); update setHeaders to
store an unmodifiable map (e.g., Collections.unmodifiableMap(new
LinkedHashMap<>(headers))) so it matches the constructor behavior, or
alternatively change headers() to return a defensive copy; modify the setHeaders
implementation (method name setHeaders and the headers() accessor/headers field)
accordingly so callers cannot mutate the internal headers map.

In `@src/main/java/org/juv25d/Pipeline.java`:
- Around line 23-25: createChain() can NPE when plugin is null; add a guard at
the start of createChain() to validate that the field plugin (set by setPlugin)
is non-null and throw a clear IllegalStateException (or similar) if it hasn't
been set, then construct and return the chain; also change the return type from
FilterChainImpl to the FilterChain interface to reduce coupling so the method
signature returns FilterChain while still instantiating new
FilterChainImpl(List.copyOf(filters), plugin).

In `@src/main/java/org/juv25d/plugin/README.md`:
- Line 22: The README has an inconsistent directory path: it shows
"src/main/java/.../plugins/" (plural) but the actual package is
org.juv25d.plugin (singular); update the README line to use the correct
directory/package name (e.g., "src/main/java/.../plugin/") so it matches the
package org.juv25d.plugin and any references to creating new classes in that
package.
- Around line 30-45: The README example for RouterPlugin uses non-existent
HttpResponse methods; update the RouterPlugin.handle(HttpRequest req,
HttpResponse res) example to call HttpResponse.setStatusCode(int) instead of
setStatus and to call HttpResponse.setBody(byte[]) by converting strings to
bytes (e.g., "Welcome!".getBytes(StandardCharsets.UTF_8)) or otherwise providing
a byte[]; reference the RouterPlugin class, Plugin interface, handle method, and
HttpResponse.setStatusCode / HttpResponse.setBody(byte[]) when making the change
and add the necessary import for StandardCharsets if used.
- Around line 48-52: Update the README plugin registration example to reference
the correct App class/package (org.juv25d.App) and fix the plugin instantiation:
call Pipeline.setPlugin with a proper HelloPlugin constructor invocation (use
new HelloPlugin()) instead of new HelloPlugin; ensure the code block shows
Pipeline pipeline = new Pipeline(); pipeline.setPlugin(new HelloPlugin()); to
match the actual App class and project package.

In `@src/test/java/org/juv25d/filter/LoggingFilterTest.java`:
- Line 45: Replace the Java language assertion in LoggingFilterTest with a JUnit
assertion so the test fails reliably; locate the line asserting
output.contains("GET /test") and change it to use JUnit (e.g.,
Assertions.assertTrue or Assert.assertTrue depending on your test framework) and
add a helpful failure message, ensuring the appropriate org.junit import is
present (class: LoggingFilterTest, method containing the assertion).

In `@src/test/java/org/juv25d/handler/StaticFileHandlerTest.java`:
- Around line 22-28: Update the assertions in StaticFileHandlerTest to expect
the charset suffix appended by StaticFileHandler.handle: change checks like
assertThat(response.headers()).containsEntry("Content-Type", "text/html") to
assert the header value includes "; charset=utf-8" (e.g., "text/html;
charset=utf-8") for the HTML, CSS, JS, and root-path tests (refer to the test
methods shouldReturnCorrectContentTypeForHtml and the corresponding
CSS/JS/root-path test methods) so they match the behavior of
StaticFileHandler.handle().
🧹 Nitpick comments (18)
src/main/resources/static/css/styles.css (2)

23-23: Use modern CSS color function notation.

Stylelint flags the legacy rgba() notation. Modern CSS uses rgb() with a slash-separated alpha value and percentage notation.

♻️ Proposed fix
-    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+    box-shadow: 0 10px 30px rgb(0 0 0 / 20%);

110-110: Use modern media feature range notation.

Stylelint recommends context-based range notation over the legacy max-width prefix syntax.

♻️ Proposed fix
-@media (max-width: 768px) {
+@media (width <= 768px) {
src/main/java/org/juv25d/logging/ServerLogging.java (1)

30-36: Inconsistent indentation inside try block.

Line 31 (Level level = ...) is not indented relative to the try block, while lines 33-35 in the catch are properly indented.

♻️ Proposed fix
         try {
-        Level level = Level.parse(levelName.toUpperCase());
-        logger.setLevel(level);
+            Level level = Level.parse(levelName.toUpperCase());
+            logger.setLevel(level);
         } catch (IllegalArgumentException e) {
src/main/java/org/juv25d/App.java (1)

19-23: Remove commented-out code; use documentation or configuration instead.

The commented-out HelloPlugin option adds noise. If you want to support swappable plugins, consider a configuration mechanism or document it in the README rather than leaving dead code.

src/main/java/org/juv25d/Server.java (3)

20-33: No graceful shutdown mechanism.

The while (true) loop with no interrupt/shutdown check means the server can only be stopped by killing the process. Consider adding a volatile boolean running flag and a stop() method that closes the ServerSocket to break out of the accept() call. This becomes important for testability and clean shutdown.


9-9: Hardcoded port.

Consider making the port configurable (constructor parameter or system property) to support different environments and avoid conflicts during testing.


21-21: Unencrypted server socket.

Static analysis flagged this as using a plain ServerSocket (cleartext). This is fine for local development, but keep in mind that for production use, you'd want TLS (e.g., SSLServerSocket) or a reverse proxy in front.

src/main/java/org/juv25d/plugin/HelloPlugin.java (1)

10-13: Placeholder plugin produces no response.

If this plugin is activated (as shown in the commented-out code in App.java), it will leave the HttpResponse unmodified, resulting in whatever default state it was constructed with. Consider setting a minimal response (e.g., 200 with "Hello, World!") to make it useful for testing the pipeline.

src/main/java/org/juv25d/filter/FilterChainImpl.java (2)

16-18: Store a defensive copy of the filters list.

The constructor stores the caller's list reference directly. If the caller mutates that list after constructing the chain, behavior becomes unpredictable. A defensive copy is a simple safeguard.

♻️ Proposed fix
     public FilterChainImpl(List<Filter> filters, Plugin plugin) {
-        this.filters = filters;
+        this.filters = List.copyOf(filters);
         this.plugin = plugin;
     }

22-29: Consider guarding against a null plugin.

If plugin is null (e.g., no plugin set on the pipeline), line 27 will throw an NPE when all filters have been processed. Either add a null check or document that plugin must not be null.

src/main/java/org/juv25d/plugin/Plugin.java (1)

1-10: Clean interface definition.

Minor nit: extra space before the parenthesis on Line 9 (handle (HttpRequest...handle(HttpRequest...).

src/main/java/org/juv25d/filter/LoggingFilter.java (1)

11-14: Use ServerLogging.getLogger() instead of System.out.println.

The rest of the codebase uses java.util.logging.Logger via ServerLogging.getLogger(). A LoggingFilter using raw System.out is inconsistent and bypasses log-level control, formatting, and handler configuration.

♻️ Proposed fix
 package org.juv25d.filter;
 
 import org.juv25d.http.HttpRequest;
 import org.juv25d.http.HttpResponse;
+import org.juv25d.logging.ServerLogging;
 
 import java.io.IOException;
+import java.util.logging.Logger;
 
 public class LoggingFilter implements Filter {
 
+    private static final Logger logger = ServerLogging.getLogger();
+
     `@Override`
     public void doFilter(HttpRequest req, HttpResponse res, FilterChain chain) throws IOException {
-        System.out.println(req.method() + " " + req.path());
+        logger.info(req.method() + " " + req.path());
         chain.doFilter(req, res);
     }
 }
src/test/java/org/juv25d/logging/ServerLoggingTest.java (1)

40-60: Redundant handler cleanup — setUp() already handles this.

Lines 43-46 duplicate the handler-removal logic that @BeforeEach setUp() already performs before each test. You can drop the manual cleanup and call ServerLogging.configure(logger) directly.

src/main/java/org/juv25d/plugin/StaticFilesPlugin.java (2)

3-3: Remove unnecessary same-package import.

Plugin is in the same package (org.juv25d.plugin), so the import on Line 3 is redundant.


16-25: Field-by-field copy is fragile if HttpResponse gains new fields.

If HttpResponse is extended with additional fields (e.g., HTTP version, trailers), this adapter will silently drop them. Consider adding a bulk-copy method on HttpResponse (e.g., copyFrom(HttpResponse other)) to keep this in sync, or refactor StaticFileHandler.handle to mutate the response directly rather than returning a new one.

src/test/java/org/juv25d/handler/StaticFileHandlerTest.java (1)

142-149: Redundant assertion — isNotEmpty() already covers length > 0.

Line 148 (assertThat(response.body().length).isGreaterThan(0)) is redundant after assertThat(response.body()).isNotEmpty() on Line 147.

src/main/java/org/juv25d/handler/MimeTypeResolver.java (1)

12-42: Consider using an unmodifiable map for MIME_TYPES.

The MIME_TYPES map is private and only populated in the static initializer, so there's no immediate bug, but wrapping it in Collections.unmodifiableMap (or using Map.ofEntries) would make the intent explicit and guard against accidental mutation in future changes.

♻️ Example using Map.ofEntries
-    private static final Map<String, String> MIME_TYPES = new HashMap<>();
-
-    static {
-        // Text types
-        MIME_TYPES.put("html", "text/html");
-        MIME_TYPES.put("htm", "text/html");
-        ...
-        MIME_TYPES.put("zip", "application/zip");
-    }
+    private static final Map<String, String> MIME_TYPES = Map.ofEntries(
+        Map.entry("html", "text/html"),
+        Map.entry("htm", "text/html"),
+        Map.entry("css", "text/css"),
+        Map.entry("js", "application/javascript"),
+        Map.entry("json", "application/json"),
+        Map.entry("xml", "application/xml"),
+        Map.entry("txt", "text/plain"),
+        Map.entry("png", "image/png"),
+        Map.entry("jpg", "image/jpeg"),
+        Map.entry("jpeg", "image/jpeg"),
+        Map.entry("gif", "image/gif"),
+        Map.entry("svg", "image/svg+xml"),
+        Map.entry("ico", "image/x-icon"),
+        Map.entry("webp", "image/webp"),
+        Map.entry("woff", "font/woff"),
+        Map.entry("woff2", "font/woff2"),
+        Map.entry("ttf", "font/ttf"),
+        Map.entry("otf", "font/otf"),
+        Map.entry("pdf", "application/pdf"),
+        Map.entry("zip", "application/zip")
+    );
src/main/java/org/juv25d/ConnectionHandler.java (1)

35-40: Minor: prefer static import over fully qualified java.util.Map.of().

Map is already imported at the top of the file (via HttpResponse usage pattern). Add an import for java.util.Map and use Map.of() for consistency.

Comment thread src/main/java/org/juv25d/ConnectionHandler.java
Comment thread src/main/java/org/juv25d/ConnectionHandler.java
Comment thread src/main/java/org/juv25d/filter/README.md
Comment thread src/main/java/org/juv25d/filter/README.md
Comment thread src/main/java/org/juv25d/handler/StaticFileHandler.java
Comment thread src/main/java/org/juv25d/plugin/README.md
Comment thread src/main/java/org/juv25d/plugin/README.md
Comment thread src/main/java/org/juv25d/plugin/README.md
Comment thread src/test/java/org/juv25d/filter/LoggingFilterTest.java Outdated
Comment thread src/test/java/org/juv25d/handler/StaticFileHandlerTest.java
@TatjanaTrajkovic

Copy link
Copy Markdown

Integration Complete

Hey @LinusWestling @fmazmz @TatjanaTrajkovic

Following @fmazmz's suggestion, I've integrated StaticFileHandler directly in this PR. I made some architectural decisions I'd like your feedback on.

Files Changed

New:

  • StaticFilesPlugin.java - Integrates StaticFileHandler with Pipeline

Modified:

  • App.java - Use StaticFilesPlugin instead of HelloPlugin
  • HttpResponse.java - Removed final, added setters
  • StaticFileHandler.java - Fixed import (HttpRequest from org.juv25d.http)
  • StaticFileHandlerTest.java - Fixed import

Architecture: Pipeline Integration

ADR-001 suggested direct integration:

if (request.method().equals("GET")) {
    HttpResponse response = StaticFileHandler.handle(request);
    HttpResponseWriter.write(outputStream, response);
}

I used the existing Pipeline pattern instead:

Pipeline → LoggingFilter → StaticFilesPlugin → StaticFileHandler

Why: Follows existing architecture, more extensible, same as Express/Spring/Django middleware.

Question:Is this acceptable, or prefer direct integration?

Breaking Change: HttpResponse Mutability

Removed final from fields and added setters:

// Before
private final int statusCode;

// After  
private int statusCode;
public void setStatusCode(int statusCode) { ... }

Why: Pipeline pattern requires modifying response object. Each request gets own instance (no sharing), single-threaded (no concurrency). Suggestion: If you prefer immutability, I can create a separate MutableHttpResponse wrapper class instead?

Feedback Needed

  1. Pipeline integration- OK or prefer direct approach?
  2. HttpResponse mutability- Acceptable or create wrapper?

Thanks

Closes #18

I agree with the pipeline integration, it follows the existing patterns and keeps the architecture consistent.

Regarding mutability, it might be better to keep HttpResponse immutable and, as you suggested, introduce a separate MutableHttpResponse. Immutability helps prevent accidental side effects and keeps the core response model safer, especially if we introduce multi-threading in the future.

@fmazmz

fmazmz commented Feb 11, 2026

Copy link
Copy Markdown
Member

nice one. This looks good to me.
Once you fix the branch conflicts I will be happy to approve this and merge.

Note regarding HttpResponse classes etc:
Other people are working on these classes in other branches, with the implementation from main, please defer from doing any changes as much as possible and keep this PR regarding the static file handling only.

Once we merge feel free to open a new issue and branch to make any other changes.

Otherwise now each PR will be getting a conflict.

fmazmz
fmazmz previously approved these changes Feb 12, 2026
@fmazmz

fmazmz commented Feb 12, 2026

Copy link
Copy Markdown
Member

Connected the plugin directly, its one line of code. Another PR will only delay our release further.

closes #54

@fmazmz
fmazmz merged commit d9fa188 into main Feb 12, 2026
2 checks passed
@fmazmz
fmazmz deleted the feature/18-static-file-handler branch February 12, 2026 11:04
@coderabbitai coderabbitai Bot mentioned this pull request Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GET handling for static files

7 participants