fix jable: FlaresolverrCookie missing fields + requester transport-error fallback

Three bugs prevented jable.tv from returning items:

1. FlaresolverrCookie deserialization (root cause): fields like `expires`,
   `size`, `priority`, `sameParty`, `sourceScheme`, `sourcePort` are
   omitted by FlareSolverr for session cookies, causing serde to fail with
   "missing field" and the entire solve to return Err. Added #[serde(default)]
   to all non-essential cookie fields.

2. Transport-error branch in get_with_headers: when direct fetch fails with
   a transport error and Jina returns a CF challenge body, the old code
   returned Ok(challenge_html) instead of falling through to FlareSolverr.
   Now checks looks_like_cloudflare_challenge and breaks to FS.

3. Post-FlareSolverr retry: wreq's Firefox136 TLS fingerprint causes CF to
   drop the connection even with FS cookies applied, so request.send()
   returns Err. Was propagated with `?`; now catches and returns
   res.solution.response (the headless-browser body) directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Simon
2026-06-26 20:43:56 +00:00
parent 03c4e4d3c4
commit 9caa5f7c01
2 changed files with 34 additions and 6 deletions

View File

@@ -19,15 +19,24 @@ pub struct FlaresolverrCookie {
pub value: String, pub value: String,
pub domain: String, pub domain: String,
pub path: String, pub path: String,
#[serde(default)]
pub expires: f64, pub expires: f64,
#[serde(default)]
pub size: u64, pub size: u64,
#[serde(default)]
pub httpOnly: bool, pub httpOnly: bool,
#[serde(default)]
pub secure: bool, pub secure: bool,
#[serde(default)]
pub session: bool, pub session: bool,
pub sameSite: Option<String>, pub sameSite: Option<String>,
#[serde(default)]
pub priority: String, pub priority: String,
#[serde(default)]
pub sameParty: bool, pub sameParty: bool,
#[serde(default)]
pub sourceScheme: String, pub sourceScheme: String,
#[serde(default)]
pub sourcePort: u32, pub sourcePort: u32,
pub partitionKey: Option<String>, pub partitionKey: Option<String>,
} }

View File

@@ -535,15 +535,25 @@ impl Requester {
} }
let response = match request.send().await { let response = match request.send().await {
Ok(response) => response, Ok(response) => response,
Err(error) => { Err(_error) => {
crate::flow_debug!( crate::flow_debug!(
"trace={} requester direct transport failed url={} error={} using_jina_fallback=true", "trace={} requester direct transport failed url={} error={} using_jina_fallback=true",
_trace_id, _trace_id,
crate::util::flow_debug::preview(url, 120), crate::util::flow_debug::preview(url, 120),
crate::util::flow_debug::preview(&error.to_string(), 160) crate::util::flow_debug::preview(&_error.to_string(), 160)
); );
match jina_handle.await { match jina_handle.await {
Ok(Ok(body)) => return Ok(body), Ok(Ok(body)) => {
if Self::looks_like_cloudflare_challenge(&body) {
crate::flow_debug!(
"trace={} requester jina fallback returned cloudflare challenge after transport error url={} using_flaresolverr=true",
_trace_id,
crate::util::flow_debug::preview(url, 120)
);
} else {
return Ok(body);
}
}
Ok(Err(_jina_error)) => { Ok(Err(_jina_error)) => {
crate::flow_debug!( crate::flow_debug!(
"trace={} requester jina fallback failed after transport error url={} error={}", "trace={} requester jina fallback failed after transport error url={} error={}",
@@ -551,7 +561,6 @@ impl Requester {
crate::util::flow_debug::preview(url, 120), crate::util::flow_debug::preview(url, 120),
crate::util::flow_debug::preview(&_jina_error.to_string(), 160) crate::util::flow_debug::preview(&_jina_error.to_string(), 160)
); );
return Err(error.into());
} }
Err(_join_error) => { Err(_join_error) => {
crate::flow_debug!( crate::flow_debug!(
@@ -560,9 +569,9 @@ impl Requester {
crate::util::flow_debug::preview(url, 120), crate::util::flow_debug::preview(url, 120),
crate::util::flow_debug::preview(&_join_error.to_string(), 160) crate::util::flow_debug::preview(&_join_error.to_string(), 160)
); );
return Err(error.into());
} }
} }
break;
} }
}; };
self.store_response_cookies(url, &response); self.store_response_cookies(url, &response);
@@ -714,7 +723,17 @@ impl Requester {
} }
} }
let response = request.send().await?; let response = match request.send().await {
Ok(r) => r,
Err(_retry_err) => {
crate::flow_debug!(
"trace={} requester post-flaresolverr retry transport failed url={} using_flaresolverr_body=true",
self.debug_trace_id().unwrap_or("none"),
crate::util::flow_debug::preview(url, 120)
);
return Ok(res.solution.response);
}
};
self.store_response_cookies(url, &response); self.store_response_cookies(url, &response);
crate::flow_debug!( crate::flow_debug!(
"trace={} requester retry response url={} status={}", "trace={} requester retry response url={} status={}",