tackle client disconnects
This commit is contained in:
@@ -12,6 +12,31 @@ app.use(express.json({
|
|||||||
type: 'application/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
|
// Health check endpoint
|
||||||
app.get('/health', (req, res) => {
|
app.get('/health', (req, res) => {
|
||||||
res.json({
|
res.json({
|
||||||
@@ -60,6 +85,19 @@ const server = app.listen(config.port, () => {
|
|||||||
}, 'ETH JSON-RPC proxy started');
|
}, '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
|
// Graceful shutdown
|
||||||
process.on('SIGTERM', () => {
|
process.on('SIGTERM', () => {
|
||||||
logger.info('SIGTERM received, shutting down gracefully');
|
logger.info('SIGTERM received, shutting down gracefully');
|
||||||
|
|||||||
@@ -146,14 +146,54 @@ class RPCProxy {
|
|||||||
|
|
||||||
// Handle client disconnect
|
// Handle client disconnect
|
||||||
let clientClosed = false;
|
let clientClosed = false;
|
||||||
|
let clientCloseReason = null;
|
||||||
|
|
||||||
req.on('close', () => {
|
req.on('close', () => {
|
||||||
clientClosed = true;
|
if (!clientClosed) {
|
||||||
logger.warn({ requestId }, 'Client connection closed');
|
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) => {
|
req.on('error', (error) => {
|
||||||
clientClosed = true;
|
if (!clientClosed) {
|
||||||
logger.error({ requestId, error: error.message }, 'Client connection error');
|
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 {
|
try {
|
||||||
@@ -228,9 +268,15 @@ class RPCProxy {
|
|||||||
// Set response headers only if client hasn't closed
|
// Set response headers only if client hasn't closed
|
||||||
if (!isClientClosed() && !res.headersSent) {
|
if (!isClientClosed() && !res.headersSent) {
|
||||||
res.status(response.status);
|
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]) => {
|
Object.entries(response.headers).forEach(([key, value]) => {
|
||||||
// Remove content-encoding since we're requesting uncompressed
|
// 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);
|
res.setHeader(key, value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user