diff --git a/server/index.js b/server/index.js index bf0e504..21442c1 100644 --- a/server/index.js +++ b/server/index.js @@ -6,8 +6,8 @@ const http = require('http'); const { randomUUID } = require('crypto'); require('dotenv').config(); -// Prevent HTTP parser errors (e.g. from keep-alive connections to monitored -// services that send data after Connection: close) from crashing the process. +// Prevent HTTP parser errors (e.g. "Data after Connection: close") and other +// transient async errors from crashing the process. process.on('uncaughtException', (err) => { if (err.code === 'HPE_INVALID_CONSTANT' || (err.message && err.message.includes('Parse Error'))) { console.error('HTTP parse error (swallowed):', err.message); @@ -17,6 +17,15 @@ process.on('uncaughtException', (err) => { process.exit(1); }); +process.on('unhandledRejection', (reason) => { + const msg = reason instanceof Error ? reason.message : String(reason); + if (msg.includes('Parse Error')) { + console.error('HTTP parse error (swallowed):', msg); + return; + } + console.error('Unhandled rejection:', reason); +}); + const db = require('./database'); const scheduler = require('./scheduler'); const monitorService = require('./monitorService'); diff --git a/server/monitorService.js b/server/monitorService.js index ec044a5..f840a1f 100644 --- a/server/monitorService.js +++ b/server/monitorService.js @@ -31,14 +31,14 @@ function parseHostPort(urlString, fallbackPort) { class MonitorService { constructor() { - // Keep-alive agents for pooled HTTP/S connections. - // Socket-level 'error' listener prevents parse errors (e.g. "Data after - // Connection: close") from bubbling up as uncaught exceptions when a - // monitored server sends a malformed response on a reused connection. - this.httpAgent = new http.Agent({ keepAlive: true, maxSockets: 50 }); - this.httpAgent.on('error', () => {}); - this.httpsAgent = new https.Agent({ keepAlive: true, maxSockets: 50 }); - this.httpsAgent.on('error', () => {}); + // Disable keep-alive to avoid "Parse Error: Data after Connection: close" + // crashes. Keep-alive connection reuse causes the HTTP parser to throw + // when a monitored server sends extra data after Connection: close — the + // socket-level error becomes an uncaught exception that kills the process. + // Monitoring checks are infrequent enough that connection pooling provides + // no meaningful benefit. + this.httpAgent = new http.Agent({ keepAlive: false }); + this.httpsAgent = new https.Agent({ keepAlive: false }); // Circuit breaker for HTTP checks (5 failures before opening, 60s reset) this.httpCircuitBreaker = new CircuitBreaker(5, 60000, this.isTransientError);