Skip to content

41-add-cors-filter-to-enable-cross-origin-requests - #120

Merged
fmazmz merged 9 commits into
mainfrom
41-add-cors-filter-to-enable-cross-origin-requests
Feb 25, 2026
Merged

41-add-cors-filter-to-enable-cross-origin-requests#120
fmazmz merged 9 commits into
mainfrom
41-add-cors-filter-to-enable-cross-origin-requests

Conversation

@VonAdamo

@VonAdamo VonAdamo commented Feb 22, 2026

Copy link
Copy Markdown

Add CORS filter with preflight support

Description

This PR adds CORS support to the server.

Made HttpResponse mutable to allow filters to modify status, headers, and body.

Implemented a whitelist-based CorsFilter.

Added proper handling of OPTIONS preflight requests (returns 204 with required CORS headers).

Added integration tests covering:

GET with/without Origin

OPTIONS preflight

Disallowed origin

Summary by CodeRabbit

  • New Features

    • Adds CORS handling for a local whitelist (allows requests from the local development origin), sets Access-Control-Allow-Origin and Vary headers, and properly handles preflight OPTIONS requests with allowed methods, headers, and caching.
  • Tests

    • Adds tests covering allowed origins, missing Origin header, preflight handling (204 + CORS headers), and disallowed origins.

HttpResponse was immutable, which prevented filters from adding headers
or modifying status/body. Since our filter chain relies on mutating the
shared response object, immutability blocked correct CORS implementation.

Added setter methods and removed unmodifiable headers wrapper.
HttpResponseWriter remains unaffected.
@VonAdamo VonAdamo linked an issue Feb 22, 2026 that may be closed by this pull request
@coderabbitai

coderabbitai Bot commented Feb 22, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@VonAdamo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 58 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 2f54503 and 17623f9.

📒 Files selected for processing (1)
  • src/test/java/org/juv25d/filter/CorsFilterTest.java
📝 Walkthrough

Walkthrough

Adds a new CorsFilter implementing CORS logic (whitelists http://localhost:3000, handles preflight OPTIONS) and updates HttpResponse to support case-insensitive header lookup and mutable headers; includes JUnit 5 tests exercising the filter against a local server.

Changes

Cohort / File(s) Summary
CORS Filter
src/main/java/org/juv25d/filter/CorsFilter.java
New public CorsFilter implementing Filter. Reads Origin case-insensitively; if whitelisted (http://localhost:3000) sets Access-Control-Allow-Origin and Vary: Origin. Handles OPTIONS preflight: sets Access-Control-Allow-Methods, mirrors Access-Control-Request-Headers or uses Content-Type, sets Access-Control-Max-Age: 3600, returns 204. Otherwise forwards to filter chain.
HttpResponse header API
src/main/java/org/juv25d/http/HttpResponse.java
Made headers mutable (removed final) and added public String getHeader(String name) performing case-insensitive lookup with null-safety.
Tests
src/test/java/org/juv25d/filter/CorsFilterTest.java
New JUnit 5 tests covering whitelisted-origin responses, missing-origin behavior, OPTIONS preflight handling, and unknown-origin requests. Tests use HttpClient and expect a running server at http://localhost:3000.

Sequence Diagram

sequenceDiagram
    participant Client
    participant CorsFilter
    participant FilterChain
    participant ServerResponse as Response

    Client->>CorsFilter: HTTP Request (with/without Origin)
    alt Origin missing or not whitelisted
        CorsFilter->>FilterChain: forward request
        FilterChain->>Response: produce response
        Response->>Client: response (no CORS headers)
    else Origin whitelisted
        alt OPTIONS (preflight)
            CorsFilter->>Response: set AC-Allow-Origin, AC-Allow-Methods
            CorsFilter->>Response: set AC-Allow-Headers (mirror or Content-Type)
            CorsFilter->>Response: set AC-Max-Age: 3600
            CorsFilter->>Client: 204 No Content (empty)
        else Non-OPTIONS
            CorsFilter->>Response: set AC-Allow-Origin, Vary: Origin
            CorsFilter->>FilterChain: forward request
            FilterChain->>Response: produce response
            Response->>Client: response (with CORS headers)
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • Cavve
  • annikaholmqvist94

Poem

🐇 I sniffed each Origin by the moonlit code,
For localhost I nodded, set the CORS abode.
OPTIONS bowed, I whispered "204",
Headers now mutable — I hopped out the door. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% 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 '41-add-cors-filter-to-enable-cross-origin-requests' directly and specifically describes the main change: implementing a CORS filter to enable cross-origin requests, which matches the primary objective of the PR.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 41-add-cors-filter-to-enable-cross-origin-requests

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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/org/juv25d/http/HttpResponse.java (1)

30-32: ⚠️ Potential issue | 🟠 Major

headers() exposes the internal mutable map — encapsulation leak.

Before this PR the map was wrapped in Collections.unmodifiableMap, preventing external callers from bypassing setHeader. The new accessor returns the raw LinkedHashMap directly, so any caller can call response.headers().put(...) / response.headers().remove(...), sidestepping the null-validation in setHeader and making mutation semantics inconsistent.

🛡️ Proposed fix
 public Map<String, String> headers() {
-    return headers;
+    return Collections.unmodifiableMap(headers);
 }
🤖 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 30 - 32, The
headers() accessor in HttpResponse currently returns the internal mutable
LinkedHashMap (headers), leaking encapsulation; change headers() to return an
unmodifiable view or a defensive copy so callers cannot mutate the internal map
directly and bypass setHeader's validation. Specifically, update the
HttpResponse.headers() method to return Collections.unmodifiableMap(headers) (or
new LinkedHashMap<>(headers)) and keep setHeader(...) as the only sanctioned
mutator to preserve null-checks and consistent mutation semantics.
🧹 Nitpick comments (1)
src/main/java/org/juv25d/filter/CorsFilter.java (1)

13-15: Hardcoded development origin is not production-ready.

http://localhost:3000 is the only permitted origin. Any production deployment will silently deny all browser cross-origin requests. Consider accepting the allowed-origins list via constructor injection or an externalized configuration property so the filter is reusable without code changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/filter/CorsFilter.java` around lines 13 - 15,
Replace the hardcoded ALLOWED_ORIGINS Set in CorsFilter with a configurable
source: add a constructor or setter on class CorsFilter that accepts a
Collection<String> (e.g., allowedOrigins) or read from an external config
property, remove the static Set.of(...) usage, and use the injected
allowedOrigins inside the filter logic; ensure existing tests/usage instantiate
CorsFilter with the desired origins (e.g., from application properties or
environment) so production deployments do not rely on the hardcoded
"http://localhost:3000".
🤖 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/filter/CorsFilter.java`:
- Around line 45-50: The else branch in CorsFilter currently overwrites the
Access-Control-Allow-Origin header with the literal "Content-Type"; update the
fallback to set Access-Control-Allow-Headers instead so the origin set earlier
remains untouched. Specifically, in the block that reads requestedHeaders and
handles the null/blank case, replace the
res.setHeader("Access-Control-Allow-Origin", "Content-Type") call with
res.setHeader("Access-Control-Allow-Headers", "Content-Type") so
Access-Control-Allow-Origin set by the earlier code remains correct and the
preflight response includes a default allowed header.

In `@src/test/java/org/juv25d/filter/CorsFilterTest.java`:
- Around line 19-24: The `@BeforeAll` method ensureServerIsRunning is currently a
no-op causing ConnectException for tests; implement server lifecycle here by
starting the test HTTP server (e.g., call your HTTP server start method such as
Server.start(3000) or equivalent) before tests and stop it in a corresponding
`@AfterAll` teardown (e.g., Server.stop()), or switch the test class to per-class
lifecycle with `@TestInstance`(Lifecycle.PER_CLASS) if you need non-static
setup/teardown; ensure the started server binds to port 3000 so the tests that
hit localhost:3000 succeed.
- Around line 55-80: Add a new test that exercises the CorsFilter fallback by
sending an OPTIONS preflight request WITHOUT the Access-Control-Request-Headers
header (mirror the existing shouldHandlePreflightOptionsRequest pattern) and
assert the response status is 204 and that
response.headers().firstValue("Access-Control-Allow-Headers") equals
"Content-Type"; this will hit the CorsFilter fallback branch (the logic around
line 49) and verify the fallback header value instead of the mirrored path.

---

Outside diff comments:
In `@src/main/java/org/juv25d/http/HttpResponse.java`:
- Around line 30-32: The headers() accessor in HttpResponse currently returns
the internal mutable LinkedHashMap (headers), leaking encapsulation; change
headers() to return an unmodifiable view or a defensive copy so callers cannot
mutate the internal map directly and bypass setHeader's validation.
Specifically, update the HttpResponse.headers() method to return
Collections.unmodifiableMap(headers) (or new LinkedHashMap<>(headers)) and keep
setHeader(...) as the only sanctioned mutator to preserve null-checks and
consistent mutation semantics.

---

Nitpick comments:
In `@src/main/java/org/juv25d/filter/CorsFilter.java`:
- Around line 13-15: Replace the hardcoded ALLOWED_ORIGINS Set in CorsFilter
with a configurable source: add a constructor or setter on class CorsFilter that
accepts a Collection<String> (e.g., allowedOrigins) or read from an external
config property, remove the static Set.of(...) usage, and use the injected
allowedOrigins inside the filter logic; ensure existing tests/usage instantiate
CorsFilter with the desired origins (e.g., from application properties or
environment) so production deployments do not rely on the hardcoded
"http://localhost:3000".

Comment thread src/main/java/org/juv25d/filter/CorsFilter.java
Comment thread src/test/java/org/juv25d/filter/CorsFilterTest.java Outdated
Comment thread src/test/java/org/juv25d/filter/CorsFilterTest.java

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

Very nice, just one thought below regarding where we put and read the properties from.

Comment thread src/main/java/org/juv25d/filter/CorsFilter.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.

♻️ Duplicate comments (1)
src/main/java/org/juv25d/filter/CorsFilter.java (1)

44-50: ⚠️ Potential issue | 🔴 Critical

Fix the preflight fallback header assignment.

Line 49 overwrites Access-Control-Allow-Origin with "Content-Type", which drops the real origin and omits Access-Control-Allow-Headers when no Access-Control-Request-Headers is present. This breaks the preflight response in that branch.

🐛 Proposed fix
-            } else {
-                res.setHeader("Access-Control-Allow-Origin", "Content-Type");
-            }
+            } else {
+                res.setHeader("Access-Control-Allow-Headers", "Content-Type");
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/filter/CorsFilter.java` around lines 44 - 50, In
CorsFilter (the preflight handling block that uses header(req.headers(),
"Access-Control-Request-Headers")), the else branch mistakenly overwrites
Access-Control-Allow-Origin with "Content-Type"; change that branch to set
Access-Control-Allow-Headers to a sensible default (e.g. "Content-Type")
instead, so the real origin header is not clobbered and the preflight response
still includes Access-Control-Allow-Headers when no
Access-Control-Request-Headers is present.
🧹 Nitpick comments (1)
src/main/java/org/juv25d/filter/CorsFilter.java (1)

37-38: Avoid clobbering existing Vary values.

If another filter or handler sets Vary, this overwrites it. Consider appending Origin instead.

♻️ Suggested tweak
-        res.setHeader("Vary", "Origin");
+        String vary = res.headers().get("Vary");
+        if (vary == null || vary.isBlank()) {
+            res.setHeader("Vary", "Origin");
+        } else if (!vary.toLowerCase().contains("origin")) {
+            res.setHeader("Vary", vary + ", Origin");
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/filter/CorsFilter.java` around lines 37 - 38, In
CorsFilter, avoid overwriting an existing Vary header: read the current Vary
value from the response (res.getHeader("Vary")), and if it's null or empty call
res.setHeader("Vary", "Origin"), otherwise if it doesn't already contain
"Origin" append ", Origin" and call res.setHeader with the combined value;
update the code around the existing res.setHeader("Vary", "Origin") lines so you
preserve any previously set Vary values.
🤖 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/filter/CorsFilter.java`:
- Around line 44-50: In CorsFilter (the preflight handling block that uses
header(req.headers(), "Access-Control-Request-Headers")), the else branch
mistakenly overwrites Access-Control-Allow-Origin with "Content-Type"; change
that branch to set Access-Control-Allow-Headers to a sensible default (e.g.
"Content-Type") instead, so the real origin header is not clobbered and the
preflight response still includes Access-Control-Allow-Headers when no
Access-Control-Request-Headers is present.

---

Nitpick comments:
In `@src/main/java/org/juv25d/filter/CorsFilter.java`:
- Around line 37-38: In CorsFilter, avoid overwriting an existing Vary header:
read the current Vary value from the response (res.getHeader("Vary")), and if
it's null or empty call res.setHeader("Vary", "Origin"), otherwise if it doesn't
already contain "Origin" append ", Origin" and call res.setHeader with the
combined value; update the code around the existing res.setHeader("Vary",
"Origin") lines so you preserve any previously set Vary values.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6ff0c1 and 5bf3aa1.

📒 Files selected for processing (1)
  • src/main/java/org/juv25d/filter/CorsFilter.java

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

Looks great!

@fmazmz
fmazmz merged commit 24c1a34 into main Feb 25, 2026
2 checks passed
@fmazmz
fmazmz deleted the 41-add-cors-filter-to-enable-cross-origin-requests branch February 25, 2026 15:59
@coderabbitai coderabbitai Bot mentioned this pull request Feb 27, 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.

Add CORS filter to enable cross-origin requests

3 participants