88
99import jstyleson as json
1010from requests import PreparedRequest , Response , Session
11+ from requests .adapters import HTTPAdapter
12+ from urllib3 .util .retry import Retry
1113
1214# this is bad, loading private stuff. find a better way
1315from requests .exceptions import SSLError
@@ -123,7 +125,7 @@ def get_cookie(self):
123125
124126 def get_session (self ):
125127 if self .httpdef .session_clear :
126- # calle should close session
128+ # caller should close session
127129 # TODO
128130 return get_new_session ()
129131 session = self .global_session
@@ -346,6 +348,30 @@ def format_http(http: Http, property_util: Optional[PropertyProvider]=None):
346348 output_str += f'{ new_line } azurespcert("tenant_id={ cert_auth .tenant_id } ", client_id="{ cert_auth .client_id } ", client_secret="{ cert_auth .certificate_path } ", scope="{ cert_auth .scope } ")'
347349 else :
348350 output_str += f'{ new_line } azurecli( scope = "{ azure_auth .scope } ")'
351+
352+ # Format timeout
353+ if timeout := http .timeout :
354+ output_str += f'{ new_line } timeout({ timeout .timeout_seconds } )'
355+
356+ # Format retry
357+ if retry := http .retry :
358+ if retry .retry_params :
359+ params = []
360+ for param in retry .retry_params :
361+ if param .total :
362+ params .append (f'total={ param .total } ' )
363+ if param .backoff_factor :
364+ params .append (f'backoff_factor={ param .backoff_factor } ' )
365+ if param .status_forcelist :
366+ codes = ', ' .join (param .status_forcelist )
367+ params .append (f'status_forcelist=[{ codes } ]' )
368+ if params :
369+ output_str += f'{ new_line } retry({ ", " .join (params )} )'
370+
371+ # Format proxy
372+ if proxy := http .proxy :
373+ output_str += f'{ new_line } proxy({ apply_quote_or_unquote (proxy .proxy )} )'
374+
349375 if lines := http .lines :
350376
351377 def check_for_quotes (line ):
@@ -555,10 +581,59 @@ def func(data):
555581 )
556582 eprint ("output file close failed" )
557583
584+ def _create_retry_adapter (self ):
585+ """
586+ Create a retry adapter based on httpdef retry settings.
587+
588+ Returns HTTPAdapter with retry configuration, or None if no retry is configured.
589+ This adapter can be used directly with adapter.send() without mounting on session.
590+
591+ Passes parameters directly to urllib3.Retry without additional processing.
592+ """
593+ # Check if any retry configuration exists
594+ if self .httpdef .retry_total is None :
595+ return None # No retry configuration
596+
597+ # Build kwargs for Retry, passing only configured parameters
598+ retry_kwargs = {}
599+
600+ if self .httpdef .retry_total is not None :
601+ retry_kwargs ['total' ] = self .httpdef .retry_total
602+
603+ if self .httpdef .retry_status_forcelist is not None :
604+ retry_kwargs ['status_forcelist' ] = self .httpdef .retry_status_forcelist
605+
606+ if self .httpdef .retry_backoff_factor is not None :
607+ retry_kwargs ['backoff_factor' ] = self .httpdef .retry_backoff_factor
608+
609+ # Always set raise_on_status=False for API testing tools
610+ # Users want to see the response regardless of status code
611+ retry_kwargs ['raise_on_status' ] = False
612+
613+ # Create retry strategy with configured parameters
614+ retry_strategy = Retry (** retry_kwargs )
615+
616+ # Create adapter with retry strategy
617+ adapter = HTTPAdapter (max_retries = retry_strategy )
618+
619+ request_logger .debug (f"Retry adapter created with: { retry_kwargs } " )
620+
621+ return adapter
622+
558623 def get_response (self ):
624+ """
625+ Get HTTP response with optional retry support via urllib3.Retry.
626+
627+ Uses adapter.send() directly if retry is configured, avoiding session
628+ state modification and ensuring thread-safety in concurrent environments.
629+ """
559630 session = self .get_session ()
560631 request = self .get_request ()
561632 session .cookies = request ._cookies
633+
634+ # Create retry adapter if configured (doesn't modify session)
635+ retry_adapter = self ._create_retry_adapter ()
636+
562637 if self .httpdef .p12 :
563638 session .mount (
564639 request .url ,
@@ -572,25 +647,56 @@ def get_response(self):
572647 cert = tuple (self .httpdef .certificate )
573648 else :
574649 cert = None
575- resp : Response = session .send (
576- request ,
577- cert = cert ,
578- verify = not self .httpdef .allow_insecure ,
579- proxies = self .httpdef .proxy ,
580- # stream=True
581- )
650+
651+ # Prepare kwargs for session.send
652+ send_kwargs = {
653+ 'cert' : cert ,
654+ 'verify' : not self .httpdef .allow_insecure ,
655+ }
656+
657+ # Handle proxy configuration
658+ # Priority: custom_proxy (from DSL) > proxy (from named_args)
659+ if self .httpdef .custom_proxy :
660+ send_kwargs ['proxies' ] = {
661+ 'http' : self .httpdef .custom_proxy ,
662+ 'https' : self .httpdef .custom_proxy ,
663+ }
664+ request_logger .debug (f"Using custom proxy: { self .httpdef .custom_proxy } " )
665+ elif self .httpdef .proxy :
666+ send_kwargs ['proxies' ] = self .httpdef .proxy
667+
668+ # Add timeout if configured
669+ if self .httpdef .timeout :
670+ send_kwargs ['timeout' ] = self .httpdef .timeout
671+
672+ # Use retry adapter if configured, otherwise use session.send()
673+ if retry_adapter :
674+ # Call adapter.send() directly - no need to mount on session!
675+ # This keeps session state clean and is thread-safe
676+ resp : Response = retry_adapter .send (request , ** send_kwargs )
677+ else :
678+ # Normal request without retry
679+ resp : Response = session .send (request , ** send_kwargs )
582680 except UnicodeEncodeError :
583681 # for Chinese, smiley all other default encode converts into latin-1
584682 # as latin-1 didn't consist of those characters it will fail
585683 # in those scenarios, request will try to encode with utf-8
586684 # as a last resort, it may not be correct solution. may be it is.
587685 # for now proceeding with this
588686 request .prepare_body (request .body .encode ("utf-8" ), files = None )
589- resp : Response = session .send (
590- request ,
591- cert = self .httpdef .certificate ,
592- verify = not self .httpdef .allow_insecure ,
593- )
687+
688+ send_kwargs = {
689+ 'cert' : self .httpdef .certificate ,
690+ 'verify' : not self .httpdef .allow_insecure ,
691+ }
692+ if self .httpdef .timeout :
693+ send_kwargs ['timeout' ] = self .httpdef .timeout
694+
695+ # Use retry adapter if configured
696+ if retry_adapter :
697+ resp : Response = retry_adapter .send (request , ** send_kwargs )
698+ else :
699+ resp : Response = session .send (request , ** send_kwargs )
594700 # in case of ssl self signed error, try to catch it and add more info
595701 # to user
596702 except SSLError as e :
0 commit comments