MovixOpenSource/API/Mainapi/server.js
Movix cbc3f1dad9 Merge depuis le repo privé après tests
Corrections de vulnérabilités sur les commentaires (IDOR)
Rajouts de ratelimit sur les commentaires
Correction de pas de son sur hlsplayer
Livetvplayer détecte le type du flux automatiquement
Amélioration du scraper darkiworld (très chiant)
Correction du détection de l'extension

Correction de la vérification du VIP sur server.py (proxy)
Correction du scraper seekstreaming proxy

Nouvelle url streamonsport et coflix
Correction du scraper fstream (get seasons) car la route fonctionne plus sur leur site
Correction du scraper francetv
Ajout de hydrackerBatch (c'étais pour des tests)
2026-05-03 12:47:41 +02:00

215 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const cluster = require('cluster');
const os = require('os');
require('dotenv').config();
// === CLUSTER MODE CONFIGURATION (au tout début pour éviter que le master charge tout) ===
const NUM_WORKERS = parseInt(process.env.NUM_WORKERS) || 6; // 6 coeurs physiques sur le serveur
if (cluster.isPrimary ?? cluster.isMaster) {
// === MODE MASTER — ne charge RIEN d'autre (pas de MySQL, Redis, Express, etc.) ===
console.log(`🚀 Master process ${process.pid} démarré en mode cluster`);
console.log(`📊 Création de ${NUM_WORKERS} workers...`);
for (let i = 0; i < NUM_WORKERS; i++) {
const worker = cluster.fork();
console.log(
`✓ Worker ${worker.process.pid} créé (${i + 1}/${NUM_WORKERS})`,
);
}
// Anti-fork-bomb : limiter les redémarrages rapides
const workerRestarts = new Map(); // pid -> [timestamps]
const MAX_RESTARTS = 5;
const RESTART_WINDOW_MS = 60000; // 1 minute
let isShuttingDown = false; // Flag pour empêcher le redémarrage des workers pendant le shutdown
cluster.on('exit', (worker, code, signal) => {
if (signal) {
console.warn(`⚠️ Worker ${worker.process.pid} tué par le signal ${signal}`);
} else if (code !== 0) {
console.error(`❌ Worker ${worker.process.pid} terminé avec le code ${code}`);
} else {
console.log(` Worker ${worker.process.pid} terminé normalement`);
}
// Ne pas redémarrer les workers si le master est en cours d'arrêt
if (isShuttingDown) {
console.log(`🛑 Shutdown en cours — worker ${worker.process.pid} ne sera pas redémarré`);
return;
}
// Vérifier le taux de redémarrage pour éviter la boucle infinie
const now = Date.now();
const restarts = workerRestarts.get('global') || [];
const recentRestarts = restarts.filter(t => now - t < RESTART_WINDOW_MS);
recentRestarts.push(now);
workerRestarts.set('global', recentRestarts);
if (recentRestarts.length > MAX_RESTARTS) {
console.error(`🚨 Trop de redémarrages (${recentRestarts.length} en ${RESTART_WINDOW_MS / 1000}s) — arrêt du fork`);
return;
}
console.log(`🔄 Redémarrage d'un nouveau worker...`);
const newWorker = cluster.fork();
console.log(`✅ Nouveau worker ${newWorker.process.pid} créé`);
});
// Graceful shutdown master
const shutdownMaster = () => {
if (isShuttingDown) return; // Guard: SIGINT + SIGTERM peuvent arriver quasi-simultanement
isShuttingDown = true;
console.log('\n🛑 Signal de fermeture reçu par le master...');
console.log('📤 Envoi du signal de fermeture à tous les workers...');
for (const id in cluster.workers) {
cluster.workers[id].send('shutdown');
}
let workersAlive = Object.keys(cluster.workers).length;
const checkInterval = setInterval(() => {
workersAlive = Object.keys(cluster.workers).length;
if (workersAlive === 0) {
clearInterval(checkInterval);
console.log('✅ Tous les workers sont arrêtés. Arrêt du master.');
process.exit(0);
}
}, 100);
setTimeout(() => {
console.warn('⚠️ Timeout atteint (30s). Arrêt forcé du master.');
process.exit(1);
}, 30000);
};
process.on('SIGTERM', shutdownMaster);
process.on('SIGINT', shutdownMaster);
console.log(`
╔═══════════════════════════════════════════════════════╗
║ 🎯 Mode Cluster Actif ║
║ 👷 Workers: ${NUM_WORKERS.toString().padEnd(42, ' ')}
║ 🔒 Redis Locks: Verrous distribués entre workers ║
║ ⚛️ Atomic writes: Garantis entre les processus ║
╚═══════════════════════════════════════════════════════╝
`);
// Le master ne fait RIEN d'autre — pas de require express, mysql, redis, etc.
return;
}
// =============================================================================
// === WORKER PROCESS ONLY (below) =============================================
// =============================================================================
process.env.UV_THREADPOOL_SIZE = 8; // 8 threads libuv par worker (6 workers x 8 = 48 threads total)
const http = require('http');
const { app, appReady } = require('./app');
const { redis } = require('./config/redis');
const { shutdownCycleTLS, refreshProxyScrapeProxies } = require('./utils/proxyManager');
const { getPool } = require('./mysqlPool');
const PORT = 25565;
// ---------------------------------------------------------------------------
// startServer — create HTTP server with retry logic
// ---------------------------------------------------------------------------
const startServer = async (retries = 3) => {
try {
await appReady;
} catch (error) {
if (retries > 0) {
console.error(`[BOOTSTRAP] Échec avant listen: ${error.message}`);
console.log(`Redémarrage... (${retries} restantes)`);
return setTimeout(() => startServer(retries - 1), 5000);
}
console.error('Échec du bootstrap applicatif après plusieurs tentatives');
process.exit(1);
}
try {
await refreshProxyScrapeProxies({ force: false, silent: false });
} catch (error) {
console.warn(`[PROXYSCRAPE] Initialisation incomplete avant listen: ${error.message}`);
}
const server = http.createServer(app);
// Configure Keep-Alive settings
server.keepAliveTimeout = 65000; // 65 secondes
server.headersTimeout = 66000; // 66 secondes
server.maxRequestsPerSocket = 0; // Illimité
server.requestTimeout = 300000; // 5 minutes timeout to prevent hung sockets
// Backlog increased to 4096 to handle burst connections
server.listen(PORT, '0.0.0.0', 4096, () => {
console.log(`Serveur démarré sur le port ${PORT} - Process ${process.pid}`);
console.log(`Proxy service available at http://localhost:${PORT}/proxy/`);
console.log(`Keep-Alive configuré: timeout=${server.keepAliveTimeout}ms, max=1000`);
console.log(`Performance tuning: UV_THREADPOOL_SIZE=${process.env.UV_THREADPOOL_SIZE}, RequestTimeout=${server.requestTimeout}ms`);
});
server.on('error', (err) => {
console.error('Erreur de démarrage:', err);
if (retries > 0) {
console.log(`Redémarrage... (${retries} restantes)`);
setTimeout(() => startServer(retries - 1), 5000);
} else {
console.error('Échec du démarrage après plusieurs tentatives');
process.exit(1);
}
});
return server;
};
// === DÉMARRAGE DU WORKER ===
let activeServer = null;
startServer().then((server) => {
activeServer = server;
console.log(`✅ Worker ${process.pid} - Serveur démarré sur le port ${PORT}`);
}).catch((error) => {
console.error(`❌ Worker ${process.pid} - Échec du démarrage:`, error);
process.exit(1);
});
// Graceful shutdown worker — cleanup all resources
const { setShuttingDown, isShuttingDown } = require('./utils/shutdownFlag');
let isWorkerShuttingDown = false;
const shutdownWorker = async () => {
if (isWorkerShuttingDown) return;
isWorkerShuttingDown = true;
setShuttingDown();
console.log(`\n🛑 Worker ${process.pid} - Signal de fermeture reçu...`);
// 1. Arrêter d'accepter de nouvelles connexions et attendre les requêtes en cours
if (activeServer) {
await new Promise((resolve) => {
// Empêcher les nouvelles connexions keep-alive de prolonger le shutdown
activeServer.keepAliveTimeout = 1;
activeServer.close(() => {
console.log(`✅ Worker ${process.pid} - Serveur HTTP fermé (plus de requêtes en cours)`);
resolve();
});
// Force-close après 15s si des connexions trainent
setTimeout(() => {
console.warn(`⚠️ Worker ${process.pid} - Timeout 15s, fermeture forcée des connexions`);
activeServer.closeAllConnections();
resolve();
}, 15000);
});
}
// 2. Cleanup des ressources
try { await redis.quit(); } catch { /* ignore */ }
try { await shutdownCycleTLS(); } catch { /* ignore */ }
try { const pool = getPool(); if (pool) await pool.end(); } catch { /* ignore */ }
process.exit(0);
};
process.on('SIGTERM', shutdownWorker);
process.on('SIGINT', shutdownWorker);
process.on('message', (msg) => {
if (msg === 'shutdown') shutdownWorker();
});