55import abc
66import logging
77import json
8- from splitio .optional .loaders import HTTPKerberosAuth , OPTIONAL
8+ import threading
9+ from urllib3 .util import parse_url
910
11+ from splitio .optional .loaders import HTTPKerberosAuth , OPTIONAL
1012from splitio .client .config import AuthenticateScheme
1113from splitio .optional .loaders import aiohttp
1214from splitio .util .time import get_current_epoch_time_ms
@@ -69,6 +71,24 @@ def __init__(self, message):
6971 """
7072 Exception .__init__ (self , message )
7173
74+ class HTTPAdapterWithProxyKerberosAuth (requests .adapters .HTTPAdapter ):
75+ """HTTPAdapter override for Kerberos Proxy auth"""
76+
77+ def __init__ (self , principal = None , password = None ):
78+ requests .adapters .HTTPAdapter .__init__ (self )
79+ self ._principal = principal
80+ self ._password = password
81+
82+ def proxy_headers (self , proxy ):
83+ headers = {}
84+ if self ._principal is not None :
85+ auth = HTTPKerberosAuth (principal = self ._principal , password = self ._password )
86+ else :
87+ auth = HTTPKerberosAuth ()
88+ negotiate_details = auth .generate_request_header (None , parse_url (proxy ).host , is_preemptive = True )
89+ headers ['Proxy-Authorization' ] = negotiate_details
90+ return headers
91+
7292class HttpClientBase (object , metaclass = abc .ABCMeta ):
7393 """HttpClient wrapper template."""
7494
@@ -93,6 +113,11 @@ def set_telemetry_data(self, metric_name, telemetry_runtime_producer):
93113 self ._telemetry_runtime_producer = telemetry_runtime_producer
94114 self ._metric_name = metric_name
95115
116+ def _get_headers (self , extra_headers , sdk_key ):
117+ headers = _build_basic_headers (sdk_key )
118+ if extra_headers is not None :
119+ headers .update (extra_headers )
120+ return headers
96121
97122class HttpClient (HttpClientBase ):
98123 """HttpClient wrapper."""
@@ -112,10 +137,12 @@ def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, t
112137 :param telemetry_url: Optional alternative telemetry URL.
113138 :type telemetry_url: str
114139 """
140+ _LOGGER .debug ("Initializing httpclient" )
115141 self ._timeout = timeout / 1000 if timeout else None # Convert ms to seconds.
142+ self ._urls = _construct_urls (sdk_url , events_url , auth_url , telemetry_url )
116143 self ._authentication_scheme = authentication_scheme
117144 self ._authentication_params = authentication_params
118- self ._urls = _construct_urls ( sdk_url , events_url , auth_url , telemetry_url )
145+ self ._lock = threading . RLock ( )
119146
120147 def get (self , server , path , sdk_key , query = None , extra_headers = None ): # pylint: disable=too-many-arguments
121148 """
@@ -135,25 +162,22 @@ def get(self, server, path, sdk_key, query=None, extra_headers=None): # pylint:
135162 :return: Tuple of status_code & response text
136163 :rtype: HttpResponse
137164 """
138- headers = _build_basic_headers (sdk_key )
139- if extra_headers is not None :
140- headers .update (extra_headers )
141-
142- authentication = self ._get_authentication ()
143- start = get_current_epoch_time_ms ()
144- try :
145- response = requests .get (
146- _build_url (server , path , self ._urls ),
147- params = query ,
148- headers = headers ,
149- timeout = self ._timeout ,
150- auth = authentication
151- )
152- self ._record_telemetry (response .status_code , get_current_epoch_time_ms () - start )
153- return HttpResponse (response .status_code , response .text , response .headers )
154-
155- except Exception as exc : # pylint: disable=broad-except
156- raise HttpClientException ('requests library is throwing exceptions' ) from exc
165+ with self ._lock :
166+ start = get_current_epoch_time_ms ()
167+ with requests .Session () as session :
168+ self ._set_authentication (session )
169+ try :
170+ response = session .get (
171+ _build_url (server , path , self ._urls ),
172+ params = query ,
173+ headers = self ._get_headers (extra_headers , sdk_key ),
174+ timeout = self ._timeout
175+ )
176+ self ._record_telemetry (response .status_code , get_current_epoch_time_ms () - start )
177+ return HttpResponse (response .status_code , response .text , response .headers )
178+
179+ except Exception as exc : # pylint: disable=broad-except
180+ raise HttpClientException ('requests library is throwing exceptions' ) from exc
157181
158182 def post (self , server , path , sdk_key , body , query = None , extra_headers = None ): # pylint: disable=too-many-arguments
159183 """
@@ -175,36 +199,37 @@ def post(self, server, path, sdk_key, body, query=None, extra_headers=None): #
175199 :return: Tuple of status_code & response text
176200 :rtype: HttpResponse
177201 """
178- headers = _build_basic_headers (sdk_key )
179-
180- if extra_headers is not None :
181- headers .update (extra_headers )
182-
183- authentication = self ._get_authentication ()
184- start = get_current_epoch_time_ms ()
185- try :
186- response = requests .post (
187- _build_url (server , path , self ._urls ),
188- json = body ,
189- params = query ,
190- headers = headers ,
191- timeout = self ._timeout ,
192- auth = authentication
193- )
194- self ._record_telemetry (response .status_code , get_current_epoch_time_ms () - start )
195- return HttpResponse (response .status_code , response .text , response .headers )
196-
197- except Exception as exc : # pylint: disable=broad-except
198- raise HttpClientException ('requests library is throwing exceptions' ) from exc
199-
200- def _get_authentication (self ):
201- authentication = None
202- if self ._authentication_scheme == AuthenticateScheme .KERBEROS :
203- if self ._authentication_params is not None :
204- authentication = HTTPKerberosAuth (principal = self ._authentication_params [0 ], password = self ._authentication_params [1 ], mutual_authentication = OPTIONAL )
202+ with self ._lock :
203+ start = get_current_epoch_time_ms ()
204+ with requests .Session () as session :
205+ self ._set_authentication (session )
206+ try :
207+ response = session .post (
208+ _build_url (server , path , self ._urls ),
209+ json = body ,
210+ params = query ,
211+ headers = self ._get_headers (extra_headers , sdk_key ),
212+ timeout = self ._timeout ,
213+ )
214+ self ._record_telemetry (response .status_code , get_current_epoch_time_ms () - start )
215+ return HttpResponse (response .status_code , response .text , response .headers )
216+ except Exception as exc : # pylint: disable=broad-except
217+ raise HttpClientException ('requests library is throwing exceptions' ) from exc
218+
219+ def _set_authentication (self , session ):
220+ if self ._authentication_scheme == AuthenticateScheme .KERBEROS_SPNEGO :
221+ _LOGGER .debug ("Using Kerberos Spnego Authentication" )
222+ if self ._authentication_params != [None , None ]:
223+ session .auth = HTTPKerberosAuth (principal = self ._authentication_params [0 ], password = self ._authentication_params [1 ], mutual_authentication = OPTIONAL )
224+ else :
225+ session .auth = HTTPKerberosAuth (mutual_authentication = OPTIONAL )
226+ elif self ._authentication_scheme == AuthenticateScheme .KERBEROS_PROXY :
227+ _LOGGER .debug ("Using Kerberos Proxy Authentication" )
228+ if self ._authentication_params != [None , None ]:
229+ session .mount ('https://' , HTTPAdapterWithProxyKerberosAuth (principal = self ._authentication_params [0 ], password = self ._authentication_params [1 ]))
205230 else :
206- authentication = HTTPKerberosAuth ( mutual_authentication = OPTIONAL )
207- return authentication
231+ session . mount ( 'https://' , HTTPAdapterWithProxyKerberosAuth () )
232+
208233
209234 def _record_telemetry (self , status_code , elapsed ):
210235 """
@@ -220,8 +245,8 @@ def _record_telemetry(self, status_code, elapsed):
220245 if 200 <= status_code < 300 :
221246 self ._telemetry_runtime_producer .record_successful_sync (self ._metric_name , get_current_epoch_time_ms ())
222247 return
223- self ._telemetry_runtime_producer .record_sync_error (self ._metric_name , status_code )
224248
249+ self ._telemetry_runtime_producer .record_sync_error (self ._metric_name , status_code )
225250
226251class HttpClientAsync (HttpClientBase ):
227252 """HttpClientAsync wrapper."""
@@ -260,10 +285,8 @@ async def get(self, server, path, apikey, query=None, extra_headers=None): # py
260285 :return: Tuple of status_code & response text
261286 :rtype: HttpResponse
262287 """
263- headers = _build_basic_headers (apikey )
264- if extra_headers is not None :
265- headers .update (extra_headers )
266288 start = get_current_epoch_time_ms ()
289+ headers = self ._get_headers (extra_headers , apikey )
267290 try :
268291 url = _build_url (server , path , self ._urls )
269292 _LOGGER .debug ("GET request: %s" , url )
@@ -303,9 +326,7 @@ async def post(self, server, path, apikey, body, query=None, extra_headers=None)
303326 :return: Tuple of status_code & response text
304327 :rtype: HttpResponse
305328 """
306- headers = _build_basic_headers (apikey )
307- if extra_headers is not None :
308- headers .update (extra_headers )
329+ headers = self ._get_headers (extra_headers , apikey )
309330 start = get_current_epoch_time_ms ()
310331 try :
311332 headers ['Accept-Encoding' ] = 'gzip'
0 commit comments