feat: add caching for peer proxy requests

- Cache successful GET responses from peers (5 min TTL)
- Return cached data when peer is unreachable
- Add _cached, _cached_at, _cache_age_seconds flags
- Add /api/node/<id>/health proxy endpoint

Enables master dashboard to show peer data even when
peers are temporarily unreachable (e.g., mobile devices,
VPN disconnections).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
User
2026-02-01 12:38:15 +01:00
parent 5b9612dfae
commit b695e19079

View File

@@ -1679,12 +1679,21 @@ def create_app(config: Config | None = None) -> Flask:
# ==================== Multi-Node Master Dashboard Proxy API ====================
# Cache for peer responses (scanner_id -> path -> {data, timestamp})
_peer_cache: dict[str, dict[str, dict]] = {}
_peer_cache_ttl = 300 # 5 minutes cache TTL
def proxy_peer_request(scanner_id: str, path: str, method: str = "GET", **kwargs):
"""Proxy a request to a peer node.
"""Proxy a request to a peer node with caching.
Returns:
- Response tuple (jsonify(...), status_code) on error or peer response
- None if scanner_id matches local scanner (caller should use local handler)
Caching behavior:
- GET requests are cached on success
- On peer unreachable, returns cached data if available (with _cached flag)
- POST requests are not cached but will return cached GET data on failure
"""
local_id = app.config.get("SCANNER_IDENTITY", {}).get("id")
if scanner_id == local_id:
@@ -1695,10 +1704,13 @@ def create_app(config: Config | None = None) -> Flask:
if not peer or not peer.get("url"):
return jsonify({"error": "Peer not found"}), 404
cache_key = f"{scanner_id}:{path}"
try:
url = f"{peer['url']}{path}"
resp = requests.request(method, url, timeout=15, **kwargs)
resp = requests.request(method, url, timeout=10, **kwargs)
data = resp.json()
# Enrich response with source scanner info
data["_source_scanner"] = scanner_id
data["_source_position"] = {
@@ -1706,9 +1718,39 @@ def create_app(config: Config | None = None) -> Flask:
"lon": peer.get("longitude"),
"floor": peer.get("floor")
}
data["_cached"] = False
# Cache successful GET responses
if method == "GET":
if scanner_id not in _peer_cache:
_peer_cache[scanner_id] = {}
_peer_cache[scanner_id][path] = {
"data": data,
"timestamp": datetime.now()
}
return jsonify(data)
except requests.RequestException as e:
return jsonify({"error": f"Peer unreachable: {e}"}), 503
# Try to return cached data
if scanner_id in _peer_cache and path in _peer_cache[scanner_id]:
cached = _peer_cache[scanner_id][path]
cache_age = (datetime.now() - cached["timestamp"]).total_seconds()
# Return cached data if within TTL (or always for display purposes)
cached_data = cached["data"].copy()
cached_data["_cached"] = True
cached_data["_cached_at"] = cached["timestamp"].isoformat()
cached_data["_cache_age_seconds"] = int(cache_age)
cached_data["_peer_error"] = str(e)
return jsonify(cached_data)
return jsonify({
"error": f"Peer unreachable: {e}",
"_source_scanner": scanner_id,
"_cached": False
}), 503
@app.route("/api/node/<scanner_id>/latest")
def api_node_latest(scanner_id: str):
@@ -1742,6 +1784,14 @@ def create_app(config: Config | None = None) -> Flask:
return api_trilaterated_positions()
return result
@app.route("/api/node/<scanner_id>/health")
def api_node_health(scanner_id: str):
"""Proxy: Get health status from a peer node."""
result = proxy_peer_request(scanner_id, "/api/health")
if result is None:
return api_health()
return result
return app