fix: 改进socket hang up和网络错误处理机制

- 修复socket hang up错误导致返回空字符串的问题
- 改进非流式请求的错误处理,根据错误类型返回适当的HTTP状态码
- 优化流式请求的错误处理,返回SSE格式的错误事件
- 增强错误日志记录,包含详细的网络错误信息
- 确保在任何情况下都返回有效的JSON响应格式

修复内容:
- ECONNRESET错误返回502状态码和明确的错误信息
- ENOTFOUND错误返回502状态码和DNS解析失败信息
- ECONNREFUSED错误返回502状态码和连接被拒绝信息
- ETIMEDOUT错误返回504状态码和超时信息
- 流式请求错误时返回符合SSE规范的错误事件

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-07-16 17:56:29 +08:00
parent 06ad6ec440
commit 8ca9ffee68
2 changed files with 155 additions and 18 deletions

View File

@@ -145,13 +145,41 @@ router.post('/v1/messages', authenticateApiKey, async (req, res) => {
logger.api(`✅ Request completed in ${duration}ms for key: ${req.apiKey.name}`);
} catch (error) {
logger.error('❌ Claude relay error:', error);
logger.error('❌ Claude relay error:', error.message, {
code: error.code,
stack: error.stack
});
// 确保在任何情况下都能返回有效的JSON响应
if (!res.headersSent) {
res.status(500).json({
error: 'Relay service error',
message: error.message
// 根据错误类型设置适当的状态码
let statusCode = 500;
let errorType = 'Relay service error';
if (error.message.includes('Connection reset') || error.message.includes('socket hang up')) {
statusCode = 502;
errorType = 'Upstream connection error';
} else if (error.message.includes('Connection refused')) {
statusCode = 502;
errorType = 'Upstream service unavailable';
} else if (error.message.includes('timeout')) {
statusCode = 504;
errorType = 'Upstream timeout';
} else if (error.message.includes('resolve') || error.message.includes('ENOTFOUND')) {
statusCode = 502;
errorType = 'Upstream hostname resolution failed';
}
res.status(statusCode).json({
error: errorType,
message: error.message || 'An unexpected error occurred',
timestamp: new Date().toISOString()
});
} else {
// 如果响应头已经发送,尝试结束响应
if (!res.destroyed && !res.finished) {
res.end();
}
}
}
});

View File

@@ -281,8 +281,27 @@ class ClaudeRelayService {
}
req.on('error', (error) => {
logger.error('❌ Claude API request error:', error);
reject(error);
logger.error('❌ Claude API request error:', error.message, {
code: error.code,
errno: error.errno,
syscall: error.syscall,
address: error.address,
port: error.port
});
// 根据错误类型提供更具体的错误信息
let errorMessage = 'Upstream request failed';
if (error.code === 'ECONNRESET') {
errorMessage = 'Connection reset by Claude API server';
} else if (error.code === 'ENOTFOUND') {
errorMessage = 'Unable to resolve Claude API hostname';
} else if (error.code === 'ECONNREFUSED') {
errorMessage = 'Connection refused by Claude API server';
} else if (error.code === 'ETIMEDOUT') {
errorMessage = 'Connection timed out to Claude API server';
}
reject(new Error(errorMessage));
});
req.on('timeout', () => {
@@ -453,12 +472,46 @@ class ClaudeRelayService {
});
req.on('error', (error) => {
logger.error('❌ Claude stream request error:', error);
if (!responseStream.headersSent) {
responseStream.writeHead(500, { 'Content-Type': 'application/json' });
logger.error('❌ Claude stream request error:', error.message, {
code: error.code,
errno: error.errno,
syscall: error.syscall
});
// 根据错误类型提供更具体的错误信息
let errorMessage = 'Upstream request failed';
let statusCode = 500;
if (error.code === 'ECONNRESET') {
errorMessage = 'Connection reset by Claude API server';
statusCode = 502;
} else if (error.code === 'ENOTFOUND') {
errorMessage = 'Unable to resolve Claude API hostname';
statusCode = 502;
} else if (error.code === 'ECONNREFUSED') {
errorMessage = 'Connection refused by Claude API server';
statusCode = 502;
} else if (error.code === 'ETIMEDOUT') {
errorMessage = 'Connection timed out to Claude API server';
statusCode = 504;
}
if (!responseStream.headersSent) {
responseStream.writeHead(statusCode, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
}
if (!responseStream.destroyed) {
responseStream.end(JSON.stringify({ error: 'Upstream request failed' }));
// 发送 SSE 错误事件
responseStream.write(`event: error\n`);
responseStream.write(`data: ${JSON.stringify({
error: errorMessage,
code: error.code,
timestamp: new Date().toISOString()
})}\n\n`);
responseStream.end();
}
reject(error);
});
@@ -467,10 +520,21 @@ class ClaudeRelayService {
req.destroy();
logger.error('❌ Claude stream request timeout');
if (!responseStream.headersSent) {
responseStream.writeHead(504, { 'Content-Type': 'application/json' });
responseStream.writeHead(504, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
}
if (!responseStream.destroyed) {
responseStream.end(JSON.stringify({ error: 'Request timeout' }));
// 发送 SSE 错误事件
responseStream.write(`event: error\n`);
responseStream.write(`data: ${JSON.stringify({
error: 'Request timeout',
code: 'TIMEOUT',
timestamp: new Date().toISOString()
})}\n\n`);
responseStream.end();
}
reject(new Error('Request timeout'));
});
@@ -538,12 +602,46 @@ class ClaudeRelayService {
});
req.on('error', (error) => {
logger.error('❌ Claude stream request error:', error);
if (!responseStream.headersSent) {
responseStream.writeHead(500, { 'Content-Type': 'application/json' });
logger.error('❌ Claude stream request error:', error.message, {
code: error.code,
errno: error.errno,
syscall: error.syscall
});
// 根据错误类型提供更具体的错误信息
let errorMessage = 'Upstream request failed';
let statusCode = 500;
if (error.code === 'ECONNRESET') {
errorMessage = 'Connection reset by Claude API server';
statusCode = 502;
} else if (error.code === 'ENOTFOUND') {
errorMessage = 'Unable to resolve Claude API hostname';
statusCode = 502;
} else if (error.code === 'ECONNREFUSED') {
errorMessage = 'Connection refused by Claude API server';
statusCode = 502;
} else if (error.code === 'ETIMEDOUT') {
errorMessage = 'Connection timed out to Claude API server';
statusCode = 504;
}
if (!responseStream.headersSent) {
responseStream.writeHead(statusCode, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
}
if (!responseStream.destroyed) {
responseStream.end(JSON.stringify({ error: 'Upstream request failed' }));
// 发送 SSE 错误事件
responseStream.write(`event: error\n`);
responseStream.write(`data: ${JSON.stringify({
error: errorMessage,
code: error.code,
timestamp: new Date().toISOString()
})}\n\n`);
responseStream.end();
}
reject(error);
});
@@ -552,10 +650,21 @@ class ClaudeRelayService {
req.destroy();
logger.error('❌ Claude stream request timeout');
if (!responseStream.headersSent) {
responseStream.writeHead(504, { 'Content-Type': 'application/json' });
responseStream.writeHead(504, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
}
if (!responseStream.destroyed) {
responseStream.end(JSON.stringify({ error: 'Request timeout' }));
// 发送 SSE 错误事件
responseStream.write(`event: error\n`);
responseStream.write(`data: ${JSON.stringify({
error: 'Request timeout',
code: 'TIMEOUT',
timestamp: new Date().toISOString()
})}\n\n`);
responseStream.end();
}
reject(new Error('Request timeout'));
});