mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
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:
@@ -145,13 +145,41 @@ router.post('/v1/messages', authenticateApiKey, async (req, res) => {
|
|||||||
logger.api(`✅ Request completed in ${duration}ms for key: ${req.apiKey.name}`);
|
logger.api(`✅ Request completed in ${duration}ms for key: ${req.apiKey.name}`);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Claude relay error:', error);
|
logger.error('❌ Claude relay error:', error.message, {
|
||||||
|
code: error.code,
|
||||||
if (!res.headersSent) {
|
stack: error.stack
|
||||||
res.status(500).json({
|
|
||||||
error: 'Relay service error',
|
|
||||||
message: error.message
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 确保在任何情况下都能返回有效的JSON响应
|
||||||
|
if (!res.headersSent) {
|
||||||
|
// 根据错误类型设置适当的状态码
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -281,8 +281,27 @@ class ClaudeRelayService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
req.on('error', (error) => {
|
req.on('error', (error) => {
|
||||||
logger.error('❌ Claude API request error:', error);
|
logger.error('❌ Claude API request error:', error.message, {
|
||||||
reject(error);
|
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', () => {
|
req.on('timeout', () => {
|
||||||
@@ -453,12 +472,46 @@ class ClaudeRelayService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
req.on('error', (error) => {
|
req.on('error', (error) => {
|
||||||
logger.error('❌ Claude stream request error:', error);
|
logger.error('❌ Claude stream request error:', error.message, {
|
||||||
if (!responseStream.headersSent) {
|
code: error.code,
|
||||||
responseStream.writeHead(500, { 'Content-Type': 'application/json' });
|
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) {
|
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);
|
reject(error);
|
||||||
});
|
});
|
||||||
@@ -467,10 +520,21 @@ class ClaudeRelayService {
|
|||||||
req.destroy();
|
req.destroy();
|
||||||
logger.error('❌ Claude stream request timeout');
|
logger.error('❌ Claude stream request timeout');
|
||||||
if (!responseStream.headersSent) {
|
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) {
|
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'));
|
reject(new Error('Request timeout'));
|
||||||
});
|
});
|
||||||
@@ -538,12 +602,46 @@ class ClaudeRelayService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
req.on('error', (error) => {
|
req.on('error', (error) => {
|
||||||
logger.error('❌ Claude stream request error:', error);
|
logger.error('❌ Claude stream request error:', error.message, {
|
||||||
if (!responseStream.headersSent) {
|
code: error.code,
|
||||||
responseStream.writeHead(500, { 'Content-Type': 'application/json' });
|
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) {
|
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);
|
reject(error);
|
||||||
});
|
});
|
||||||
@@ -552,10 +650,21 @@ class ClaudeRelayService {
|
|||||||
req.destroy();
|
req.destroy();
|
||||||
logger.error('❌ Claude stream request timeout');
|
logger.error('❌ Claude stream request timeout');
|
||||||
if (!responseStream.headersSent) {
|
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) {
|
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'));
|
reject(new Error('Request timeout'));
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user