Skip to content

Initialize safe defaults in HttpResponse no-arg constructorFix/httpresponse defaults - #87

Merged
lindaeskilsson merged 8 commits into
mainfrom
fix/httpresponse-defaults
Feb 20, 2026
Merged

Initialize safe defaults in HttpResponse no-arg constructorFix/httpresponse defaults#87
lindaeskilsson merged 8 commits into
mainfrom
fix/httpresponse-defaults

Conversation

@lindaeskilsson

@lindaeskilsson lindaeskilsson commented Feb 18, 2026

Copy link
Copy Markdown

This PR makes HttpResponse safe to use out of the box by initializing sensible defaults in the no-arg constructor.

Changes

  • Initialize safe defaults in HttpResponse():
    • statusCode = 200
    • statusText = "OK"
    • empty headers map
    • empty body byte array
  • Make header initialization null-safe in the parameterized constructor

Tests

  • Added a unit test verifying:
    • default values are set
    • headers/body are never null
    • setHeader() does not throw and stores the header

Closes #81

Summary by CodeRabbit

  • New Features

    • Adds global security response headers and improves routing with static-file and not-found handling plus expanded redirect rules.
  • Bug Fixes

    • Improved HTTP response initialization and null-safety: consistent default status, non-null headers/body, and safer status text/headers handling.
    • Config loading now defaults missing values more robustly and surfaces underlying errors.
  • Tests

    • Added tests for HTTP response behavior, IP filter whitelist/blocks, and configuration loading/defaults.

@coderabbitai

coderabbitai Bot commented Feb 18, 2026

Copy link
Copy Markdown

Caution

Review failed

Failed to post review comments

📝 Walkthrough

Walkthrough

Adds safe defaults and null-safety to HttpResponse (final headers, default 200/OK, empty body), introduces SecurityHeadersFilter and registers it, makes ConfigLoader test-friendly (InputStream constructor), and adds unit tests for HttpResponse, IpFilter, and ConfigLoader.

Changes

Cohort / File(s) Summary
HttpResponse
src/main/java/org/juv25d/http/HttpResponse.java
Made headers field final; added safe defaults in no-arg constructor (200, "OK", empty LinkedHashMap, empty body); parameterized ctor and setStatusText use Objects.requireNonNull; defensive copies for headers/body; null→empty body handling.
App & Filters
src/main/java/org/juv25d/App.java, src/main/java/org/juv25d/filter/SecurityHeadersFilter.java
Added SecurityHeadersFilter class annotated as global; registered it in App pipeline (global filter order 0); changed imports to wildcard and added plugin registrations and redirect rule tweak.
Config loader
src/main/java/org/juv25d/util/ConfigLoader.java
Introduced package-private ConfigLoader(InputStream) and loadConfiguration(InputStream) for testability; explicit defaults for port/root/logging; safer map casting and improved error propagation.
Tests
src/test/java/org/juv25d/http/HttpResponseTest.java, src/test/java/org/juv25d/filter/IpFilterTest.java, src/test/java/org/juv25d/util/ConfigLoaderTest.java
Added unit tests: HttpResponse defaults and header mutation; IpFilter whitelist allow/block behavior; ConfigLoader YAML loading, defaulting, and null-InputStream error behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Router as Router
    participant SecFilter as SecurityHeadersFilter
    participant Chain as FilterChain
    participant Handler as Handler
    participant Response as HttpResponse

    Client->>Router: HTTP request
    Router->>SecFilter: pass request to global filters
    SecFilter->>Chain: chain.doFilter(req, res)
    Chain->>Handler: invoke handler(s)
    Handler-->>Response: write body/status/headers
    Chain-->>SecFilter: return to filter
    Note right of SecFilter: finally block ensures headers added
    SecFilter-->>Response: add X-Content-Type-Options: nosniff
    SecFilter-->>Response: add X-Frame-Options: DENY
    SecFilter-->>Response: add X-XSS-Protection: 0
    SecFilter-->>Response: add Referrer-Policy: no-referrer
    SecFilter-->>Router: return response
    Router-->>Client: HTTP response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • SandraNelj
  • bamsemats
  • fmazmz

Poem

🐰 I hopped through code with tiny paws,
Defaults tucked in, no crashing claws,
Filters guard the response at night,
Configs test-friendly, tests alight,
Hooray — the server's snug and bright! 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes out-of-scope changes: SecurityHeadersFilter class, App.java modifications adding plugins/filters unrelated to issue #81, and ConfigLoader changes that extend beyond the stated objective of safe HttpResponse defaults. Remove SecurityHeadersFilter, revert App.java changes to focus only on HttpResponse, and remove ConfigLoader modifications; these belong in separate PRs aligned with their own objectives.
Docstring Coverage ⚠️ Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main change: initializing safe defaults in HttpResponse's no-arg constructor, which aligns with the primary objective of making HttpResponse safe by default.
Linked Issues check ✅ Passed All objectives from issue #81 are met: no-arg constructor initializes statusCode to 200, statusText to 'OK', headers to empty map, and body to empty byte array; parameterized constructor validates statusText; unit test verifies defaults and setHeader() behavior.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/httpresponse-defaults

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.

@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: 1

🧹 Nitpick comments (2)
src/test/java/org/juv25d/http/HttpResponseTest.java (2)

17-18: Chain the two body() assertions to avoid a redundant array clone.

body() clones the underlying byte[] on every call. Two separate assertions allocate two clones for what is a single logical check.

♻️ Proposed change
-        assertThat(response.body()).isNotNull();
-        assertThat(response.body()).isEmpty();
+        assertThat(response.body()).isNotNull().isEmpty();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/org/juv25d/http/HttpResponseTest.java` around lines 17 - 18,
The two assertions call response.body() twice (which clones the byte[] both
times); replace the two separate assertions in HttpResponseTest (the lines using
assertThat(response.body()).isNotNull(); and
assertThat(response.body()).isEmpty();) with a single chained assertion that
calls response.body() once and asserts both conditions (e.g.,
assertThat(response.body()).isNotNull().isEmpty()), thereby avoiding the
redundant array clone.

10-24: Add a test covering the parameterized constructor's null-headers fix.

The PR description calls out null-safety for the parameterized constructor as an explicit objective, but there is no test for it. new HttpResponse(200, "OK", null, null) should be exercised to confirm headers() is non-null and body() is empty.

✅ Suggested additional test
`@Test`
void parameterizedConstructor_withNullHeadersAndBody_hasSafeDefaults() {
    HttpResponse response = new HttpResponse(200, "OK", null, null);

    assertThat(response.headers()).isNotNull().isEmpty();
    assertThat(response.body()).isNotNull().isEmpty();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/org/juv25d/http/HttpResponseTest.java` around lines 10 - 24,
Add a unit test that constructs HttpResponse via the parameterized constructor
(new HttpResponse(200, "OK", null, null)) and asserts that headers() is non-null
and empty and body() is non-null and empty; this verifies the null-headers/body
fix in the HttpResponse constructor and mirrors the existing defaultConstructor
test by exercising
HttpResponse(HttpResponse(int,String,Map<String,String>,byte[])) and calling
headers() and body() to confirm safe defaults.
🤖 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/main/java/org/juv25d/http/HttpResponse.java`:
- Around line 24-29: The parameterized constructor HttpResponse(int statusCode,
String statusText, Map<String, String> headers, byte[] body) currently allows a
null statusText; update the constructor to enforce the same non-null contract as
setStatusText by either calling Objects.requireNonNull(statusText, "statusText
must not be null") or replacing null with a safe default (e.g., empty string)
before assigning to this.statusText, while keeping the existing null-safe
handling for headers and body.

---

Nitpick comments:
In `@src/test/java/org/juv25d/http/HttpResponseTest.java`:
- Around line 17-18: The two assertions call response.body() twice (which clones
the byte[] both times); replace the two separate assertions in HttpResponseTest
(the lines using assertThat(response.body()).isNotNull(); and
assertThat(response.body()).isEmpty();) with a single chained assertion that
calls response.body() once and asserts both conditions (e.g.,
assertThat(response.body()).isNotNull().isEmpty()), thereby avoiding the
redundant array clone.
- Around line 10-24: Add a unit test that constructs HttpResponse via the
parameterized constructor (new HttpResponse(200, "OK", null, null)) and asserts
that headers() is non-null and empty and body() is non-null and empty; this
verifies the null-headers/body fix in the HttpResponse constructor and mirrors
the existing defaultConstructor test by exercising
HttpResponse(HttpResponse(int,String,Map<String,String>,byte[])) and calling
headers() and body() to confirm safe defaults.

Comment thread src/main/java/org/juv25d/http/HttpResponse.java

@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.

🧹 Nitpick comments (2)
src/main/java/org/juv25d/http/HttpResponse.java (2)

48-50: headers() returns the live internal map, allowing mutations that bypass setHeader().

Callers can invoke response.headers().remove(key) or response.headers().clear() directly. Since setHeader currently has no special validation logic, this is functionally equivalent today — but it couples external code to the internal Map API and bypasses any future hooks added to setHeader/removeHeader.

Consider wrapping the return in Collections.unmodifiableMap(headers) and providing an explicit removeHeader(String name) method to keep all mutation through the defined API. Given the intentionally mutable design, this is non-urgent.

♻️ Proposed refactor
 public Map<String, String> headers() {
-    return headers;
+    return Collections.unmodifiableMap(headers);
 }

+public void removeHeader(String name) {
+    headers.remove(name);
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/http/HttpResponse.java` around lines 48 - 50,
headers() currently exposes the internal mutable Map which allows callers to
mutate it directly and bypass API methods; change headers() to return an
unmodifiable view (e.g., wrap headers in Collections.unmodifiableMap) and add
explicit mutation methods such as removeHeader(String name) (and keep
setHeader(String name, String value)) so all modifications go through the
class's API (update references to headers() consumers if they relied on direct
mutations).

52-54: setHeader has no null guard on name or value.

null header names and values are invalid per the HTTP spec (RFC 9110). While LinkedHashMap.put accepts null keys without throwing, silently storing a null-named header would produce a malformed HTTP message.

🛡️ Proposed fix
 public void setHeader(String name, String value) {
+    Objects.requireNonNull(name, "header name must not be null");
+    Objects.requireNonNull(value, "header value must not be null");
     headers.put(name, value);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/http/HttpResponse.java` around lines 52 - 54, The
setHeader method stores headers without validating inputs; add null guards in
HttpResponse.setHeader to reject null header names or values (and optionally
empty names) before calling headers.put: check the method parameters (name and
value) and throw an IllegalArgumentException with a clear message if either is
null (or name is empty), so the headers LinkedHashMap cannot be populated with
invalid entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/main/java/org/juv25d/http/HttpResponse.java`:
- Around line 24-29: No change required: the HttpResponse(int statusCode, String
statusText, Map<String, String> headers, byte[] body) constructor already
enforces null-safety by calling Objects.requireNonNull on statusText and by
defaulting headers to Map.of() and defensively cloning body, so no further
modifications to statusText, headers or body handling are necessary.

---

Nitpick comments:
In `@src/main/java/org/juv25d/http/HttpResponse.java`:
- Around line 48-50: headers() currently exposes the internal mutable Map which
allows callers to mutate it directly and bypass API methods; change headers() to
return an unmodifiable view (e.g., wrap headers in Collections.unmodifiableMap)
and add explicit mutation methods such as removeHeader(String name) (and keep
setHeader(String name, String value)) so all modifications go through the
class's API (update references to headers() consumers if they relied on direct
mutations).
- Around line 52-54: The setHeader method stores headers without validating
inputs; add null guards in HttpResponse.setHeader to reject null header names or
values (and optionally empty names) before calling headers.put: check the method
parameters (name and value) and throw an IllegalArgumentException with a clear
message if either is null (or name is empty), so the headers LinkedHashMap
cannot be populated with invalid entries.

johanbriger
johanbriger previously approved these changes Feb 18, 2026

@johanbriger johanbriger 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.

Nice work! Solid improvement for null-safety!
Initializing defaults in the no-arg constructor and adding null-checks in the parameterized constructor is a great way to make the API more robust.

@SandraNelj

Copy link
Copy Markdown

Looks good, I have also done the changes regarding the HttpResponse in my PR (#76), so we are going to have a conflict, but I don't think that it's going to be a big change! Just FYI 👍

@SandraNelj
SandraNelj self-requested a review February 18, 2026 09:05
SandraNelj
SandraNelj previously approved these changes Feb 18, 2026
Tyreviel
Tyreviel previously approved these changes Feb 19, 2026
SandraNelj
SandraNelj previously approved these changes Feb 19, 2026
johanbriger and others added 3 commits February 20, 2026 10:19
* Add SecurityHeadersFilter for hardened HTTP responses

* Add SecurityHeadersFilter for hardened HTTP responses

* Changed X-XSS-Protection value to recommended 0,

* address code review feedback from CodeRabbit

* Add @global annotation to SecurityHeadersFilter for automatic registration

* Removed line of code in App.java
#76)

* Added IpFilterTest class with unit test verifying IpFilter allows whitelisted IPs.

* Fix IpFilterTest to verify response interaction instead of mock state

* Added unit test for blocking IP that is not in the whitelist, results in 403 Forbidden response.

Fixed HttpResponse construtors to always initialize headers and body to prevent NPE when filters call setHeader or setBody.

* Update IpFilter whitelist allow test to use real HttpResponse

* Assert expected status code in IpFilter whitelist allow test
* test(config-loader): add test skeleton for ConfigLoader

* test(config-loader): add initial test for loading config

* refactor(config-loader): extract configuration loading to InputStream constructor

* test(config-loader): verify values are loaded from yaml input

* test(config-loader): add test for default values when server keys missing

* test(config-loader): add null-input error handling test

* refactor(config-loader): add safe map casting and robust value parsing

* fix: handle missing server config and keep original exception cause

* fix(config-loader): handle empty yaml config safely

* fix (config-loader): add default log level for consistent config values

* Add missing curly bracket.

* fix(config-loader): address review rabbit comments and improve tests

---------

Co-authored-by: Simon Forsberg <simon.co.forsberg@gmail.com>
Co-authored-by: mattknatt <mattiashagstrommusic@gmail.com>
@lindaeskilsson
lindaeskilsson dismissed stale reviews from SandraNelj and Tyreviel via d0d75ce February 20, 2026 09:23
@lindaeskilsson
lindaeskilsson merged commit 997b771 into main Feb 20, 2026
2 checks passed
This was referenced Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Initialize safe defaults in HttpResponse no-arg constructor

5 participants