Skip to content

fix(http): CF Challenge fail and implement Happy Eyeballs - #90

Merged
YueMiyuki merged 3 commits into
masterfrom
next-dev
May 30, 2026
Merged

fix(http): CF Challenge fail and implement Happy Eyeballs#90
YueMiyuki merged 3 commits into
masterfrom
next-dev

Conversation

@YueMiyuki

@YueMiyuki YueMiyuki commented May 30, 2026

Copy link
Copy Markdown
Owner

Summary by cubic

Implements Happy Eyeballs v2 in the TCP connector for faster, more reliable IPv6/IPv4 connects, and adds clearer diagnostics when Cloudflare blocks a request to make failures easier to debug.

  • New Features
    • Happy Eyeballs (RFC 8305): IPv6-first interleaving, 300 ms staggered attempts, per-attempt connect_timeout, first-success wins, and debug logging of the selected family; adds tests for interleaving order, failover, all-fail, and local listener success.
    • Cloudflare challenge diagnostics: centralized warning log with sanitized URIs (no query/fragment/credentials), effective cookies from header or client jar (names, length, cf_clearance presence), request UA, and response headers (cf-ray, cf-mitigated, cf-cache-status, server) across probe, piece-stream, and single-download paths.

Written for commit 7b52af8. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Improvements

    • Faster, more reliable network connections with IPv6-first, parallel connection attempts and improved failover.
    • Better detection and handling of Cloudflare-like blocks, producing clearer diagnostics when downloads or streaming are blocked.
    • Improved cookie handling for HTTP clients, enhancing compatibility with sites that rely on cookies.
  • Chores

    • Internal refactors and enhanced diagnostic logging to aid troubleshooting.

Review Change Stack

Copilot AI review requested due to automatic review settings May 30, 2026 13:49
@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f0c1d975-d70a-47b7-b2f8-b3978e754911

📥 Commits

Reviewing files that changed from the base of the PR and between f5164d9 and 7b52af8.

📒 Files selected for processing (2)
  • src-tauri/risuko-engine/src/engine/http.rs
  • src-tauri/risuko-http/src/client.rs

📝 Walkthrough

Walkthrough

Implements an IPv6-first staggered-parallel (Happy Eyeballs v2) TCP connector, adds a structured Cloudflare diagnostic logger invoked in three download paths, and applies small formatting refactors to DoH option handling and a Vue conditional.

Changes

Connection Strategy and Diagnostic Improvements

Layer / File(s) Summary
Happy Eyeballs v2 Implementation
src-tauri/risuko-http/src/connector.rs
TCP connector now resolves and interleaves addresses by IP family (IPv6 first) and uses staggered-parallel connection with 300ms stagger delays, returning the first successful stream; adds connect_one and interleave_by_family, and includes unit/async tests for ordering, success/failover/exhaustion.
Cloudflare Diagnostic Logging
src-tauri/risuko-engine/src/engine/http.rs, src-tauri/risuko-http/src/client.rs
New private log_cloudflare_diagnostic(...) sanitizes the request URI, derives cookie names and cf_clearance presence (from outgoing Cookie header or client jar via Client::jar_cookies), extracts User-Agent and selected response headers (cf-ray, cf-mitigated, cf-cache-status, server), and emits a structured tracing::warn!; invoked in probe_range_support, download_piece_stream, and run_single_download before returning Cloudflare marker errors.
Code Formatting
src-tauri/src/cli/commands.rs, src/renderer/components/Preference/Advanced.vue
DoH option keys array refactored from inline to multi-line literal in do_download; DoH provider validation conditional reformatted to multi-line expression without logic changes.

Sequence Diagram

sequenceDiagram
  participant Resolver as DNS resolver
  participant Interleaver as interleave_by_family
  participant Connector as staggered_parallel_connector
  participant Attempt as connect_one
  participant Stream as TcpStream

  Resolver->>Interleaver: resolved IPv6 & IPv4 addrs
  Interleaver->>Connector: IPv6-first ordered list
  Connector->>Attempt: start first attempt
  Attempt-->>Connector: success (TcpStream) / error
  alt success
    Connector-->>Stream: return tuned TcpStream
  else
    Connector->>Attempt: start next attempt after 300ms
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • YueMiyuki/Risuko#80: Modifies engine Cloudflare handling; likely related to the Cloudflare retry/cookie flow that this PR instruments.

Poem

🐰 I scoped the nets with nimble paws,
IPv6 first, I counted the laws,
Happy Eyeballs chase the stream,
Cloudflare hints in one log-gleam,
Downloads hum beneath moonlight's claws.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the two main changes: fixing Cloudflare challenge failure detection with enhanced diagnostics and implementing Happy Eyeballs v2 for IPv6-first dual-stack connectivity.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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 added the next The "next" steps label May 30, 2026

This comment was marked as low quality.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/risuko-engine/src/engine/http.rs`:
- Around line 1219-1260: The function log_cloudflare_diagnostic currently logs
the full uri (potentially leaking tokens); update it to sanitize the uri before
logging by parsing the uri (use Url::parse) and removing query, fragment, and
any userinfo (username/password) or, if parsing fails, by stripping everything
after the first '?' or '#' as a fallback, assign that sanitized string to a new
variable (e.g., sanitized_uri) and use sanitized_uri in the tracing::warn call
instead of uri; keep all other logged fields (cookie_names, cookie_len,
has_cf_clearance, sent_ua, and resp_str values) unchanged.

In `@src-tauri/risuko-http/src/connector.rs`:
- Around line 257-300: The loop currently recreates stagger =
tokio::time::sleep(ATTEMPT_DELAY) on every iteration which resets the stagger
whenever an in-flight attempt finishes; instead, create a single Sleep before
the loop (e.g. let mut stagger = tokio::time::sleep(ATTEMPT_DELAY);
tokio::pin!(stagger);) and inside the select await &mut stagger so the timer
continues across iterations; when the timer completes push the next
connect_one(addr, timeout) and then reset the existing Sleep by assigning a new
sleep to the pinned variable (stagger.set(tokio::time::sleep(ATTEMPT_DELAY))) so
ATTEMPT_DELAY remains a steady cadence while still using in_flight, remaining,
connect_one, and ATTEMPT_DELAY as currently named.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ffe2ca0f-1dce-4708-9f98-5632e7541c11

📥 Commits

Reviewing files that changed from the base of the PR and between 7613156 and 071761e.

📒 Files selected for processing (4)
  • src-tauri/risuko-engine/src/engine/http.rs
  • src-tauri/risuko-http/src/connector.rs
  • src-tauri/src/cli/commands.rs
  • src/renderer/components/Preference/Advanced.vue

Comment thread src-tauri/risuko-engine/src/engine/http.rs Outdated
Comment thread src-tauri/risuko-http/src/connector.rs

@cubic-dev-ai cubic-dev-ai 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.

3 issues found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src-tauri/risuko-http/src/connector.rs">

<violation number="1" location="src-tauri/risuko-http/src/connector.rs:604">
P3: Using hardcoded "dead" ports (1/2) makes the new async tests environment-dependent and potentially flaky.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src-tauri/risuko-engine/src/engine/http.rs Outdated
Comment thread src-tauri/risuko-http/src/connector.rs Outdated
async fn happy_eyeballs_fails_over_to_second_address() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let live = listener.local_addr().unwrap();
let dead: SocketAddr = "127.0.0.1:1".parse().unwrap();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Using hardcoded "dead" ports (1/2) makes the new async tests environment-dependent and potentially flaky.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src-tauri/risuko-http/src/connector.rs, line 604:

<comment>Using hardcoded "dead" ports (1/2) makes the new async tests environment-dependent and potentially flaky.</comment>

<file context>
@@ -434,6 +528,98 @@ fn percent_decode_str(s: &str) -> String {
+    async fn happy_eyeballs_fails_over_to_second_address() {
+        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
+        let live = listener.local_addr().unwrap();
+        let dead: SocketAddr = "127.0.0.1:1".parse().unwrap();
+        let c = test_connector();
+        let stream = c
</file context>

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

Caution

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

⚠️ Outside diff range comments (1)
src-tauri/risuko-engine/src/engine/http.rs (1)

1242-1255: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Include cookie-jar cookies in this diagnostic.

req_headers.get(COOKIE) only reflects manually injected headers. This client also sends cookies via cookie_provider(jar), so a cf_clearance loaded from load-cookies or set by earlier responses will be logged as missing even when it was actually sent. That makes these Cloudflare diagnostics misleading on the main path this PR is trying to debug.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src-tauri/risuko-engine/src/engine/http.rs` around lines 1242 - 1255, The
diagnostic currently inspects only req_headers.get(COOKIE) so cf_clearance
cookies that come from the cookie jar (via cookie_provider(jar) or load-cookies)
are missed; update the logic that builds cookie_names, cookie_len and
has_cf_clearance to also read cookies from the cookie jar (the same jar used by
cookie_provider), merge those cookie strings with the manual COOKIE header
(req_headers.get(COOKIE)), parse the combined string into names, compute
cookie_len from the combined header length, and set has_cf_clearance by checking
the merged names for "cf_clearance" (keep using the existing variables
cookie_names, cookie_len, has_cf_clearance so callers remain unchanged).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src-tauri/risuko-engine/src/engine/http.rs`:
- Around line 1242-1255: The diagnostic currently inspects only
req_headers.get(COOKIE) so cf_clearance cookies that come from the cookie jar
(via cookie_provider(jar) or load-cookies) are missed; update the logic that
builds cookie_names, cookie_len and has_cf_clearance to also read cookies from
the cookie jar (the same jar used by cookie_provider), merge those cookie
strings with the manual COOKIE header (req_headers.get(COOKIE)), parse the
combined string into names, compute cookie_len from the combined header length,
and set has_cf_clearance by checking the merged names for "cf_clearance" (keep
using the existing variables cookie_names, cookie_len, has_cf_clearance so
callers remain unchanged).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8b65ef71-0d0e-40f9-8d3f-c0696f66bf71

📥 Commits

Reviewing files that changed from the base of the PR and between 071761e and f5164d9.

📒 Files selected for processing (2)
  • src-tauri/risuko-engine/src/engine/http.rs
  • src-tauri/risuko-http/src/connector.rs

@YueMiyuki
YueMiyuki merged commit e36baeb into master May 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

next The "next" steps

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants