|
1 | 1 | from typing import Any, Optional |
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, |
@@ -156,11 +156,39 @@ def get_environment( |
156 | 156 | return environment_document |
157 | 157 | raise FlagsmithUnknownKeyError(server_side_key) |
158 | 158 |
|
159 | | - async def _fetch_document(self, server_side_key: str) -> dict[str, Any]: |
| 159 | + async def _fetch_document(self, key_pair: EnvironmentKeyPair) -> dict[str, Any]: |
| 160 | + headers = { |
| 161 | + "X-Environment-Key": key_pair.server_side_key, |
| 162 | + } |
| 163 | + environment_document = self.cache.get_environment( |
| 164 | + environment_api_key=key_pair.client_side_key |
| 165 | + ) |
| 166 | + if environment_document: |
| 167 | + updated_at: str = environment_document.get("updated_at") |
| 168 | + if updated_at: |
| 169 | + try: |
| 170 | + epoch_seconds = datetime.fromisoformat(updated_at).timestamp() |
| 171 | + # Same implementation as https://docs.djangoproject.com/en/4.2/ref/utils/#django.utils.http.http_date |
| 172 | + headers["If-Modified-Since"] = formatdate( |
| 173 | + epoch_seconds, usegmt=True |
| 174 | + ) |
| 175 | + except ValueError: |
| 176 | + logger.warning( |
| 177 | + f"failed to parse updated_at, environment={key_pair.client_side_key} updated_at={updated_at}" |
| 178 | + ) |
| 179 | + else: |
| 180 | + logger.warning( |
| 181 | + f"received environment with no updated_at: {key_pair.client_side_key}" |
| 182 | + ) |
160 | 183 | response = await self._client.get( |
161 | 184 | url=f"{self.settings.api_url}/environment-document/", |
162 | | - headers={"X-Environment-Key": server_side_key}, |
| 185 | + headers=headers, |
163 | 186 | ) |
| 187 | + if response.status_code == starlette.status.HTTP_304_NOT_MODIFIED: |
| 188 | + assert environment_document, ( |
| 189 | + f"GET /environment-document returned 304 without a cached document. environment={key_pair.client_side_key}" |
| 190 | + ) |
| 191 | + return environment_document |
164 | 192 | response.raise_for_status() |
165 | 193 | return orjson.loads(response.text) |
166 | 194 |
|
|
0 commit comments