Skip to content

Url redirect filter #33#41

Closed
Ericthilen wants to merge 16 commits intomainfrom
URL-Redirect-Filter
Closed

Url redirect filter #33#41
Ericthilen wants to merge 16 commits intomainfrom
URL-Redirect-Filter

Conversation

@Ericthilen
Copy link

@Ericthilen Ericthilen commented Feb 11, 2026

Implements a RedirectFilter that intercepts incoming requests and returns HTTP redirects based on a configurable list of redirect rules.

Requirements covered

  • Configurable redirect rules: sourcePath (regex / wildcard compiled to Pattern), targetUrl, statusCode (301/302)
  • Rules evaluated in order (first match wins)
  • Stops the pipeline on match
  • Sets correct Location header + status code
  • Logs each redirect for debugging

Tests

  • 301 redirect
  • 302 redirect
  • No matching rule continues pipeline
  • Wildcard matching via RedirectRulesLoader.compileSourcePattern("/docs/*")

How to test

Run RedirectFilterTest.

Issue #33

Co-authored by @viktorlindell12

Summary by CodeRabbit

  • New Features

    • Added HTTP request redirection capabilities with support for wildcard and regex-based redirect patterns.
    • Configurable redirect status codes (301 and 302) for flexible redirect behavior.
  • Tests

    • Added comprehensive unit tests covering redirect functionality, pattern matching, and edge cases.

@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

Introduces a filter chain pattern for HTTP request processing with configurable redirect rules. Includes FilterChain orchestrator, TerminalHandler interface, RedirectFilter implementation, RedirectRule data class with validation, RedirectRulesLoader utility for pattern compilation supporting wildcards and regex, and comprehensive unit tests.

Changes

Cohort / File(s) Summary
Filter Chain Infrastructure
src/main/java/org/example/server/FilterChain.java, src/main/java/org/example/server/TerminalHandler.java
Introduces filter chain pattern with a sequential filter processor and a functional interface for terminal request handlers.
Redirect Implementation
src/main/java/org/example/server/RedirectFilter.java, src/main/java/org/example/server/RedirectRule.java, src/main/java/org/example/server/RedirectRulesLoader.java
Implements HTTP redirect functionality with configurable rules, pattern matching, wildcard and regex support, and input validation for status codes and rule parameters.
Tests
src/test/java/org/example/server/RedirectFilterTest.java
Comprehensive unit tests validating redirect behavior, rule matching, wildcard pattern compilation, regex-based patterns, and error handling.

Sequence Diagram(s)

sequenceDiagram
    participant Client as HTTP Client
    participant FC as FilterChain
    participant RF as RedirectFilter
    participant TH as TerminalHandler

    Client->>FC: doFilter(request, response)
    
    loop While filters remain
        FC->>RF: doFilter(request, response, chain)
        alt Rule matches
            RF->>RF: matches(requestPath)
            RF->>response: setStatus(statusCode)
            RF->>response: setHeader("Location", targetUrl)
            Note over RF: Stop processing
        else No match
            RF->>FC: chain.doFilter(request, response)
            FC->>TH: handle(request, response)
            TH->>response: populateResponse
        end
    end
    
    FC-->>Client: Response with redirect or content
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

priority:high

Poem

🐰 Hops with glee down the filter chain,
Where redirects leap without pain,
Wildcards and regex dance with delight,
Each request finds its path just right! 🎯

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 "Url redirect filter #33" directly refers to the main feature introduced: a redirect filter implementation addressing issue #33. It is specific and relates to the primary change.

✏️ 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 URL-Redirect-Filter

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/main/java/org/example/server/RedirectResponse.java`:
- Around line 3-19: Remove the unused mutable class RedirectResponse: delete the
RedirectResponse class entirely (it is not referenced anywhere; RedirectFilter
sets status and headers directly) and ensure no leftover imports reference
RedirectResponse; follow the PR convention used by RedirectRule (declare types
as final/immutable) by not reintroducing this mutable type—run a build to
confirm no usages remain and adjust any tests or references to use HttpResponse
via RedirectFilter instead.
🧹 Nitpick comments (2)
src/main/java/org/example/server/RedirectRulesLoader.java (1)

34-45: wildcardToRegex produces unnecessarily bloated patterns by quoting each character individually.

Pattern.quote(String.valueOf(c)) wraps every single literal character in \Q…\E. For "/docs/*" this yields \Q/\E\Qd\E\Qo\E\Qc\E\Qs\E\Q/\E.* instead of the simpler \Q/docs/\E.*. Consider accumulating runs of literal characters and quoting them in one call.

♻️ Suggested refactor
 private static String wildcardToRegex(String wildcard) {
     StringBuilder sb = new StringBuilder();
-    for (int i = 0; i < wildcard.length(); i++) {
-        char c = wildcard.charAt(i);
-        if (c == '*') {
-            sb.append(".*");
-        } else {
-            sb.append(Pattern.quote(String.valueOf(c)));
+    int start = 0;
+    for (int i = 0; i < wildcard.length(); i++) {
+        if (wildcard.charAt(i) == '*') {
+            if (i > start) {
+                sb.append(Pattern.quote(wildcard.substring(start, i)));
+            }
+            sb.append(".*");
+            start = i + 1;
         }
     }
+    if (start < wildcard.length()) {
+        sb.append(Pattern.quote(wildcard.substring(start)));
+    }
     return sb.toString();
 }
src/main/java/org/example/server/FilterChain.java (1)

15-18: Consider defensively copying the filters array.

The constructor stores the caller's array reference directly. If the caller later mutates the array, the chain's behavior becomes unpredictable. A shallow copy is cheap and eliminates this risk.

🛡️ Proposed fix
 public FilterChain(HttpFilter[] filters, TerminalHandler terminal) {
-    this.filters = Objects.requireNonNull(filters, "filters");
+    Objects.requireNonNull(filters, "filters");
+    this.filters = filters.clone();
     this.terminal = Objects.requireNonNull(terminal, "terminal");
 }

@gitnes94 gitnes94 assigned gitnes94 and unassigned gitnes94 Feb 11, 2026
@gitnes94 gitnes94 self-requested a review February 11, 2026 18:00
@gitnes94 gitnes94 self-requested a review February 12, 2026 08:01
gitnes94
gitnes94 previously approved these changes Feb 12, 2026
Copy link

@gitnes94 gitnes94 left a comment

Choose a reason for hiding this comment

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

Allt ser bra ut nu, jätteduktigt jobbat!

@gvaguirres
Copy link

Jag föreslår att vi pausar merge av specifika filter då jag har märkt att vi har dubbla implementeringar av FilterChain och HttpRequest, tills vi har definierat en gemensam arkitektur och paketstruktur. Annars kommer vi att få namnkonflikter och redundant kod eller vad tycker ni?

@eeebbaandersson
Copy link

Låter smart tycker jag!
Kanske är bra ifall vi ser till att få in basstrukturen som @eraiicphu gjort först och sedan addera varje filter separat?
Finns en aktiv PR för detta nu.

@gvaguirres
Copy link

Ja precis @eeebbaandersson. Jag håller med dig att vänta på @eraiicphu basstrukturen

@eraiicphu eraiicphu self-requested a review February 12, 2026 11:21
@eraiicphu
Copy link

Wild Eric joins conversation:
Grym ide! jag kikar igenom lite jag med så kanske vi kan harmonisera våra implementeringer.
Mitt issue (23) handlade om att skapa base interfaces för filter och filterChain. som då framtida filters ska implementa.

Vi tänkte på samma sak även när det kom till HttpRequest klass, men ser att du har färre fält (?) Jag speglade klassen HttpParseRequestLina som mergades den klassen tänker jag är den som parsar inkommande HTTP från browsern.
HttpParser klassen parsar header.
och förberedde för body.

Så bara från lite snabb kik så verkar det som att mina klasserna:
(I)FilterChain
är samma som din HttpFilter.

Ser även att du gjort en HttpResponse.
Tror detta fyller samma funktion som tidigare klass som mergades igår med, HttpResponseBuilder

@eraiicphu
Copy link

Vi kan försöka linka ihop så kan vi försöka reda ut och få det att funka tillsammans :D

@eeebbaandersson
Copy link

eeebbaandersson commented Feb 12, 2026

Jag föreslår att vi kör basstrukturen som @eraiicphu gjort som grund i sin issue #23, då den ändå handlade om att definiera interfacen. Tänker att det blir lättare att ha det satt på plats och sen får vi övriga som skapar separata filter modifiera vår kod lite för att förhålla oss till detta?

Som Eric nämnde fanns ju även färdig kod i main för HttpResponse via HttpResponseBuilder.

@Ericthilen @gvaguirres @viktorlindell12 @AnnaZiafar @fredrikmohlen @Rickank @apaegs
@AntonAhlqvist @gurkvatten

Vad tror ni om detta? :)

@fredrikmohlen fredrikmohlen linked an issue Feb 18, 2026 that may be closed by this pull request
@Boppler12 Boppler12 self-requested a review February 19, 2026 11:07
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (4)
src/test/java/org/example/filter/redirect/RedirectFilterTest.java (2)

108-112: Consider asserting the exception message for richer test diagnostics.

Currently the test only verifies the exception type. Chaining .withMessageContaining(...) makes the intent explicit and gives better failure output if the wrong IllegalArgumentException is thrown (e.g., from an unrelated guard).

✨ Suggested addition
 assertThatThrownBy(() -> new RedirectRule(Pattern.compile("^/x$"), "/y", 307))
-        .isInstanceOf(IllegalArgumentException.class);
+        .isInstanceOf(IllegalArgumentException.class)
+        .withMessageContaining("307");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/org/example/filter/redirect/RedirectFilterTest.java` around
lines 108 - 112, The test redirect_rule_rejects_invalid_status_code in
RedirectFilterTest currently only asserts the exception type; update the
assertion to also check the exception message to make the failure
diagnostic-specific (e.g., chain .withMessageContaining(...) or equivalent) so
it verifies the IllegalArgumentException thrown by the RedirectRule constructor
(new RedirectRule(Pattern.compile("^/x$"), "/y", 307)) contains the expected
message about invalid status codes.

92-98: Consider covering edge cases /docs/ and /docs in the wildcard test.

The current assertions document that * is a single-segment wildcard but leave undefined whether /docs/ (empty segment) or /docs (missing segment entirely) match. These boundary cases are the most likely sources of future regressions when the pattern-compilation logic is modified.

✨ Suggested additions
 `@Test`
 void wildcard_matching_docs_star() {
     var p = RedirectRulesLoader.compileSourcePattern("/docs/*");
     assertThat(p.matcher("/docs/test").matches()).isTrue();
     assertThat(p.matcher("/docs/any/path").matches()).isFalse();
     assertThat(p.matcher("/doc/test").matches()).isFalse();
+    assertThat(p.matcher("/docs/").matches()).isFalse();  // empty segment
+    assertThat(p.matcher("/docs").matches()).isFalse();   // no segment at all
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/org/example/filter/redirect/RedirectFilterTest.java` around
lines 92 - 98, Add assertions to the wildcard_matching_docs_star test to cover
the boundary cases: verify that compileSourcePattern("/docs/*") does NOT match
"/docs/" (empty segment) and does NOT match "/docs" (missing segment). Update
RedirectFilterTest (method wildcard_matching_docs_star) using the existing
pattern variable p from RedirectRulesLoader.compileSourcePattern to add
assertThat(p.matcher("/docs/").matches()).isFalse() and
assertThat(p.matcher("/docs").matches()).isFalse().
src/main/java/org/example/filter/redirect/RedirectRulesLoader.java (1)

34-44: [^/]* semantics undocumented; per-character quoting is harder to read than a split-based approach

Two points:

  1. Single-segment wildcard* maps to [^/]*, meaning /docs/* matches /docs/foo but not /docs/foo/bar. This is a meaningful, non-obvious constraint (unlike, e.g., Ant-style **). It should be documented in the method's Javadoc so rule authors are not surprised.

  2. Per-character Pattern.quote — wrapping each non-* character in its own \Q…\E escape is correct but noisy and harder to read. Splitting on * and quoting each literal segment is the conventional approach:

♻️ Proposed refactor
-    private static String wildcardToRegex(String wildcard) {
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < wildcard.length(); i++) {
-            char c = wildcard.charAt(i);
-            if (c == '*') {
-                sb.append("[^/]*");
-            } else {
-                sb.append(Pattern.quote(String.valueOf(c)));
-            }
-        }
-        return sb.toString();
-    }
+    /**
+     * Converts a wildcard path to a regex fragment.
+     * Each {`@code` *} matches any sequence of non-slash characters (single path segment only).
+     * Use {`@code` regex:} prefix in the source path for cross-segment or more complex patterns.
+     */
+    private static String wildcardToRegex(String wildcard) {
+        String[] parts = wildcard.split("\\*", -1);
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < parts.length; i++) {
+            if (i > 0) {
+                sb.append("[^/]*");
+            }
+            sb.append(Pattern.quote(parts[i]));
+        }
+        return sb.toString();
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/filter/redirect/RedirectRulesLoader.java` around
lines 34 - 44, Update the wildcardToRegex method: add Javadoc that clearly
states a '*' matches any characters except '/' (single path segment semantics so
"/docs/*" does not match "/docs/foo/bar"), and refactor the implementation to
avoid per-character Pattern.quote by splitting the input on '*' and
Pattern.quote-ing each literal segment, then joining them with the literal
segment-regex for '*' (i.e., "[^/]*") to preserve current behavior; reference
the wildcardToRegex method when making these changes.
src/main/java/org/example/filter/redirect/RedirectRule.java (1)

13-15: Consider supporting additional standard redirect codes (303, 307, 308).

RFC 7231/9110 defines 303 See Other, 307 Temporary Redirect, and 308 Permanent Redirect as valid redirect responses. Restricting to only 301/302 will silently reject legitimate configurations. This is a future-proofing concern but worth addressing before the API solidifies.

♻️ Proposed fix
-        if (statusCode != 301 && statusCode != 302) {
-            throw new IllegalArgumentException("statusCode must be 301 or 302");
+        if (statusCode != 301 && statusCode != 302 && statusCode != 303
+                && statusCode != 307 && statusCode != 308) {
+            throw new IllegalArgumentException("statusCode must be a valid redirect code (301, 302, 303, 307, 308)");
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/filter/redirect/RedirectRule.java` around lines 13
- 15, The constructor/check in RedirectRule currently throws for any statusCode
other than 301 or 302; update the validation to allow the full set of standard
redirect status codes (301, 302, 303, 307, 308) instead of only 301/302. Modify
the conditional that checks statusCode in RedirectRule to accept 303, 307 and
308 (or validate against a small set/enum of allowed codes) and keep the
IllegalArgumentException for any non-redirect codes; ensure any unit tests
referencing RedirectRule/statusCode are updated accordingly.
🤖 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/example/filter/redirect/RedirectFilter.java`:
- Line 31: In RedirectFilter (LOG.info(...) line) the user-controlled variable
path is logged raw; remove control characters or encode them before logging to
prevent CRLF log injection. Update the code around the LOG.info(...) call to
create a sanitizedPath (e.g., strip/replace \r and \n and other control chars or
URL-encode the value) and use sanitizedPath in the log message instead of path;
keep rule.getTargetUrl() and rule.getStatusCode() unchanged.
- Around line 27-30: Guard against a null request path before calling
RedirectRule.matches: check the result of request.getPath() and handle null
(e.g., skip processing or treat as empty string) so you never pass null into
rule.matches which calls sourcePattern.matcher(requestPath).matches(); update
the logic around request.getPath() in RedirectFilter (the variable `path`) to
early-return or continue the loop when null, or normalize to a non-null value,
ensuring RedirectRule.matches and its use of sourcePattern.matcher(...) never
receive null.
- Around line 3-6: RedirectFilter currently imports
org.example.httpparser.HttpRequest and org.example.filter.FilterChain which
duplicates existing core types; update RedirectFilter to use the consolidated,
canonical core abstractions (replace imports to the single project definitions
such as org.example.server.HttpRequest and org.example.server.FilterChain or
whichever package your codebase has chosen as canonical), adjust any method
signatures or usages inside RedirectFilter to the canonical types (e.g., the
class name RedirectFilter, method parameters using HttpRequest and FilterChain),
and remove the old conflicting imports so the file consistently references the
single agreed-upon HttpRequest and FilterChain implementations.

In `@src/main/java/org/example/filter/redirect/RedirectRule.java`:
- Line 12: The constructor in RedirectRule currently uses
Objects.requireNonNull(targetUrl, "targetUrl") but doesn't reject empty or blank
strings, which can produce a Location header with no destination; update
RedirectRule to validate targetUrl is not empty/blank (e.g., trim and check
length or use String.isBlank()) and throw an IllegalArgumentException with a
clear message (e.g., "targetUrl must not be blank") when it fails, replacing or
augmenting the existing requireNonNull call so targetUrl is guaranteed non-null
and non-empty for later use.

In `@src/main/java/org/example/filter/redirect/RedirectRulesLoader.java`:
- Around line 18-19: The extracted regex string rawRegex in RedirectRulesLoader
is not trimmed after removing REGEX_PREFIX, so leading spaces (e.g. "regex:
/docs/.*") become part of the pattern and cause non-matching rules; update the
code that sets rawRegex (and the subsequent blank check) to trim() the substring
result (e.g., rawRegex = trimmed.substring(REGEX_PREFIX.length()).trim()) before
calling Pattern.compile and before the blank check, ensuring you still handle
empty/blank values and surface a clear error if the pattern is empty or invalid.

In `@src/test/java/org/example/filter/redirect/RedirectFilterTest.java`:
- Around line 52-67: The test returns_302_redirect currently doesn't assert the
filter stops the chain; add the same AtomicBoolean guard used in
returns_301_redirect_and_stops_pipeline to ensure the FilterChain is not
invoked. Modify the local FilterChain lambda in returns_302_redirect to flip an
AtomicBoolean (e.g., chainCalled.set(true)) if executed, and after
filter.doFilter(...) assert that chainCalled.get() is false in addition to the
existing status/Location assertions; reference RedirectFilter, RedirectRule,
FilterChain and responseAsString when locating the test to change.

---

Nitpick comments:
In `@src/main/java/org/example/filter/redirect/RedirectRule.java`:
- Around line 13-15: The constructor/check in RedirectRule currently throws for
any statusCode other than 301 or 302; update the validation to allow the full
set of standard redirect status codes (301, 302, 303, 307, 308) instead of only
301/302. Modify the conditional that checks statusCode in RedirectRule to accept
303, 307 and 308 (or validate against a small set/enum of allowed codes) and
keep the IllegalArgumentException for any non-redirect codes; ensure any unit
tests referencing RedirectRule/statusCode are updated accordingly.

In `@src/main/java/org/example/filter/redirect/RedirectRulesLoader.java`:
- Around line 34-44: Update the wildcardToRegex method: add Javadoc that clearly
states a '*' matches any characters except '/' (single path segment semantics so
"/docs/*" does not match "/docs/foo/bar"), and refactor the implementation to
avoid per-character Pattern.quote by splitting the input on '*' and
Pattern.quote-ing each literal segment, then joining them with the literal
segment-regex for '*' (i.e., "[^/]*") to preserve current behavior; reference
the wildcardToRegex method when making these changes.

In `@src/test/java/org/example/filter/redirect/RedirectFilterTest.java`:
- Around line 108-112: The test redirect_rule_rejects_invalid_status_code in
RedirectFilterTest currently only asserts the exception type; update the
assertion to also check the exception message to make the failure
diagnostic-specific (e.g., chain .withMessageContaining(...) or equivalent) so
it verifies the IllegalArgumentException thrown by the RedirectRule constructor
(new RedirectRule(Pattern.compile("^/x$"), "/y", 307)) contains the expected
message about invalid status codes.
- Around line 92-98: Add assertions to the wildcard_matching_docs_star test to
cover the boundary cases: verify that compileSourcePattern("/docs/*") does NOT
match "/docs/" (empty segment) and does NOT match "/docs" (missing segment).
Update RedirectFilterTest (method wildcard_matching_docs_star) using the
existing pattern variable p from RedirectRulesLoader.compileSourcePattern to add
assertThat(p.matcher("/docs/").matches()).isFalse() and
assertThat(p.matcher("/docs").matches()).isFalse().

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/main/java/org/example/filter/redirect/RedirectFilter.java (2)

27-27: Null path guard still missing — already flagged in a prior review.

If request.getPath() returns null, path.replaceAll(...) (or rule.matches(path) once restored) will throw a NullPointerException. Add a null check before the loop as previously suggested.

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

In `@src/main/java/org/example/filter/redirect/RedirectFilter.java` at line 27,
The code in RedirectFilter uses String path = request.getPath() and then calls
path.replaceAll(...) / rule.matches(path) which will NPE if path is null; add a
null guard right after obtaining path (e.g., if (path == null) { /* either set
path = "" or skip processing/return early depending on desired behavior */ }) so
the subsequent loop over rules (and calls to rule.matches(path) or
path.replaceAll(...)) never executes with a null path; update RedirectFilter to
handle the null case consistently with existing redirect semantics.

3-6: Duplicate type definitions — already flagged in a prior review.

This file still imports from org.example.filter.Filter, org.example.filter.FilterChain, and org.example.httpparser.HttpRequest, while the canonical project types live under org.example.server (e.g., FilterChain.doFilter(HttpRequest, HttpResponse)). These need to be reconciled with the agreed-upon base interfaces before merging.

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

In `@src/main/java/org/example/filter/redirect/RedirectFilter.java` around lines 3
- 6, The RedirectFilter currently imports incorrect duplicate types
(org.example.filter.Filter, org.example.filter.FilterChain,
org.example.httpparser.HttpRequest); update imports to use the canonical server
interfaces (e.g., org.example.server.Filter, org.example.server.FilterChain,
org.example.server.HttpRequest) and adjust any method signatures in
RedirectFilter (such as calls to FilterChain.doFilter(HttpRequest, HttpResponse)
and the RedirectFilter's implemented Filter.doFilter) to match the canonical
interfaces so the class compiles against the agreed base types.
🤖 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/example/filter/redirect/RedirectFilter.java`:
- Around line 29-36: Restore the missing match condition inside RedirectFilter's
loop: wrap the existing redirect logic (sanitizing path, logging, setting
response status/header and return) in an if (rule.matches(path)) block so the
redirect only runs for matching RedirectRule instances; ensure the braces around
the for (RedirectRule rule : rules) loop and the new if are balanced so
chain.doFilter(...) remains inside the method body and non-matching requests
continue down the filter chain.

---

Duplicate comments:
In `@src/main/java/org/example/filter/redirect/RedirectFilter.java`:
- Line 27: The code in RedirectFilter uses String path = request.getPath() and
then calls path.replaceAll(...) / rule.matches(path) which will NPE if path is
null; add a null guard right after obtaining path (e.g., if (path == null) { /*
either set path = "" or skip processing/return early depending on desired
behavior */ }) so the subsequent loop over rules (and calls to
rule.matches(path) or path.replaceAll(...)) never executes with a null path;
update RedirectFilter to handle the null case consistently with existing
redirect semantics.
- Around line 3-6: The RedirectFilter currently imports incorrect duplicate
types (org.example.filter.Filter, org.example.filter.FilterChain,
org.example.httpparser.HttpRequest); update imports to use the canonical server
interfaces (e.g., org.example.server.Filter, org.example.server.FilterChain,
org.example.server.HttpRequest) and adjust any method signatures in
RedirectFilter (such as calls to FilterChain.doFilter(HttpRequest, HttpResponse)
and the RedirectFilter's implemented Filter.doFilter) to match the canonical
interfaces so the class compiles against the agreed base types.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aebd6de and 5fdac73.

📒 Files selected for processing (1)
  • src/main/java/org/example/filter/redirect/RedirectFilter.java

Copy link

@coderabbitai coderabbitai bot left a comment

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/example/filter/redirect/RedirectFilter.java (1)

3-6: ⚠️ Potential issue | 🟠 Major

Unresolved: conflicting HttpRequest and FilterChain type definitions across the codebase.

This file still imports org.example.httpparser.HttpRequest and org.example.filter.FilterChain, while multiple competing definitions exist in other packages (org.example.server.HttpRequest, org.example.server.FilterChain, org.example.filter.FilterChainImpl). The type consolidation mentioned in the PR comments (waiting for @eraiicphu's base interfaces from Issue #23) has not yet happened; merging this as-is will make that future reconciliation harder.

#!/bin/bash
# Verify current state of duplicate type definitions
echo "=== HttpRequest definitions ==="
rg -n "class HttpRequest|interface HttpRequest" -t java -A1

echo ""
echo "=== FilterChain definitions ==="
rg -n "class FilterChain|interface FilterChain" -t java -A1

echo ""
echo "=== Filter interface definitions ==="
rg -n "^public interface Filter\b" -t java -A1
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/filter/redirect/RedirectFilter.java` around lines 3
- 6, The file RedirectFilter imports conflicting types
(org.example.httpparser.HttpRequest and org.example.filter.FilterChain) which
clash with other definitions (org.example.server.HttpRequest,
org.example.server.FilterChain, org.example.filter.FilterChainImpl); update
RedirectFilter to use the project’s canonical base interfaces (replace the
current imports with the agreed server-level interfaces
org.example.server.HttpRequest and org.example.server.FilterChain or the
canonical Filter interface used across the codebase) and add a short TODO
referencing Issue `#23` and `@eraiicphu` so future reconciliation is clear; ensure
all references inside class RedirectFilter compile against the chosen
HttpRequest and FilterChain symbols and remove any reliance on the duplicate
org.example.httpparser or filter-local types.
🧹 Nitpick comments (1)
src/main/java/org/example/filter/redirect/RedirectFilter.java (1)

35-35: Remove debug artifact comment before merge.

The inline comment // <-- DENNA RADEN SKA FINNAS ("THIS LINE SHOULD EXIST" in Swedish) is a development-time annotation confirming the restored if condition and has no value in production code.

🧹 Proposed cleanup
-        if (rule.matches(path)) {   // <-- DENNA RADEN SKA FINNAS
+        if (rule.matches(path)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/filter/redirect/RedirectFilter.java` at line 35,
Remove the development-time inline comment next to the if condition in
RedirectFilter (the comment after "if (rule.matches(path))"); open the
RedirectFilter class and delete the trailing "// <-- DENNA RADEN SKA FINNAS"
artifact so the if statement reads cleanly (preserve existing spacing/formatting
and keep the condition "if (rule.matches(path))" intact).
🤖 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/example/filter/redirect/RedirectFilter.java`:
- Around line 3-6: The file RedirectFilter imports conflicting types
(org.example.httpparser.HttpRequest and org.example.filter.FilterChain) which
clash with other definitions (org.example.server.HttpRequest,
org.example.server.FilterChain, org.example.filter.FilterChainImpl); update
RedirectFilter to use the project’s canonical base interfaces (replace the
current imports with the agreed server-level interfaces
org.example.server.HttpRequest and org.example.server.FilterChain or the
canonical Filter interface used across the codebase) and add a short TODO
referencing Issue `#23` and `@eraiicphu` so future reconciliation is clear; ensure
all references inside class RedirectFilter compile against the chosen
HttpRequest and FilterChain symbols and remove any reliance on the duplicate
org.example.httpparser or filter-local types.

---

Nitpick comments:
In `@src/main/java/org/example/filter/redirect/RedirectFilter.java`:
- Line 35: Remove the development-time inline comment next to the if condition
in RedirectFilter (the comment after "if (rule.matches(path))"); open the
RedirectFilter class and delete the trailing "// <-- DENNA RADEN SKA FINNAS"
artifact so the if statement reads cleanly (preserve existing spacing/formatting
and keep the condition "if (rule.matches(path))" intact).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5fdac73 and ebfe583.

📒 Files selected for processing (4)
  • src/main/java/org/example/filter/redirect/RedirectFilter.java
  • src/main/java/org/example/filter/redirect/RedirectRule.java
  • src/main/java/org/example/filter/redirect/RedirectRulesLoader.java
  • src/test/java/org/example/filter/redirect/RedirectFilterTest.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/org/example/filter/redirect/RedirectRulesLoader.java
  • src/test/java/org/example/filter/redirect/RedirectFilterTest.java
  • src/main/java/org/example/filter/redirect/RedirectRule.java

Boppler12
Boppler12 previously approved these changes Feb 23, 2026
eraiicphu
eraiicphu previously approved these changes Feb 23, 2026
Copy link

@eraiicphu eraiicphu left a comment

Choose a reason for hiding this comment

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

Snyggt !

Copy link

@coderabbitai coderabbitai bot left a comment

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/example/server/RedirectRule.java (1)

11-18: ⚠️ Potential issue | 🟡 Minor

Reject blank targetUrl to avoid empty Location headers.
requireNonNull allows ""/whitespace, which can produce a redirect with no destination.

🛡️ Suggested guard
 public RedirectRule(Pattern sourcePattern, String targetUrl, int statusCode) {
     this.sourcePattern = Objects.requireNonNull(sourcePattern, "sourcePattern");
-    this.targetUrl = Objects.requireNonNull(targetUrl, "targetUrl");
+    this.targetUrl = Objects.requireNonNull(targetUrl, "targetUrl");
+    if (this.targetUrl.isBlank()) {
+        throw new IllegalArgumentException("targetUrl must not be blank");
+    }
     if (statusCode != 301 && statusCode != 302) {
         throw new IllegalArgumentException("statusCode must be 301 or 302");
     }
     this.statusCode = statusCode;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/server/RedirectRule.java` around lines 11 - 18, The
constructor RedirectRule(Pattern sourcePattern, String targetUrl, int
statusCode) currently only checks for null but allows empty or whitespace
targetUrl which can produce empty Location headers; after
Objects.requireNonNull(targetUrl, "targetUrl") add a validation that rejects
blank values (e.g. if targetUrl.trim().isEmpty() throw new
IllegalArgumentException("targetUrl must not be blank")) so the constructor
enforces a non-null, non-blank targetUrl while preserving the existing
statusCode check.
🧹 Nitpick comments (1)
src/main/java/org/example/server/FilterChain.java (1)

5-23: FilterChain statefulness is a design concern, but the current codebase doesn't manifest this issue.

The index field being mutable would indeed cause problems if FilterChain instances were reused across requests or shared concurrently. However, the current application code does not instantiate or use FilterChain at all—TcpServer only accepts and closes connections. Tests create fresh instances per test case. While the suggested refactor (making index immutable and creating new chain instances per filter step) is sound defensive design practice, there is no immediate bug to fix in the current codebase.

If FilterChain is to be integrated into the actual HTTP request handling pipeline, ensure each request gets a fresh instance, or adopt the immutable pattern shown in the proposed diff to prevent future reuse-related bugs.

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

In `@src/main/java/org/example/server/FilterChain.java` around lines 5 - 23,
FilterChain currently keeps mutable state in the field "index", which can cause
reuse/concurrency bugs; change the design so doFilter does not mutate shared
state: remove the "index" field and make doFilter accept an index parameter or
create a new FilterChain instance with the next index when delegating; update
the constructor and any callers to create fresh chains per request (refer to
class FilterChain, field index, constructor FilterChain(HttpFilter[] filters,
TerminalHandler terminal), and method doFilter(HttpRequest request, HttpResponse
response)) so each filter invocation uses an immutable/indexed chain rather than
mutating a single instance.
🤖 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/example/server/RedirectRule.java`:
- Around line 11-18: The constructor RedirectRule(Pattern sourcePattern, String
targetUrl, int statusCode) currently only checks for null but allows empty or
whitespace targetUrl which can produce empty Location headers; after
Objects.requireNonNull(targetUrl, "targetUrl") add a validation that rejects
blank values (e.g. if targetUrl.trim().isEmpty() throw new
IllegalArgumentException("targetUrl must not be blank")) so the constructor
enforces a non-null, non-blank targetUrl while preserving the existing
statusCode check.

---

Nitpick comments:
In `@src/main/java/org/example/server/FilterChain.java`:
- Around line 5-23: FilterChain currently keeps mutable state in the field
"index", which can cause reuse/concurrency bugs; change the design so doFilter
does not mutate shared state: remove the "index" field and make doFilter accept
an index parameter or create a new FilterChain instance with the next index when
delegating; update the constructor and any callers to create fresh chains per
request (refer to class FilterChain, field index, constructor
FilterChain(HttpFilter[] filters, TerminalHandler terminal), and method
doFilter(HttpRequest request, HttpResponse response)) so each filter invocation
uses an immutable/indexed chain rather than mutating a single instance.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ebfe583 and 42f23cd.

📒 Files selected for processing (8)
  • src/main/java/org/example/server/FilterChain.java
  • src/main/java/org/example/server/RedirectFilter.java
  • src/main/java/org/example/server/RedirectRule.java
  • src/main/java/org/example/server/RedirectRulesLoader.java
  • src/main/java/org/example/server/TerminalHandler.java
  • src/main/resources/.gitkeep
  • src/test/java/org/example/server/RedirectFilterTest.java
  • src/test/resources/.gitkeep

@Ericthilen
Copy link
Author

Created new pull request and added redirect and tests #95

@Ericthilen Ericthilen closed this Feb 25, 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.

URL Redirect Filter (301/302)

7 participants