mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-24 15:38:04 +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)
|
/// 3. Rate Limit Middleware (In-Memory)
|
||||||
/// Limits requests per IP address.
|
/// Limits requests per IP address.
|
||||||
/// Default: 200 requests per minute per IP.
|
/// 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>>{};
|
final clientRequests = <String, List<DateTime>>{};
|
||||||
|
|
||||||
// Cleanup timer to remove old entries and prevent memory leaks
|
// Cleanup timer to remove old entries and prevent memory leaks
|
||||||
|
|||||||
@@ -47,6 +47,16 @@ class XtreamService {
|
|||||||
/// On sérialise donc par petits paquets.
|
/// On sérialise donc par petits paquets.
|
||||||
static final _Semaphore _epgGate = _Semaphore(3);
|
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 Dio _dio;
|
||||||
late final CacheOptions _cacheOptions;
|
late final CacheOptions _cacheOptions;
|
||||||
|
|
||||||
@@ -726,6 +736,11 @@ class XtreamService {
|
|||||||
await Future.delayed(_throttleDelay);
|
await Future.delayed(_throttleDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final cached = _epgCache[streamId];
|
||||||
|
if (cached != null && DateTime.now().isBefore(cached.expiresAt)) {
|
||||||
|
return cached.programmes;
|
||||||
|
}
|
||||||
|
|
||||||
await _epgGate.acquire();
|
await _epgGate.acquire();
|
||||||
try {
|
try {
|
||||||
final response = await AuthedHttp.get(Uri.parse('/api/epg/$streamId'));
|
final response = await AuthedHttp.get(Uri.parse('/api/epg/$streamId'));
|
||||||
@@ -750,6 +765,11 @@ class XtreamService {
|
|||||||
result.add(Map<String, dynamic>.from(item));
|
result.add(Map<String, dynamic>.from(item));
|
||||||
if (result.length >= 8) break;
|
if (result.length >= 8) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_epgCache[streamId] = _EpgCacheEntry(
|
||||||
|
programmes: result,
|
||||||
|
expiresAt: DateTime.now().add(_epgCacheTtl),
|
||||||
|
);
|
||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// L'EPG reste optionnel : une panne ne doit pas casser la grille.
|
// L'EPG reste optionnel : une panne ne doit pas casser la grille.
|
||||||
@@ -765,3 +785,10 @@ class XtreamService {
|
|||||||
_dio.close();
|
_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