/** * Point d'entrée du serveur SourceFinder * Démarrage et arrêt gracieux de l'application */ require('dotenv').config(); const SourceFinderApp = require('./src/app'); const logger = require('./src/utils/logger'); class Server { constructor() { this.app = null; this.server = null; this.sourceFinderApp = new SourceFinderApp(); } /** * Démarrer le serveur */ async start() { try { // Initialiser l'application avec toutes ses dépendances this.app = await this.sourceFinderApp.initialize(); // Configuration du port const port = parseInt(process.env.PORT) || 3000; const host = process.env.HOST || '0.0.0.0'; // Démarrer le serveur HTTP this.server = this.app.listen(port, host, () => { logger.info('🚀 SourceFinder server started', { server: { port, host, environment: process.env.NODE_ENV || 'development', apiVersion: process.env.API_VERSION || 'v1', pid: process.pid }, endpoints: { health: `http://${host}:${port}/health`, api: `http://${host}:${port}/api/v1`, docs: `http://${host}:${port}/api/v1/docs` // TODO: implémenter docs } }); // Log configuration active const container = this.sourceFinderApp.getContainer(); const config = container.get('config'); logger.info('📦 Active configuration', { components: { newsProvider: config.newsProvider.type, stockRepository: config.stockRepository.type, scoringEngine: config.scoringEngine.type }, features: { rateLimiting: true, cors: true, requestLogging: true, errorHandling: true } }); }); // Configuration serveur this.server.keepAliveTimeout = 65000; // Plus que le load balancer this.server.headersTimeout = 66000; // Plus que keepAliveTimeout // Gestion des signaux de fermeture this.setupGracefulShutdown(); } catch (error) { logger.error('❌ Failed to start SourceFinder server', error); process.exit(1); } } /** * Configurer l'arrêt gracieux */ setupGracefulShutdown() { // Gestion des signaux système const signals = ['SIGTERM', 'SIGINT', 'SIGUSR2']; signals.forEach((signal) => { process.on(signal, () => { logger.info(`📡 Received ${signal}, starting graceful shutdown...`); this.shutdown(); }); }); // Gestion des exceptions non catchées process.on('uncaughtException', (error) => { logger.error('💥 Uncaught Exception', error); this.shutdown(1); }); process.on('unhandledRejection', (reason, promise) => { logger.error('💥 Unhandled Rejection', new Error(reason), { promise }); this.shutdown(1); }); // Gestion mémoire (warning si > 80% utilisée) if (process.env.NODE_ENV === 'production') { setInterval(() => { const memUsage = process.memoryUsage(); const memUsedMB = Math.round(memUsage.heapUsed / 1024 / 1024); const memTotalMB = Math.round(memUsage.heapTotal / 1024 / 1024); const memPercent = (memUsage.heapUsed / memUsage.heapTotal) * 100; if (memPercent > 80) { logger.warn(`⚠️ High memory usage: ${memUsedMB}MB / ${memTotalMB}MB (${memPercent.toFixed(1)}%)`, { memory: { used: memUsedMB, total: memTotalMB, percent: memPercent, rss: Math.round(memUsage.rss / 1024 / 1024) } }); } }, 30000); // Check toutes les 30s } } /** * Arrêter le serveur gracieusement */ async shutdown(exitCode = 0) { logger.info('🔄 Starting graceful shutdown...'); try { // Arrêter d'accepter nouvelles connexions if (this.server) { await new Promise((resolve, reject) => { this.server.close((err) => { if (err) { logger.error('❌ Error closing HTTP server', err); reject(err); } else { logger.info('✅ HTTP server closed'); resolve(); } }); }); } // Fermer l'application et ses dépendances if (this.sourceFinderApp) { await this.sourceFinderApp.shutdown(); } // Forcer fermeture après timeout setTimeout(() => { logger.warn('⚠️ Forceful shutdown after timeout'); process.exit(1); }, 30000); // 30 secondes max logger.info('✅ Graceful shutdown completed'); process.exit(exitCode); } catch (error) { logger.error('❌ Error during shutdown', error); process.exit(1); } } } // Démarrer le serveur si ce fichier est exécuté directement if (require.main === module) { const server = new Server(); server.start().catch((error) => { console.error('Failed to start server:', error); process.exit(1); }); } module.exports = Server;