44import json
55
66from splitio .api import APIException , headers_from_metadata
7- from splitio .api .commons import build_fetch
7+ from splitio .api .commons import build_fetch , FetchOptions
88from splitio .api .client import HttpClientException
99from splitio .models .telemetry import HTTPExceptionsAndLatencies
10+ from splitio .util .time import utctime_ms
11+ from splitio .spec import SPEC_VERSION
1012
1113_LOGGER = logging .getLogger (__name__ )
14+ _SPEC_1_1 = "1.1"
15+ _PROXY_CHECK_INTERVAL_MILLISECONDS_SS = 24 * 60 * 60 * 1000
1216
13-
14- class SplitsAPI (object ): # pylint: disable=too-few-public-methods
17+ class SplitsAPIBase (object ): # pylint: disable=too-few-public-methods
1518 """Class that uses an httpClient to communicate with the splits API."""
1619
1720 def __init__ (self , client , sdk_key , sdk_metadata , telemetry_runtime_producer ):
@@ -30,6 +33,35 @@ def __init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer):
3033 self ._metadata = headers_from_metadata (sdk_metadata )
3134 self ._telemetry_runtime_producer = telemetry_runtime_producer
3235 self ._client .set_telemetry_data (HTTPExceptionsAndLatencies .SPLIT , self ._telemetry_runtime_producer )
36+ self ._spec_version = SPEC_VERSION
37+ self ._last_proxy_check_timestamp = 0
38+ self .clear_storage = False
39+
40+ def _convert_to_new_spec (self , body ):
41+ return {"ff" : {"d" : body ["splits" ], "s" : body ["since" ], "t" : body ["till" ]},
42+ "rbs" : {"d" : [], "s" : - 1 , "t" : - 1 }}
43+
44+ def _check_last_proxy_check_timestamp (self ):
45+ if self ._spec_version == _SPEC_1_1 and ((utctime_ms () - self ._last_proxy_check_timestamp ) >= _PROXY_CHECK_INTERVAL_MILLISECONDS_SS ):
46+ _LOGGER .info ("Switching to new Feature flag spec (%s) and fetching." , SPEC_VERSION );
47+ self ._spec_version = SPEC_VERSION
48+
49+
50+ class SplitsAPI (SplitsAPIBase ): # pylint: disable=too-few-public-methods
51+ """Class that uses an httpClient to communicate with the splits API."""
52+
53+ def __init__ (self , client , sdk_key , sdk_metadata , telemetry_runtime_producer ):
54+ """
55+ Class constructor.
56+
57+ :param client: HTTP Client responsble for issuing calls to the backend.
58+ :type client: HttpClient
59+ :param sdk_key: User sdk_key token.
60+ :type sdk_key: string
61+ :param sdk_metadata: SDK version & machine name & IP.
62+ :type sdk_metadata: splitio.client.util.SdkMetadata
63+ """
64+ SplitsAPIBase .__init__ (self , client , sdk_key , sdk_metadata , telemetry_runtime_producer )
3365
3466 def fetch_splits (self , change_number , rbs_change_number , fetch_options ):
3567 """
@@ -48,6 +80,7 @@ def fetch_splits(self, change_number, rbs_change_number, fetch_options):
4880 :rtype: dict
4981 """
5082 try :
83+ self ._check_last_proxy_check_timestamp ()
5184 query , extra_headers = build_fetch (change_number , fetch_options , self ._metadata , rbs_change_number )
5285 response = self ._client .get (
5386 'sdk' ,
@@ -57,19 +90,32 @@ def fetch_splits(self, change_number, rbs_change_number, fetch_options):
5790 query = query ,
5891 )
5992 if 200 <= response .status_code < 300 :
93+ if self ._spec_version == _SPEC_1_1 :
94+ return self ._convert_to_new_spec (json .loads (response .body ))
95+
96+ self .clear_storage = self ._last_proxy_check_timestamp != 0
97+ self ._last_proxy_check_timestamp = 0
6098 return json .loads (response .body )
6199
62100 else :
63101 if response .status_code == 414 :
64102 _LOGGER .error ('Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.' )
103+
104+ if self ._client .is_sdk_endpoint_overridden () and response .status_code == 400 and self ._spec_version == SPEC_VERSION :
105+ _LOGGER .warning ('Detected proxy response error, changing spec version from %s to %s and re-fetching.' , self ._spec_version , _SPEC_1_1 )
106+ self ._spec_version = _SPEC_1_1
107+ self ._last_proxy_check_timestamp = utctime_ms ()
108+ return self .fetch_splits (change_number , None , FetchOptions (fetch_options .cache_control_headers , fetch_options .change_number ,
109+ None , fetch_options .sets , self ._spec_version ))
110+
65111 raise APIException (response .body , response .status_code )
112+
66113 except HttpClientException as exc :
67114 _LOGGER .error ('Error fetching feature flags because an exception was raised by the HTTPClient' )
68115 _LOGGER .debug ('Error: ' , exc_info = True )
69116 raise APIException ('Feature flags not fetched correctly.' ) from exc
70117
71-
72- class SplitsAPIAsync (object ): # pylint: disable=too-few-public-methods
118+ class SplitsAPIAsync (SplitsAPIBase ): # pylint: disable=too-few-public-methods
73119 """Class that uses an httpClient to communicate with the splits API."""
74120
75121 def __init__ (self , client , sdk_key , sdk_metadata , telemetry_runtime_producer ):
@@ -83,11 +129,7 @@ def __init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer):
83129 :param sdk_metadata: SDK version & machine name & IP.
84130 :type sdk_metadata: splitio.client.util.SdkMetadata
85131 """
86- self ._client = client
87- self ._sdk_key = sdk_key
88- self ._metadata = headers_from_metadata (sdk_metadata )
89- self ._telemetry_runtime_producer = telemetry_runtime_producer
90- self ._client .set_telemetry_data (HTTPExceptionsAndLatencies .SPLIT , self ._telemetry_runtime_producer )
132+ SplitsAPIBase .__init__ (self , client , sdk_key , sdk_metadata , telemetry_runtime_producer )
91133
92134 async def fetch_splits (self , change_number , rbs_change_number , fetch_options ):
93135 """
@@ -106,6 +148,7 @@ async def fetch_splits(self, change_number, rbs_change_number, fetch_options):
106148 :rtype: dict
107149 """
108150 try :
151+ self ._check_last_proxy_check_timestamp ()
109152 query , extra_headers = build_fetch (change_number , fetch_options , self ._metadata , rbs_change_number )
110153 response = await self ._client .get (
111154 'sdk' ,
@@ -115,12 +158,26 @@ async def fetch_splits(self, change_number, rbs_change_number, fetch_options):
115158 query = query ,
116159 )
117160 if 200 <= response .status_code < 300 :
161+ if self ._spec_version == _SPEC_1_1 :
162+ return self ._convert_to_new_spec (json .loads (response .body ))
163+
164+ self .clear_storage = self ._last_proxy_check_timestamp != 0
165+ self ._last_proxy_check_timestamp = 0
118166 return json .loads (response .body )
119167
120168 else :
121169 if response .status_code == 414 :
122170 _LOGGER .error ('Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.' )
171+
172+ if self ._client .is_sdk_endpoint_overridden () and response .status_code == 400 and self ._spec_version == SPEC_VERSION :
173+ _LOGGER .warning ('Detected proxy response error, changing spec version from %s to %s and re-fetching.' , self ._spec_version , _SPEC_1_1 )
174+ self ._spec_version = _SPEC_1_1
175+ self ._last_proxy_check_timestamp = utctime_ms ()
176+ return await self .fetch_splits (change_number , None , FetchOptions (fetch_options .cache_control_headers , fetch_options .change_number ,
177+ None , fetch_options .sets , self ._spec_version ))
178+
123179 raise APIException (response .body , response .status_code )
180+
124181 except HttpClientException as exc :
125182 _LOGGER .error ('Error fetching feature flags because an exception was raised by the HTTPClient' )
126183 _LOGGER .debug ('Error: ' , exc_info = True )
0 commit comments