55import abc
66import logging
77import json
8- from splitio .optional .loaders import HTTPKerberosAuth , OPTIONAL
8+ import time
9+ import threading
10+ from urllib3 .util import parse_url
911
12+ from splitio .optional .loaders import HTTPKerberosAuth , OPTIONAL
1013from splitio .client .config import AuthenticateScheme
1114from splitio .optional .loaders import aiohttp
1215from splitio .util .time import get_current_epoch_time_ms
@@ -69,6 +72,24 @@ def __init__(self, message):
6972 """
7073 Exception .__init__ (self , message )
7174
75+ class HTTPAdapterWithProxyKerberosAuth (requests .adapters .HTTPAdapter ):
76+ """HTTPAdapter override for Kerberos Proxy auth"""
77+
78+ def __init__ (self , principal = None , password = None ):
79+ requests .adapters .HTTPAdapter .__init__ (self )
80+ self ._principal = principal
81+ self ._password = password
82+
83+ def proxy_headers (self , proxy ):
84+ headers = {}
85+ if self ._principal is not None :
86+ auth = HTTPKerberosAuth (principal = self ._principal , password = self ._password )
87+ else :
88+ auth = HTTPKerberosAuth ()
89+ negotiate_details = auth .generate_request_header (None , parse_url (proxy ).host , is_preemptive = True )
90+ headers ['Proxy-Authorization' ] = negotiate_details
91+ return headers
92+
7293class HttpClientBase (object , metaclass = abc .ABCMeta ):
7394 """HttpClient wrapper template."""
7495
@@ -93,6 +114,11 @@ def set_telemetry_data(self, metric_name, telemetry_runtime_producer):
93114 self ._telemetry_runtime_producer = telemetry_runtime_producer
94115 self ._metric_name = metric_name
95116
117+ def _get_headers (self , extra_headers , sdk_key ):
118+ headers = _build_basic_headers (sdk_key )
119+ if extra_headers is not None :
120+ headers .update (extra_headers )
121+ return headers
96122
97123class HttpClient (HttpClientBase ):
98124 """HttpClient wrapper."""
@@ -112,10 +138,12 @@ def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, t
112138 :param telemetry_url: Optional alternative telemetry URL.
113139 :type telemetry_url: str
114140 """
141+ _LOGGER .debug ("Initializing httpclient" )
115142 self ._timeout = timeout / 1000 if timeout else None # Convert ms to seconds.
143+ self ._urls = _construct_urls (sdk_url , events_url , auth_url , telemetry_url )
116144 self ._authentication_scheme = authentication_scheme
117145 self ._authentication_params = authentication_params
118- self ._urls = _construct_urls ( sdk_url , events_url , auth_url , telemetry_url )
146+ self ._lock = threading . RLock ( )
119147
120148 def get (self , server , path , sdk_key , query = None , extra_headers = None ): # pylint: disable=too-many-arguments
121149 """
@@ -135,25 +163,22 @@ def get(self, server, path, sdk_key, query=None, extra_headers=None): # pylint:
135163 :return: Tuple of status_code & response text
136164 :rtype: HttpResponse
137165 """
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
166+ with self ._lock :
167+ start = get_current_epoch_time_ms ()
168+ with requests .Session () as session :
169+ self ._set_authentication (session )
170+ try :
171+ response = session .get (
172+ _build_url (server , path , self ._urls ),
173+ params = query ,
174+ headers = self ._get_headers (extra_headers , sdk_key ),
175+ timeout = self ._timeout
176+ )
177+ self ._record_telemetry (response .status_code , get_current_epoch_time_ms () - start )
178+ return HttpResponse (response .status_code , response .text , response .headers )
179+
180+ except Exception as exc : # pylint: disable=broad-except
181+ raise HttpClientException ('requests library is throwing exceptions' ) from exc
157182
158183 def post (self , server , path , sdk_key , body , query = None , extra_headers = None ): # pylint: disable=too-many-arguments
159184 """
@@ -175,36 +200,37 @@ def post(self, server, path, sdk_key, body, query=None, extra_headers=None): #
175200 :return: Tuple of status_code & response text
176201 :rtype: HttpResponse
177202 """
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 )
203+ with self ._lock :
204+ start = get_current_epoch_time_ms ()
205+ with requests .Session () as session :
206+ self ._set_authentication (session )
207+ try :
208+ response = session .post (
209+ _build_url (server , path , self ._urls ),
210+ json = body ,
211+ params = query ,
212+ headers = self ._get_headers (extra_headers , sdk_key ),
213+ timeout = self ._timeout ,
214+ )
215+ self ._record_telemetry (response .status_code , get_current_epoch_time_ms () - start )
216+ return HttpResponse (response .status_code , response .text , response .headers )
217+ except Exception as exc : # pylint: disable=broad-except
218+ raise HttpClientException ('requests library is throwing exceptions' ) from exc
219+
220+ def _set_authentication (self , session ):
221+ if self ._authentication_scheme == AuthenticateScheme .KERBEROS_SPNEGO :
222+ _LOGGER .debug ("Using Kerberos Spnego Authentication" )
223+ if self ._authentication_params is not [None , None ]:
224+ session .auth = HTTPKerberosAuth (principal = self ._authentication_params [0 ], password = self ._authentication_params [1 ], mutual_authentication = OPTIONAL )
225+ else :
226+ session .auth = HTTPKerberosAuth (mutual_authentication = OPTIONAL )
227+ elif self ._authentication_scheme == AuthenticateScheme .KERBEROS_PROXY :
228+ _LOGGER .debug ("Using Kerberos Proxy Authentication" )
229+ if self ._authentication_params is not [None , None ]:
230+ session .mount ('https://' , HTTPAdapterWithProxyKerberosAuth (principal = self ._authentication_params [0 ], password = self ._authentication_params [1 ]))
205231 else :
206- authentication = HTTPKerberosAuth ( mutual_authentication = OPTIONAL )
207- return authentication
232+ session . mount ( 'https://' , HTTPAdapterWithProxyKerberosAuth () )
233+
208234
209235 def _record_telemetry (self , status_code , elapsed ):
210236 """
@@ -220,8 +246,8 @@ def _record_telemetry(self, status_code, elapsed):
220246 if 200 <= status_code < 300 :
221247 self ._telemetry_runtime_producer .record_successful_sync (self ._metric_name , get_current_epoch_time_ms ())
222248 return
223- self ._telemetry_runtime_producer .record_sync_error (self ._metric_name , status_code )
224249
250+ self ._telemetry_runtime_producer .record_sync_error (self ._metric_name , status_code )
225251
226252class HttpClientAsync (HttpClientBase ):
227253 """HttpClientAsync wrapper."""
@@ -260,10 +286,8 @@ async def get(self, server, path, apikey, query=None, extra_headers=None): # py
260286 :return: Tuple of status_code & response text
261287 :rtype: HttpResponse
262288 """
263- headers = _build_basic_headers (apikey )
264- if extra_headers is not None :
265- headers .update (extra_headers )
266289 start = get_current_epoch_time_ms ()
290+ headers = self ._get_headers (extra_headers , apikey )
267291 try :
268292 url = _build_url (server , path , self ._urls )
269293 _LOGGER .debug ("GET request: %s" , url )
@@ -303,9 +327,7 @@ async def post(self, server, path, apikey, body, query=None, extra_headers=None)
303327 :return: Tuple of status_code & response text
304328 :rtype: HttpResponse
305329 """
306- headers = _build_basic_headers (apikey )
307- if extra_headers is not None :
308- headers .update (extra_headers )
330+ headers = self ._get_headers (extra_headers , apikey )
309331 start = get_current_epoch_time_ms ()
310332 try :
311333 headers ['Accept-Encoding' ] = 'gzip'
0 commit comments