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