@@ -22,10 +22,18 @@ use crate::{
2222pub struct Impit < CookieStoreImpl : CookieStore + ' static > {
2323 pub ( self ) base_client : reqwest:: Client ,
2424 pub ( self ) h3_client : Option < reqwest:: Client > ,
25+ pub ( self ) vanilla_client : Option < reqwest:: Client > ,
2526 h3_engine : Arc < RwLock < Option < H3Engine > > > ,
2627 config : ImpitBuilder < CookieStoreImpl > ,
2728}
2829
30+ struct PreparedRequest {
31+ method : Method ,
32+ url : Url ,
33+ headers : HeaderMap ,
34+ body : Option < Vec < u8 > > ,
35+ }
36+
2937impl < CookieStoreImpl : CookieStore + ' static > Default for Impit < CookieStoreImpl > {
3038 fn default ( ) -> Self {
3139 ImpitBuilder :: < CookieStoreImpl > :: default ( ) . build ( ) . unwrap ( )
@@ -107,7 +115,7 @@ impl<CookieStoreImpl: CookieStore + 'static> Default for ImpitBuilder<CookieStor
107115 ImpitBuilder {
108116 fingerprint : None ,
109117 ignore_tls_errors : false ,
110- vanilla_fallback : true ,
118+ vanilla_fallback : false ,
111119 proxy_url : String :: new ( ) ,
112120 request_timeout : Duration :: from_secs ( 30 ) ,
113121 max_http_version : Version :: HTTP_2 ,
@@ -312,6 +320,18 @@ impl<CookieStoreImpl: CookieStore + 'static> Impit<CookieStoreImpl> {
312320 } ) ?;
313321 }
314322
323+ let vanilla_client = if config. vanilla_fallback && config. fingerprint . is_some ( ) {
324+ Some ( Self :: new_reqwest_client (
325+ & ImpitBuilder :: < CookieStoreImpl > {
326+ fingerprint : None ,
327+ max_http_version : Version :: HTTP_2 ,
328+ ..config. clone ( )
329+ } ,
330+ ) ?)
331+ } else {
332+ None
333+ } ;
334+
315335 // Set pseudo-header order from fingerprint or fall back to browser enum
316336 let pseudo_headers_order: Vec < String > = if let Some ( ref fingerprint) = config. fingerprint {
317337 fingerprint. http2 . pseudo_header_order . to_vec ( )
@@ -329,6 +349,7 @@ impl<CookieStoreImpl: CookieStore + 'static> Impit<CookieStoreImpl> {
329349 Ok ( Impit {
330350 base_client,
331351 h3_client,
352+ vanilla_client,
332353 config,
333354 h3_engine : Arc :: new ( RwLock :: new ( None ) ) ,
334355 } )
@@ -401,6 +422,32 @@ impl<CookieStoreImpl: CookieStore + 'static> Impit<CookieStoreImpl> {
401422 }
402423 }
403424
425+ async fn execute_request (
426+ & self ,
427+ client : & reqwest:: Client ,
428+ prepared : & PreparedRequest ,
429+ timeout : Option < Duration > ,
430+ h3 : bool ,
431+ ) -> Result < Response , reqwest:: Error > {
432+ let mut req = client
433+ . request ( prepared. method . clone ( ) , prepared. url . clone ( ) )
434+ . headers ( prepared. headers . clone ( ) ) ;
435+
436+ if h3 {
437+ req = req. version ( Version :: HTTP_3 ) ;
438+ }
439+
440+ if let Some ( t) = timeout {
441+ req = req. timeout ( t) ;
442+ }
443+
444+ if let Some ( b) = prepared. body . clone ( ) {
445+ req = req. body ( b) ;
446+ }
447+
448+ req. send ( ) . await
449+ }
450+
404451 async fn send (
405452 & self ,
406453 request : ImpitRequest ,
@@ -426,40 +473,32 @@ impl<CookieStoreImpl: CookieStore + 'static> Impit<CookieStoreImpl> {
426473 & self . base_client
427474 } ;
428475
429- let header_map: Result < HeaderMap , ImpitError > = HttpHeaders :: from ( request. headers ) . into ( ) ;
476+ let header_map_result: Result < HeaderMap , ImpitError > =
477+ HttpHeaders :: from ( request. headers ) . into ( ) ;
478+ let header_map = header_map_result?;
430479
431480 let method = Method :: from_str ( & request. method ) . map_err ( |_| {
432481 ImpitError :: InvalidMethod ( format ! ( "Invalid HTTP method: {}" , request. method) )
433482 } ) ?;
434483
435- let mut client_request = client
436- . request ( method. clone ( ) , request. url . clone ( ) )
437- . headers ( header_map?) ;
438-
439- if h3 {
440- client_request = client_request. version ( Version :: HTTP_3 ) ;
441- }
442-
443- if let Some ( timeout) = timeout {
444- client_request = client_request. timeout ( timeout) ;
445- }
484+ let max_redirects = match self . config . redirect {
485+ RedirectBehavior :: FollowRedirect ( max) => max,
486+ RedirectBehavior :: ManualRedirect => 0 ,
487+ } ;
446488
447- client_request = match request. body {
448- Some ( body) => client_request. body ( body) ,
449- None => client_request,
489+ let prepared = PreparedRequest {
490+ method : method. clone ( ) ,
491+ url : request. url . clone ( ) ,
492+ headers : header_map,
493+ body : request. body ,
450494 } ;
451495
452- let response = client_request . send ( ) . await ;
496+ let primary_result = self . execute_request ( client , & prepared , timeout , h3 ) . await ;
453497
454- let response = match response {
498+ let response = match primary_result {
455499 Ok ( resp) => resp,
456500 Err ( err) => {
457- let max_redirects = match self . config . redirect {
458- RedirectBehavior :: FollowRedirect ( max) => max,
459- RedirectBehavior :: ManualRedirect => 0 ,
460- } ;
461-
462- return Err ( ImpitError :: from (
501+ let primary_error = ImpitError :: from (
463502 err,
464503 Some ( ErrorContext {
465504 timeout : Some ( timeout. unwrap_or ( self . config . request_timeout ) ) ,
@@ -468,7 +507,26 @@ impl<CookieStoreImpl: CookieStore + 'static> Impit<CookieStoreImpl> {
468507 protocol : Some ( request. url . scheme ( ) . to_string ( ) ) ,
469508 url : Some ( url. clone ( ) ) ,
470509 } ) ,
471- ) ) ;
510+ ) ;
511+
512+ let fallback_client = self
513+ . vanilla_client
514+ . as_ref ( )
515+ . filter ( |_| primary_error. is_connect_error ( ) ) ;
516+ let Some ( vanilla_client) = fallback_client else {
517+ return Err ( primary_error) ;
518+ } ;
519+
520+ debug ! (
521+ "Primary request to {url} failed with {primary_error}, retrying with vanilla client"
522+ ) ;
523+ match self
524+ . execute_request ( vanilla_client, & prepared, timeout, false )
525+ . await
526+ {
527+ Ok ( resp) => resp,
528+ Err ( _) => return Err ( primary_error) ,
529+ }
472530 }
473531 } ;
474532
0 commit comments