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
12+ from splitio .sync import util
1013
1114_LOGGER = logging .getLogger (__name__ )
15+ _SPEC_1_1 = "1.1"
16+ _PROXY_CHECK_INTERVAL_MILLISECONDS_SS = 24 * 60 * 60 * 1000
1217
13-
14- class SplitsAPI (object ): # pylint: disable=too-few-public-methods
18+ class SplitsAPIBase (object ): # pylint: disable=too-few-public-methods
1519 """Class that uses an httpClient to communicate with the splits API."""
1620
1721 def __init__ (self , client , sdk_key , sdk_metadata , telemetry_runtime_producer ):
@@ -30,22 +34,66 @@ def __init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer):
3034 self ._metadata = headers_from_metadata (sdk_metadata )
3135 self ._telemetry_runtime_producer = telemetry_runtime_producer
3236 self ._client .set_telemetry_data (HTTPExceptionsAndLatencies .SPLIT , self ._telemetry_runtime_producer )
37+ self ._spec_version = SPEC_VERSION
38+ self ._last_proxy_check_timestamp = 0
39+ self .clear_storage = False
40+ self ._old_spec_since = None
41+
42+ def _check_last_proxy_check_timestamp (self , since ):
43+ if self ._spec_version == _SPEC_1_1 and ((utctime_ms () - self ._last_proxy_check_timestamp ) >= _PROXY_CHECK_INTERVAL_MILLISECONDS_SS ):
44+ _LOGGER .info ("Switching to new Feature flag spec (%s) and fetching." , SPEC_VERSION );
45+ self ._spec_version = SPEC_VERSION
46+ self ._old_spec_since = since
47+
48+ def _check_old_spec_since (self , change_number ):
49+ if self ._spec_version == _SPEC_1_1 and self ._old_spec_since is not None :
50+ since = self ._old_spec_since
51+ self ._old_spec_since = None
52+ return since
53+ return change_number
54+
55+
56+ class SplitsAPI (SplitsAPIBase ): # pylint: disable=too-few-public-methods
57+ """Class that uses an httpClient to communicate with the splits API."""
58+
59+ def __init__ (self , client , sdk_key , sdk_metadata , telemetry_runtime_producer ):
60+ """
61+ Class constructor.
62+
63+ :param client: HTTP Client responsble for issuing calls to the backend.
64+ :type client: HttpClient
65+ :param sdk_key: User sdk_key token.
66+ :type sdk_key: string
67+ :param sdk_metadata: SDK version & machine name & IP.
68+ :type sdk_metadata: splitio.client.util.SdkMetadata
69+ """
70+ SplitsAPIBase .__init__ (self , client , sdk_key , sdk_metadata , telemetry_runtime_producer )
3371
34- def fetch_splits (self , change_number , fetch_options ):
72+ def fetch_splits (self , change_number , rbs_change_number , fetch_options ):
3573 """
3674 Fetch feature flags from backend.
3775
3876 :param change_number: Last known timestamp of a split modification.
3977 :type change_number: int
4078
79+ :param rbs_change_number: Last known timestamp of a rule based segment modification.
80+ :type rbs_change_number: int
81+
4182 :param fetch_options: Fetch options for getting feature flag definitions.
4283 :type fetch_options: splitio.api.commons.FetchOptions
4384
4485 :return: Json representation of a splitChanges response.
4586 :rtype: dict
4687 """
4788 try :
48- query , extra_headers = build_fetch (change_number , fetch_options , self ._metadata )
89+ self ._check_last_proxy_check_timestamp (change_number )
90+ change_number = self ._check_old_spec_since (change_number )
91+
92+ if self ._spec_version == _SPEC_1_1 :
93+ fetch_options = FetchOptions (fetch_options .cache_control_headers , fetch_options .change_number ,
94+ None , fetch_options .sets , self ._spec_version )
95+ rbs_change_number = None
96+ query , extra_headers = build_fetch (change_number , fetch_options , self ._metadata , rbs_change_number )
4997 response = self ._client .get (
5098 'sdk' ,
5199 'splitChanges' ,
@@ -54,19 +102,32 @@ def fetch_splits(self, change_number, fetch_options):
54102 query = query ,
55103 )
56104 if 200 <= response .status_code < 300 :
105+ if self ._spec_version == _SPEC_1_1 :
106+ return util .convert_to_new_spec (json .loads (response .body ))
107+
108+ self .clear_storage = self ._last_proxy_check_timestamp != 0
109+ self ._last_proxy_check_timestamp = 0
57110 return json .loads (response .body )
58111
59112 else :
60113 if response .status_code == 414 :
61114 _LOGGER .error ('Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.' )
115+
116+ if self ._client .is_sdk_endpoint_overridden () and response .status_code == 400 and self ._spec_version == SPEC_VERSION :
117+ _LOGGER .warning ('Detected proxy response error, changing spec version from %s to %s and re-fetching.' , self ._spec_version , _SPEC_1_1 )
118+ self ._spec_version = _SPEC_1_1
119+ self ._last_proxy_check_timestamp = utctime_ms ()
120+ return self .fetch_splits (change_number , None , FetchOptions (fetch_options .cache_control_headers , fetch_options .change_number ,
121+ None , fetch_options .sets , self ._spec_version ))
122+
62123 raise APIException (response .body , response .status_code )
124+
63125 except HttpClientException as exc :
64126 _LOGGER .error ('Error fetching feature flags because an exception was raised by the HTTPClient' )
65127 _LOGGER .debug ('Error: ' , exc_info = True )
66128 raise APIException ('Feature flags not fetched correctly.' ) from exc
67129
68-
69- class SplitsAPIAsync (object ): # pylint: disable=too-few-public-methods
130+ class SplitsAPIAsync (SplitsAPIBase ): # pylint: disable=too-few-public-methods
70131 """Class that uses an httpClient to communicate with the splits API."""
71132
72133 def __init__ (self , client , sdk_key , sdk_metadata , telemetry_runtime_producer ):
@@ -80,18 +141,17 @@ def __init__(self, client, sdk_key, sdk_metadata, telemetry_runtime_producer):
80141 :param sdk_metadata: SDK version & machine name & IP.
81142 :type sdk_metadata: splitio.client.util.SdkMetadata
82143 """
83- self ._client = client
84- self ._sdk_key = sdk_key
85- self ._metadata = headers_from_metadata (sdk_metadata )
86- self ._telemetry_runtime_producer = telemetry_runtime_producer
87- self ._client .set_telemetry_data (HTTPExceptionsAndLatencies .SPLIT , self ._telemetry_runtime_producer )
144+ SplitsAPIBase .__init__ (self , client , sdk_key , sdk_metadata , telemetry_runtime_producer )
88145
89- async def fetch_splits (self , change_number , fetch_options ):
146+ async def fetch_splits (self , change_number , rbs_change_number , fetch_options ):
90147 """
91148 Fetch feature flags from backend.
92149
93150 :param change_number: Last known timestamp of a split modification.
94151 :type change_number: int
152+
153+ :param rbs_change_number: Last known timestamp of a rule based segment modification.
154+ :type rbs_change_number: int
95155
96156 :param fetch_options: Fetch options for getting feature flag definitions.
97157 :type fetch_options: splitio.api.commons.FetchOptions
@@ -100,7 +160,14 @@ async def fetch_splits(self, change_number, fetch_options):
100160 :rtype: dict
101161 """
102162 try :
103- query , extra_headers = build_fetch (change_number , fetch_options , self ._metadata )
163+ self ._check_last_proxy_check_timestamp (change_number )
164+ change_number = self ._check_old_spec_since (change_number )
165+ if self ._spec_version == _SPEC_1_1 :
166+ fetch_options = FetchOptions (fetch_options .cache_control_headers , fetch_options .change_number ,
167+ None , fetch_options .sets , self ._spec_version )
168+ rbs_change_number = None
169+
170+ query , extra_headers = build_fetch (change_number , fetch_options , self ._metadata , rbs_change_number )
104171 response = await self ._client .get (
105172 'sdk' ,
106173 'splitChanges' ,
@@ -109,12 +176,26 @@ async def fetch_splits(self, change_number, fetch_options):
109176 query = query ,
110177 )
111178 if 200 <= response .status_code < 300 :
179+ if self ._spec_version == _SPEC_1_1 :
180+ return util .convert_to_new_spec (json .loads (response .body ))
181+
182+ self .clear_storage = self ._last_proxy_check_timestamp != 0
183+ self ._last_proxy_check_timestamp = 0
112184 return json .loads (response .body )
113185
114186 else :
115187 if response .status_code == 414 :
116188 _LOGGER .error ('Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.' )
189+
190+ if self ._client .is_sdk_endpoint_overridden () and response .status_code == 400 and self ._spec_version == SPEC_VERSION :
191+ _LOGGER .warning ('Detected proxy response error, changing spec version from %s to %s and re-fetching.' , self ._spec_version , _SPEC_1_1 )
192+ self ._spec_version = _SPEC_1_1
193+ self ._last_proxy_check_timestamp = utctime_ms ()
194+ return await self .fetch_splits (change_number , None , FetchOptions (fetch_options .cache_control_headers , fetch_options .change_number ,
195+ None , fetch_options .sets , self ._spec_version ))
196+
117197 raise APIException (response .body , response .status_code )
198+
118199 except HttpClientException as exc :
119200 _LOGGER .error ('Error fetching feature flags because an exception was raised by the HTTPClient' )
120201 _LOGGER .debug ('Error: ' , exc_info = True )
0 commit comments