|
1 | 1 | import typing |
2 | 2 | from datetime import datetime |
| 3 | +from email.utils import formatdate |
3 | 4 | from functools import lru_cache |
4 | 5 |
|
5 | 6 | import httpx |
| 7 | +import starlette.status |
6 | 8 | import structlog |
7 | 9 | from flag_engine.engine import ( |
8 | 10 | get_environment_feature_state, |
|
22 | 24 | map_traits_to_response_data, |
23 | 25 | ) |
24 | 26 | from edge_proxy.models import IdentityWithTraits |
25 | | -from edge_proxy.settings import AppSettings |
| 27 | +from edge_proxy.settings import AppSettings, EnvironmentKeyPair |
26 | 28 |
|
27 | 29 | logger = structlog.get_logger(__name__) |
28 | 30 |
|
@@ -58,9 +60,7 @@ async def refresh_environment_caches(self): |
58 | 60 | received_error = False |
59 | 61 | for key_pair in self.settings.environment_key_pairs: |
60 | 62 | try: |
61 | | - environment_document = await self._fetch_document( |
62 | | - key_pair.server_side_key |
63 | | - ) |
| 63 | + environment_document = await self._fetch_document(key_pair) |
64 | 64 | if self.cache.put_environment( |
65 | 65 | environment_api_key=key_pair.client_side_key, |
66 | 66 | environment_document=environment_document, |
@@ -142,11 +142,41 @@ def get_environment(self, client_side_key: str) -> dict[str, typing.Any]: |
142 | 142 | return environment_document |
143 | 143 | raise FlagsmithUnknownKeyError(client_side_key) |
144 | 144 |
|
145 | | - async def _fetch_document(self, server_side_key: str) -> dict[str, typing.Any]: |
| 145 | + async def _fetch_document( |
| 146 | + self, key_pair: EnvironmentKeyPair |
| 147 | + ) -> dict[str, typing.Any]: |
| 148 | + headers = { |
| 149 | + "X-Environment-Key": key_pair.server_side_key, |
| 150 | + } |
| 151 | + environment_document = self.cache.get_environment( |
| 152 | + environment_api_key=key_pair.client_side_key |
| 153 | + ) |
| 154 | + if environment_document: |
| 155 | + updated_at: str = environment_document.get("updated_at") |
| 156 | + if updated_at: |
| 157 | + try: |
| 158 | + epoch_seconds = datetime.fromisoformat(updated_at).timestamp() |
| 159 | + # Same implementation as https://docs.djangoproject.com/en/4.2/ref/utils/#django.utils.http.http_date |
| 160 | + headers["If-Modified-Since"] = formatdate( |
| 161 | + epoch_seconds, usegmt=True |
| 162 | + ) |
| 163 | + except ValueError: |
| 164 | + logger.warning( |
| 165 | + f"failed to parse updated_at, environment={key_pair.client_side_key} updated_at={updated_at}" |
| 166 | + ) |
| 167 | + else: |
| 168 | + logger.warning( |
| 169 | + f"received environment with no updated_at: {key_pair.client_side_key}" |
| 170 | + ) |
146 | 171 | response = await self._client.get( |
147 | 172 | url=f"{self.settings.api_url}/environment-document/", |
148 | | - headers={"X-Environment-Key": server_side_key}, |
| 173 | + headers=headers, |
149 | 174 | ) |
| 175 | + if response.status_code == starlette.status.HTTP_304_NOT_MODIFIED: |
| 176 | + assert environment_document, ( |
| 177 | + f"GET /environment-document returned 304 without a cached document. environment={key_pair.client_side_key}" |
| 178 | + ) |
| 179 | + return environment_document |
150 | 180 | response.raise_for_status() |
151 | 181 | return orjson.loads(response.text) |
152 | 182 |
|
|
0 commit comments