Spaces:
Paused
Paused
Upload unified-server.js
Browse files- unified-server.js +98 -34
unified-server.js
CHANGED
|
@@ -384,7 +384,7 @@ class ConnectionRegistry extends EventEmitter {
|
|
| 384 |
if (queue) {
|
| 385 |
this._routeMessage(parsedMessage, queue);
|
| 386 |
} else {
|
| 387 |
-
this.logger.warn(`[Server] 收到未知请求ID的消息: ${requestId}`);
|
| 388 |
}
|
| 389 |
} catch (error) {
|
| 390 |
this.logger.error('[Server] 解析内部WebSocket消息失败');
|
|
@@ -526,17 +526,34 @@ class RequestHandler {
|
|
| 526 |
return correctedDetails;
|
| 527 |
}
|
| 528 |
|
| 529 |
-
|
| 530 |
-
//
|
| 531 |
-
const
|
| 532 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 533 |
|
| 534 |
-
|
| 535 |
-
const isImmediateSwitch = this.config.immediateSwitchStatusCodes.includes(correctedErrorDetails.status);
|
| 536 |
|
| 537 |
if (isImmediateSwitch) {
|
| 538 |
-
this.logger.warn(`🔴 [Auth] 收到状态码 ${
|
| 539 |
-
if (res) this._sendErrorChunkToClient(res, `收到状态码 ${
|
| 540 |
try {
|
| 541 |
await this._switchToNextAuth();
|
| 542 |
if (res) this._sendErrorChunkToClient(res, `已切换到账号索引 ${this.currentAuthIndex},请重试`);
|
|
@@ -544,13 +561,13 @@ class RequestHandler {
|
|
| 544 |
this.logger.error(`🔴 [Auth] 账号切换失败: ${switchError.message}`);
|
| 545 |
if (res) this._sendErrorChunkToClient(res, `切换账号失败: ${switchError.message}`);
|
| 546 |
}
|
| 547 |
-
return; //
|
| 548 |
}
|
| 549 |
|
| 550 |
-
//
|
| 551 |
if (this.config.failureThreshold > 0) {
|
| 552 |
this.failureCount++;
|
| 553 |
-
this.logger.warn(`⚠️ [Auth] 请求失败 - 失败计数: ${this.failureCount}/${this.config.failureThreshold} (当前账号索引: ${this.currentAuthIndex}, 状态码: ${
|
| 554 |
if (this.failureCount >= this.config.failureThreshold) {
|
| 555 |
this.logger.warn(`🔴 [Auth] 达到失败阈值!准备切换账号...`);
|
| 556 |
if (res) this._sendErrorChunkToClient(res, `连续失败${this.failureCount}次,正在尝试切换账号...`);
|
|
@@ -563,7 +580,7 @@ class RequestHandler {
|
|
| 563 |
}
|
| 564 |
}
|
| 565 |
} else {
|
| 566 |
-
this.logger.warn(`[Auth] 请求失败 (状态码: ${
|
| 567 |
}
|
| 568 |
}
|
| 569 |
|
|
@@ -617,26 +634,25 @@ class RequestHandler {
|
|
| 617 |
this.logger.info(`[Request] 已向客户端发送标准错误信号: ${errorMessage}`);
|
| 618 |
}
|
| 619 |
}
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
this.logger.info('[Request]
|
|
|
|
| 623 |
let connectionMaintainer = null;
|
| 624 |
try {
|
| 625 |
-
|
| 626 |
-
|
|
|
|
|
|
|
| 627 |
let lastMessage, requestFailed = false;
|
| 628 |
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
|
| 629 |
this.logger.info(`[Request] 请求尝试 #${attempt}/${this.maxRetries}...`);
|
| 630 |
this._forwardRequest(proxyRequest);
|
| 631 |
lastMessage = await messageQueue.dequeue();
|
| 632 |
if (lastMessage.event_type === 'error' && lastMessage.status >= 400 && lastMessage.status <= 599) {
|
| 633 |
-
|
| 634 |
-
// --- START: MODIFICATION ---
|
| 635 |
const correctedMessage = this._parseAndCorrectErrorDetails(lastMessage);
|
| 636 |
await this._handleRequestFailureAndSwitch(correctedMessage, res);
|
| 637 |
const errorText = `收到 ${correctedMessage.status} 错误。${attempt < this.maxRetries ? `将在 ${this.retryDelay / 1000}秒后重试...` : '已达到最大重试次数。'}`;
|
| 638 |
-
// --- END: MODIFICATION ---
|
| 639 |
-
|
| 640 |
this._sendErrorChunkToClient(res, errorText);
|
| 641 |
if (attempt < this.maxRetries) {
|
| 642 |
await new Promise(resolve => setTimeout(resolve, this.retryDelay));
|
|
@@ -646,31 +662,73 @@ class RequestHandler {
|
|
| 646 |
}
|
| 647 |
break;
|
| 648 |
}
|
| 649 |
-
// --- START: MODIFICATION ---
|
| 650 |
if (lastMessage.event_type === 'error' || requestFailed) {
|
| 651 |
const finalError = this._parseAndCorrectErrorDetails(lastMessage);
|
| 652 |
-
// 抛出错误,以便被外层 catch 块捕获,并使用修正后的信息
|
| 653 |
throw new Error(`请求失败 (状态码: ${finalError.status}): ${finalError.message}`);
|
| 654 |
}
|
| 655 |
-
// --- END: MODIFICATION ---
|
| 656 |
|
| 657 |
if (this.failureCount > 0) {
|
| 658 |
this.logger.info(`✅ [Auth] 请求成功 - 失败计数已从 ${this.failureCount} 重置为 0`);
|
| 659 |
}
|
| 660 |
this.failureCount = 0;
|
|
|
|
| 661 |
const dataMessage = await messageQueue.dequeue();
|
| 662 |
const endMessage = await messageQueue.dequeue();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 663 |
if (dataMessage.data) {
|
| 664 |
-
|
| 665 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 666 |
}
|
| 667 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 668 |
} finally {
|
| 669 |
if (connectionMaintainer) clearInterval(connectionMaintainer);
|
| 670 |
if (!res.writableEnded) res.end();
|
| 671 |
this.logger.info('[Request] 假流式响应处理结束。');
|
| 672 |
}
|
| 673 |
}
|
|
|
|
| 674 |
async _handleRealStreamResponse(proxyRequest, messageQueue, res) {
|
| 675 |
let headerMessage, requestFailed = false;
|
| 676 |
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
|
|
@@ -900,16 +958,22 @@ class ProxyServerSystem extends EventEmitter {
|
|
| 900 |
let clientKey = null;
|
| 901 |
let keySource = null;
|
| 902 |
|
|
|
|
| 903 |
const headers = req.headers;
|
| 904 |
|
| 905 |
-
|
| 906 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 907 |
keySource = 'x-goog-api-key Header';
|
| 908 |
-
} else if (
|
| 909 |
-
clientKey =
|
| 910 |
keySource = 'Authorization Header';
|
| 911 |
-
} else if (
|
| 912 |
-
clientKey =
|
| 913 |
keySource = 'X-API-Key Header';
|
| 914 |
} else if (req.query.key) {
|
| 915 |
clientKey = req.query.key;
|
|
|
|
| 384 |
if (queue) {
|
| 385 |
this._routeMessage(parsedMessage, queue);
|
| 386 |
} else {
|
| 387 |
+
//this.logger.warn(`[Server] 收到未知请求ID的消息: ${requestId}`);
|
| 388 |
}
|
| 389 |
} catch (error) {
|
| 390 |
this.logger.error('[Server] 解析内部WebSocket消息失败');
|
|
|
|
| 526 |
return correctedDetails;
|
| 527 |
}
|
| 528 |
|
| 529 |
+
async _handleRequestFailureAndSwitch(errorDetails, res) {
|
| 530 |
+
// 创建一个副本进行操作,并进行深度解析
|
| 531 |
+
const correctedDetails = { ...errorDetails };
|
| 532 |
+
if (correctedDetails.message && typeof correctedDetails.message === 'string') {
|
| 533 |
+
// 增强版正则表达式,能匹配 "HTTP 429" 或 JSON 中的 "code":429 等多种模式
|
| 534 |
+
const regex = /(?:HTTP|status code)\s*(\d{3})|"code"\s*:\s*(\d{3})/;
|
| 535 |
+
const match = correctedDetails.message.match(regex);
|
| 536 |
+
|
| 537 |
+
// match[1] 对应 (?:HTTP|status code)\s*(\d{3})
|
| 538 |
+
// match[2] 对应 "code"\s*:\s*(\d{3})
|
| 539 |
+
const parsedStatusString = match ? (match[1] || match[2]) : null;
|
| 540 |
+
|
| 541 |
+
if (parsedStatusString) {
|
| 542 |
+
const parsedStatus = parseInt(parsedStatusString, 10);
|
| 543 |
+
if (parsedStatus >= 400 && parsedStatus <= 599 && correctedDetails.status !== parsedStatus) {
|
| 544 |
+
this.logger.warn(`[Auth] 修正了错误状态码!原始: ${correctedDetails.status}, 从消息中解析得到: ${parsedStatus}`);
|
| 545 |
+
correctedDetails.status = parsedStatus;
|
| 546 |
+
}
|
| 547 |
+
}
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
// --- 后续逻辑使用修正后的 correctedDetails ---
|
| 551 |
|
| 552 |
+
const isImmediateSwitch = this.config.immediateSwitchStatusCodes.includes(correctedDetails.status);
|
|
|
|
| 553 |
|
| 554 |
if (isImmediateSwitch) {
|
| 555 |
+
this.logger.warn(`🔴 [Auth] 收到状态码 ${correctedDetails.status} (已修正),触发立即切换账号...`);
|
| 556 |
+
if (res) this._sendErrorChunkToClient(res, `收到状态码 ${correctedDetails.status},正在尝试切换账号...`);
|
| 557 |
try {
|
| 558 |
await this._switchToNextAuth();
|
| 559 |
if (res) this._sendErrorChunkToClient(res, `已切换到账号索引 ${this.currentAuthIndex},请重试`);
|
|
|
|
| 561 |
this.logger.error(`🔴 [Auth] 账号切换失败: ${switchError.message}`);
|
| 562 |
if (res) this._sendErrorChunkToClient(res, `切换账号失败: ${switchError.message}`);
|
| 563 |
}
|
| 564 |
+
return; // 结束函数,外层循环将进行重试
|
| 565 |
}
|
| 566 |
|
| 567 |
+
// 基于失败计数的切换逻辑
|
| 568 |
if (this.config.failureThreshold > 0) {
|
| 569 |
this.failureCount++;
|
| 570 |
+
this.logger.warn(`⚠️ [Auth] 请求失败 - 失败计数: ${this.failureCount}/${this.config.failureThreshold} (当前账号索引: ${this.currentAuthIndex}, 状态码: ${correctedDetails.status})`);
|
| 571 |
if (this.failureCount >= this.config.failureThreshold) {
|
| 572 |
this.logger.warn(`🔴 [Auth] 达到失败阈值!准备切换账号...`);
|
| 573 |
if (res) this._sendErrorChunkToClient(res, `连续失败${this.failureCount}次,正在尝试切换账号...`);
|
|
|
|
| 580 |
}
|
| 581 |
}
|
| 582 |
} else {
|
| 583 |
+
this.logger.warn(`[Auth] 请求失败 (状态码: ${correctedDetails.status})。基于计数的自动切换已禁用 (failureThreshold=0)`);
|
| 584 |
}
|
| 585 |
}
|
| 586 |
|
|
|
|
| 634 |
this.logger.info(`[Request] 已向客户端发送标准错误信号: ${errorMessage}`);
|
| 635 |
}
|
| 636 |
}
|
| 637 |
+
async _handlePseudoStreamResponse(proxyRequest, messageQueue, req, res) {
|
| 638 |
+
// 注意:我们不再立即发送响应头,因为响应类型(SSE或JSON)尚未确定。
|
| 639 |
+
this.logger.info('[Request] 进入假流式处理流程,将根据原始请求路径决定最终响应格式。');
|
| 640 |
+
|
| 641 |
let connectionMaintainer = null;
|
| 642 |
try {
|
| 643 |
+
// 为了防止连接过早断开,可以先启动一个通用的心跳计时器
|
| 644 |
+
// 这个计时器只发送注释行,对SSE和JSON客户端都无害,但能保持连接
|
| 645 |
+
connectionMaintainer = setInterval(() => { if (!res.writableEnded) { res.write(': keep-alive\n\n'); } }, 15000);
|
| 646 |
+
|
| 647 |
let lastMessage, requestFailed = false;
|
| 648 |
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
|
| 649 |
this.logger.info(`[Request] 请求尝试 #${attempt}/${this.maxRetries}...`);
|
| 650 |
this._forwardRequest(proxyRequest);
|
| 651 |
lastMessage = await messageQueue.dequeue();
|
| 652 |
if (lastMessage.event_type === 'error' && lastMessage.status >= 400 && lastMessage.status <= 599) {
|
|
|
|
|
|
|
| 653 |
const correctedMessage = this._parseAndCorrectErrorDetails(lastMessage);
|
| 654 |
await this._handleRequestFailureAndSwitch(correctedMessage, res);
|
| 655 |
const errorText = `收到 ${correctedMessage.status} 错误。${attempt < this.maxRetries ? `将在 ${this.retryDelay / 1000}秒后重试...` : '已达到最大重试次数。'}`;
|
|
|
|
|
|
|
| 656 |
this._sendErrorChunkToClient(res, errorText);
|
| 657 |
if (attempt < this.maxRetries) {
|
| 658 |
await new Promise(resolve => setTimeout(resolve, this.retryDelay));
|
|
|
|
| 662 |
}
|
| 663 |
break;
|
| 664 |
}
|
|
|
|
| 665 |
if (lastMessage.event_type === 'error' || requestFailed) {
|
| 666 |
const finalError = this._parseAndCorrectErrorDetails(lastMessage);
|
|
|
|
| 667 |
throw new Error(`请求失败 (状态码: ${finalError.status}): ${finalError.message}`);
|
| 668 |
}
|
|
|
|
| 669 |
|
| 670 |
if (this.failureCount > 0) {
|
| 671 |
this.logger.info(`✅ [Auth] 请求成功 - 失败计数已从 ${this.failureCount} 重置为 0`);
|
| 672 |
}
|
| 673 |
this.failureCount = 0;
|
| 674 |
+
|
| 675 |
const dataMessage = await messageQueue.dequeue();
|
| 676 |
const endMessage = await messageQueue.dequeue();
|
| 677 |
+
if (endMessage.type !== 'STREAM_END') this.logger.warn('[Request] 未收到预期的流结束信号。');
|
| 678 |
+
|
| 679 |
+
// 停止心跳计时器,因为我们即将发送最终数据
|
| 680 |
+
if (connectionMaintainer) clearInterval(connectionMaintainer);
|
| 681 |
+
|
| 682 |
if (dataMessage.data) {
|
| 683 |
+
// ======================= START: CORE LOGIC CHANGE =======================
|
| 684 |
+
// 检查原始请求路径,判断客户端期望的是流还是普通JSON
|
| 685 |
+
const originalPath = req.path;
|
| 686 |
+
const isStreamRequest = originalPath.includes(':stream');
|
| 687 |
+
|
| 688 |
+
if (isStreamRequest) {
|
| 689 |
+
// 客户端想要一个流,我们模拟它 (保持原有逻辑)
|
| 690 |
+
this.logger.info(`[Request] 原始请求路径 "${originalPath}" 是流式请求,将模拟SSE响应。`);
|
| 691 |
+
res.status(200).set({
|
| 692 |
+
'Content-Type': 'text/event-stream',
|
| 693 |
+
'Cache-Control': 'no-cache',
|
| 694 |
+
'Connection': 'keep-alive'
|
| 695 |
+
});
|
| 696 |
+
// 发送数据块
|
| 697 |
+
res.write(`data: ${dataMessage.data}\n\n`);
|
| 698 |
+
// 为提高兼容性,模拟一个 [DONE] 结束标志
|
| 699 |
+
res.write('data: [DONE]\n\n');
|
| 700 |
+
this.logger.info('[Request] 已将完整响应体作为模拟SSE事件发送。');
|
| 701 |
+
} else {
|
| 702 |
+
// 客户端想要一个普通JSON,我们直接返回它
|
| 703 |
+
this.logger.info(`[Request] 原始请求路径 "${originalPath}" 是非流式请求,将返回 application/json 响应。`);
|
| 704 |
+
try {
|
| 705 |
+
// 确保我们发送的是有效的JSON
|
| 706 |
+
const jsonData = JSON.parse(dataMessage.data);
|
| 707 |
+
res.status(200).json(jsonData);
|
| 708 |
+
} catch (e) {
|
| 709 |
+
this.logger.error(`[Request] 无法将来自浏览器的响应解析为JSON: ${e.message}`);
|
| 710 |
+
this._sendErrorResponse(res, 500, '代理内部错误:无法解析来自浏览器的响应。');
|
| 711 |
+
}
|
| 712 |
+
}
|
| 713 |
+
// ======================== END: CORE LOGIC CHANGE ========================
|
| 714 |
}
|
| 715 |
+
|
| 716 |
+
} catch (error) {
|
| 717 |
+
// 如果出错时头还没发送,��们可以安全地发送一个错误状态码
|
| 718 |
+
if (!res.headersSent) {
|
| 719 |
+
this._handleRequestError(error, res);
|
| 720 |
+
} else {
|
| 721 |
+
// 如果头已发送(比如在模拟流时),我们只能在现有连接上发送错误块
|
| 722 |
+
this.logger.error(`[Request] 请求处理错误 (头已发送): ${error.message}`);
|
| 723 |
+
this._sendErrorChunkToClient(res, `处理失败: ${error.message}`);
|
| 724 |
+
}
|
| 725 |
} finally {
|
| 726 |
if (connectionMaintainer) clearInterval(connectionMaintainer);
|
| 727 |
if (!res.writableEnded) res.end();
|
| 728 |
this.logger.info('[Request] 假流式响应处理结束。');
|
| 729 |
}
|
| 730 |
}
|
| 731 |
+
|
| 732 |
async _handleRealStreamResponse(proxyRequest, messageQueue, res) {
|
| 733 |
let headerMessage, requestFailed = false;
|
| 734 |
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
|
|
|
|
| 958 |
let clientKey = null;
|
| 959 |
let keySource = null;
|
| 960 |
|
| 961 |
+
// 在Express中, 所有请求头的键名都会被自动转换为小写。
|
| 962 |
const headers = req.headers;
|
| 963 |
|
| 964 |
+
// 为了健壮性, 同时检查使用连字符(标准)和下划线(常见错误)的头。
|
| 965 |
+
const xGoogApiKey = headers['x-goog-api-key'] || headers['x_goog_api_key'];
|
| 966 |
+
const xApiKey = headers['x-api-key'] || headers['x_api_key'];
|
| 967 |
+
const authHeader = headers.authorization;
|
| 968 |
+
|
| 969 |
+
if (xGoogApiKey) {
|
| 970 |
+
clientKey = xGoogApiKey;
|
| 971 |
keySource = 'x-goog-api-key Header';
|
| 972 |
+
} else if (authHeader && authHeader.startsWith('Bearer ')) {
|
| 973 |
+
clientKey = authHeader.substring(7);
|
| 974 |
keySource = 'Authorization Header';
|
| 975 |
+
} else if (xApiKey) {
|
| 976 |
+
clientKey = xApiKey;
|
| 977 |
keySource = 'X-API-Key Header';
|
| 978 |
} else if (req.query.key) {
|
| 979 |
clientKey = req.query.key;
|