From cef8eff6f5ca96b6c0127ad80013763418dab1d9 Mon Sep 17 00:00:00 2001 From: Para Dox Date: Sun, 1 Jun 2025 21:03:11 +0700 Subject: [PATCH] tackle client disconnects --- split-proxy/index.js | 38 ++++++++++++++++++++++++++++++ split-proxy/proxy.js | 56 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/split-proxy/index.js b/split-proxy/index.js index ea63e64f..79933906 100644 --- a/split-proxy/index.js +++ b/split-proxy/index.js @@ -12,6 +12,31 @@ app.use(express.json({ type: 'application/json' })); +// Request timeout middleware +app.use((req, res, next) => { + // Set request timeout + req.setTimeout(config.requestTimeout, () => { + logger.error({ + method: req.method, + url: req.url, + timeout: config.requestTimeout, + }, 'Request timeout'); + + if (!res.headersSent) { + res.status(504).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Request timeout', + }, + id: req.body?.id, + }); + } + }); + + next(); +}); + // Health check endpoint app.get('/health', (req, res) => { res.json({ @@ -60,6 +85,19 @@ const server = app.listen(config.port, () => { }, 'ETH JSON-RPC proxy started'); }); +// Set server timeouts to be longer than request timeout +// This prevents the server from closing connections while requests are in flight +server.timeout = config.requestTimeout + 5000; // Add 5 seconds buffer +server.keepAliveTimeout = config.requestTimeout + 5000; +server.headersTimeout = config.requestTimeout + 6000; // Should be > keepAliveTimeout + +logger.info({ + serverTimeout: server.timeout, + keepAliveTimeout: server.keepAliveTimeout, + headersTimeout: server.headersTimeout, + requestTimeout: config.requestTimeout, +}, 'Server timeout configuration'); + // Graceful shutdown process.on('SIGTERM', () => { logger.info('SIGTERM received, shutting down gracefully'); diff --git a/split-proxy/proxy.js b/split-proxy/proxy.js index 570916e7..f8ee2e64 100644 --- a/split-proxy/proxy.js +++ b/split-proxy/proxy.js @@ -146,14 +146,54 @@ class RPCProxy { // Handle client disconnect let clientClosed = false; + let clientCloseReason = null; + req.on('close', () => { - clientClosed = true; - logger.warn({ requestId }, 'Client connection closed'); + if (!clientClosed) { + clientClosed = true; + clientCloseReason = 'connection_closed'; + logger.warn({ + requestId, + reason: clientCloseReason, + headers: req.headers, + userAgent: req.headers['user-agent'], + contentLength: req.headers['content-length'], + method: requestBody.method, + elapsedMs: Date.now() - startTime, + }, 'Client connection closed'); + } }); req.on('error', (error) => { - clientClosed = true; - logger.error({ requestId, error: error.message }, 'Client connection error'); + if (!clientClosed) { + clientClosed = true; + clientCloseReason = `connection_error: ${error.code || error.message}`; + logger.error({ + requestId, + error: error.message, + code: error.code, + reason: clientCloseReason, + headers: req.headers, + method: requestBody.method, + elapsedMs: Date.now() - startTime, + }, 'Client connection error'); + } + }); + + // Also track response close events + res.on('close', () => { + if (!clientClosed) { + clientClosed = true; + clientCloseReason = 'response_closed'; + logger.warn({ + requestId, + reason: clientCloseReason, + finished: res.finished, + headersSent: res.headersSent, + method: requestBody.method, + elapsedMs: Date.now() - startTime, + }, 'Response connection closed'); + } }); try { @@ -228,9 +268,15 @@ class RPCProxy { // Set response headers only if client hasn't closed if (!isClientClosed() && !res.headersSent) { res.status(response.status); + + // Ensure proper keep-alive handling + res.setHeader('Connection', 'keep-alive'); + res.setHeader('Keep-Alive', `timeout=${Math.floor(config.requestTimeout / 1000)}`); + Object.entries(response.headers).forEach(([key, value]) => { // Remove content-encoding since we're requesting uncompressed - if (key.toLowerCase() !== 'content-encoding') { + // Don't override Connection header we just set + if (key.toLowerCase() !== 'content-encoding' && key.toLowerCase() !== 'connection') { res.setHeader(key, value); } });