mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-14 18:57:41 +02:00
fix(epg): cache client du guide et plafond de débit relevé
Les journaux du serveur montrent la grille Live TV se faire couper en 429 : "Rate limit exceeded", une trentaine de requêtes refusées d'affilée après quelques écrans de défilement. Deux causes cumulées. D'une part le passage du guide de Dio vers AuthedHttp a fait perdre le cache de réponses qui existait auparavant : chaque reconstruction de tuile repartait en requête. Un cache mémoire de dix minutes le rétablit — le backend garde déjà ces réponses trente minutes, il n'y a aucune raison d'aller les rechercher. D'autre part le plafond de deux cents requêtes par minute et par adresse était trop bas pour l'usage réel : une grille demande un logo et un guide par tuile, et tous les clients partagent l'adresse du reverse proxy. Il passe à six cents. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
de49e8b34d
commit
5ab7203c8d
2 files changed
+32
-1
No files matched your search
@@ -89,7 +89,11 @@ Middleware honeypotMiddleware() {
|
||||
/// 3. Rate Limit Middleware (In-Memory)
|
||||
/// Limits requests per IP address.
|
||||
/// Default: 200 requests per minute per IP.
|
||||
Middleware rateLimitMiddleware({int requestsPerMinute = 200}) {
|
||||
/// 600 plutôt que 200 : une grille de chaînes déclenche une requête de
|
||||
/// logo et une de guide par tuile, et tous les clients partagent l'IP du
|
||||
/// reverse proxy. Le plafond précédent coupait la grille en 429 au bout de
|
||||
/// quelques écrans de défilement.
|
||||
Middleware rateLimitMiddleware({int requestsPerMinute = 600}) {
|
||||
final clientRequests = <String, List<DateTime>>{};
|
||||
|
||||
// Cleanup timer to remove old entries and prevent memory leaks
|
||||
|
||||
@@ -47,6 +47,16 @@ class XtreamService {
|
||||
/// On sérialise donc par petits paquets.
|
||||
static final _Semaphore _epgGate = _Semaphore(3);
|
||||
|
||||
/// Cache mémoire du guide, partagé par toutes les instances.
|
||||
///
|
||||
/// Une grille de chaînes reconstruit ses tuiles à chaque défilement et
|
||||
/// redemandait le guide à chaque fois. Sur une playlist de 500 chaînes, le
|
||||
/// limiteur de débit du backend (200 requêtes par minute et par IP) coupait
|
||||
/// la grille en 429 au bout de quelques écrans. Le backend garde déjà ces
|
||||
/// réponses 30 minutes ; ce cache évite d'aller les chercher.
|
||||
static final Map<String, _EpgCacheEntry> _epgCache = {};
|
||||
static const _epgCacheTtl = Duration(minutes: 10);
|
||||
|
||||
late final Dio _dio;
|
||||
late final CacheOptions _cacheOptions;
|
||||
|
||||
@@ -726,6 +736,11 @@ class XtreamService {
|
||||
await Future.delayed(_throttleDelay);
|
||||
}
|
||||
|
||||
final cached = _epgCache[streamId];
|
||||
if (cached != null && DateTime.now().isBefore(cached.expiresAt)) {
|
||||
return cached.programmes;
|
||||
}
|
||||
|
||||
await _epgGate.acquire();
|
||||
try {
|
||||
final response = await AuthedHttp.get(Uri.parse('/api/epg/$streamId'));
|
||||
@@ -750,6 +765,11 @@ class XtreamService {
|
||||
result.add(Map<String, dynamic>.from(item));
|
||||
if (result.length >= 8) break;
|
||||
}
|
||||
|
||||
_epgCache[streamId] = _EpgCacheEntry(
|
||||
programmes: result,
|
||||
expiresAt: DateTime.now().add(_epgCacheTtl),
|
||||
);
|
||||
return result;
|
||||
} catch (e) {
|
||||
// L'EPG reste optionnel : une panne ne doit pas casser la grille.
|
||||
@@ -765,3 +785,10 @@ class XtreamService {
|
||||
_dio.close();
|
||||
}
|
||||
}
|
||||
|
||||
class _EpgCacheEntry {
|
||||
const _EpgCacheEntry({required this.programmes, required this.expiresAt});
|
||||
|
||||
final List<Map<String, dynamic>> programmes;
|
||||
final DateTime expiresAt;
|
||||
}
|
||||
Reference in new issue
Block a user