From e3169160ac564599612ad5dd10f73371491cbba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20B=C3=A4r?= Date: Tue, 5 May 2026 09:12:57 +0200 Subject: [PATCH 1/2] feat: return the `vanillaFallback` option as an alternative for failing requests --- impit/src/errors.rs | 4 ++ impit/src/impit.rs | 112 +++++++++++++++++++++++++++++++++----------- 2 files changed, 89 insertions(+), 27 deletions(-) diff --git a/impit/src/errors.rs b/impit/src/errors.rs index 1a6ae737..8bcbf959 100644 --- a/impit/src/errors.rs +++ b/impit/src/errors.rs @@ -105,6 +105,10 @@ impl From for ImpitError { } impl ImpitError { + pub fn is_connect_error(&self) -> bool { + matches!(self, ImpitError::ConnectError(_)) + } + pub fn from(error: reqwest::Error, context: Option) -> Self { let context = context.unwrap_or_default(); if error.is_timeout() { diff --git a/impit/src/impit.rs b/impit/src/impit.rs index 1bd7a75c..6c0fed80 100644 --- a/impit/src/impit.rs +++ b/impit/src/impit.rs @@ -22,6 +22,7 @@ use crate::{ pub struct Impit { pub(self) base_client: reqwest::Client, pub(self) h3_client: Option, + pub(self) vanilla_client: Option, h3_engine: Arc>>, config: ImpitBuilder, } @@ -107,7 +108,7 @@ impl Default for ImpitBuilder Impit { })?; } + let vanilla_client = if config.vanilla_fallback && config.fingerprint.is_some() { + Some(Self::new_reqwest_client(&ImpitBuilder:: { + fingerprint: None, + max_http_version: Version::HTTP_2, + ..config.clone() + })?) + } else { + None + }; + // Set pseudo-header order from fingerprint or fall back to browser enum let pseudo_headers_order: Vec = if let Some(ref fingerprint) = config.fingerprint { fingerprint.http2.pseudo_header_order.to_vec() @@ -329,6 +340,7 @@ impl Impit { Ok(Impit { base_client, h3_client, + vanilla_client, config, h3_engine: Arc::new(RwLock::new(None)), }) @@ -401,6 +413,33 @@ impl Impit { } } + async fn execute_request( + &self, + client: &reqwest::Client, + method: Method, + url: Url, + headers: HeaderMap, + body: Option>, + timeout: Option, + h3: bool, + ) -> Result { + let mut req = client.request(method, url).headers(headers); + + if h3 { + req = req.version(Version::HTTP_3); + } + + if let Some(t) = timeout { + req = req.timeout(t); + } + + if let Some(b) = body { + req = req.body(b); + } + + req.send().await + } + async fn send( &self, request: ImpitRequest, @@ -426,40 +465,35 @@ impl Impit { &self.base_client }; - let header_map: Result = HttpHeaders::from(request.headers).into(); + let header_map_result: Result = + HttpHeaders::from(request.headers).into(); + let header_map = header_map_result?; let method = Method::from_str(&request.method).map_err(|_| { ImpitError::InvalidMethod(format!("Invalid HTTP method: {}", request.method)) })?; - let mut client_request = client - .request(method.clone(), request.url.clone()) - .headers(header_map?); - - if h3 { - client_request = client_request.version(Version::HTTP_3); - } - - if let Some(timeout) = timeout { - client_request = client_request.timeout(timeout); - } - - client_request = match request.body { - Some(body) => client_request.body(body), - None => client_request, + let max_redirects = match self.config.redirect { + RedirectBehavior::FollowRedirect(max) => max, + RedirectBehavior::ManualRedirect => 0, }; - let response = client_request.send().await; - - let response = match response { + let primary_result = self + .execute_request( + client, + method.clone(), + request.url.clone(), + header_map.clone(), + request.body.clone(), + timeout, + h3, + ) + .await; + + let response = match primary_result { Ok(resp) => resp, Err(err) => { - let max_redirects = match self.config.redirect { - RedirectBehavior::FollowRedirect(max) => max, - RedirectBehavior::ManualRedirect => 0, - }; - - return Err(ImpitError::from( + let primary_error = ImpitError::from( err, Some(ErrorContext { timeout: Some(timeout.unwrap_or(self.config.request_timeout)), @@ -468,7 +502,31 @@ impl Impit { protocol: Some(request.url.scheme().to_string()), url: Some(url.clone()), }), - )); + ); + + let fallback_client = self.vanilla_client.as_ref().filter(|_| primary_error.is_connect_error()); + let Some(vanilla_client) = fallback_client else { + return Err(primary_error); + }; + + debug!( + "Primary request to {url} failed with {primary_error}, retrying with vanilla client" + ); + match self + .execute_request( + vanilla_client, + method.clone(), + request.url.clone(), + header_map, + request.body, + timeout, + false, + ) + .await + { + Ok(resp) => resp, + Err(_) => return Err(primary_error), + } } }; From bc0975ee5ba61bcd6d0a851ef30cfcb4f7d27c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20B=C3=A4r?= Date: Tue, 5 May 2026 10:55:12 +0200 Subject: [PATCH 2/2] chore: fix linter / formatter errors --- impit/src/impit.rs | 64 +++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/impit/src/impit.rs b/impit/src/impit.rs index 6c0fed80..ba43d156 100644 --- a/impit/src/impit.rs +++ b/impit/src/impit.rs @@ -27,6 +27,13 @@ pub struct Impit { config: ImpitBuilder, } +struct PreparedRequest { + method: Method, + url: Url, + headers: HeaderMap, + body: Option>, +} + impl Default for Impit { fn default() -> Self { ImpitBuilder::::default().build().unwrap() @@ -314,11 +321,13 @@ impl Impit { } let vanilla_client = if config.vanilla_fallback && config.fingerprint.is_some() { - Some(Self::new_reqwest_client(&ImpitBuilder:: { - fingerprint: None, - max_http_version: Version::HTTP_2, - ..config.clone() - })?) + Some(Self::new_reqwest_client( + &ImpitBuilder:: { + fingerprint: None, + max_http_version: Version::HTTP_2, + ..config.clone() + }, + )?) } else { None }; @@ -416,14 +425,13 @@ impl Impit { async fn execute_request( &self, client: &reqwest::Client, - method: Method, - url: Url, - headers: HeaderMap, - body: Option>, + prepared: &PreparedRequest, timeout: Option, h3: bool, ) -> Result { - let mut req = client.request(method, url).headers(headers); + let mut req = client + .request(prepared.method.clone(), prepared.url.clone()) + .headers(prepared.headers.clone()); if h3 { req = req.version(Version::HTTP_3); @@ -433,7 +441,7 @@ impl Impit { req = req.timeout(t); } - if let Some(b) = body { + if let Some(b) = prepared.body.clone() { req = req.body(b); } @@ -478,17 +486,14 @@ impl Impit { RedirectBehavior::ManualRedirect => 0, }; - let primary_result = self - .execute_request( - client, - method.clone(), - request.url.clone(), - header_map.clone(), - request.body.clone(), - timeout, - h3, - ) - .await; + let prepared = PreparedRequest { + method: method.clone(), + url: request.url.clone(), + headers: header_map, + body: request.body, + }; + + let primary_result = self.execute_request(client, &prepared, timeout, h3).await; let response = match primary_result { Ok(resp) => resp, @@ -504,7 +509,10 @@ impl Impit { }), ); - let fallback_client = self.vanilla_client.as_ref().filter(|_| primary_error.is_connect_error()); + let fallback_client = self + .vanilla_client + .as_ref() + .filter(|_| primary_error.is_connect_error()); let Some(vanilla_client) = fallback_client else { return Err(primary_error); }; @@ -513,15 +521,7 @@ impl Impit { "Primary request to {url} failed with {primary_error}, retrying with vanilla client" ); match self - .execute_request( - vanilla_client, - method.clone(), - request.url.clone(), - header_map, - request.body, - timeout, - false, - ) + .execute_request(vanilla_client, &prepared, timeout, false) .await { Ok(resp) => resp,