mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-27 08:14:33 +08:00
fix(proxy): replace unwrap/expect with graceful error handling
- Handle HTTP client initialization failure with no_proxy fallback - Fix potential panic on Unicode slicing in API key preview - Add proper error handling for response body builder - Handle edge case where SystemTime is before UNIX_EPOCH
This commit is contained in:
@@ -81,7 +81,8 @@ pub struct ForwardError {
|
||||
}
|
||||
|
||||
pub struct RequestForwarder {
|
||||
client: Client,
|
||||
client: Option<Client>,
|
||||
client_init_error: Option<String>,
|
||||
/// 共享的 ProviderRouter(持有熔断器状态)
|
||||
router: Arc<ProviderRouter>,
|
||||
status: Arc<RwLock<ProxyStatus>>,
|
||||
@@ -111,21 +112,51 @@ impl RequestForwarder {
|
||||
// 参考 Claude Code Hub 的 undici 全局超时设计
|
||||
const GLOBAL_TIMEOUT_SECS: u64 = 1800;
|
||||
|
||||
let mut client_builder = Client::builder();
|
||||
if non_streaming_timeout > 0 {
|
||||
// 使用配置的非流式超时
|
||||
client_builder = client_builder.timeout(Duration::from_secs(non_streaming_timeout));
|
||||
let timeout_secs = if non_streaming_timeout > 0 {
|
||||
non_streaming_timeout
|
||||
} else {
|
||||
// 禁用超时时使用全局超时作为保底
|
||||
client_builder = client_builder.timeout(Duration::from_secs(GLOBAL_TIMEOUT_SECS));
|
||||
}
|
||||
GLOBAL_TIMEOUT_SECS
|
||||
};
|
||||
|
||||
let client = client_builder
|
||||
// 注意:这里不能用 expect/unwrap。
|
||||
// release 配置为 panic=abort,一旦 build 失败会导致整个应用闪退。
|
||||
// 常见原因:用户环境变量里存在不合法/不支持的代理(HTTP(S)_PROXY/ALL_PROXY 等)。
|
||||
let (client, client_init_error) = match Client::builder()
|
||||
.timeout(Duration::from_secs(timeout_secs))
|
||||
.build()
|
||||
.expect("Failed to create HTTP client");
|
||||
{
|
||||
Ok(client) => (Some(client), None),
|
||||
Err(e) => {
|
||||
log::error!("Failed to create HTTP client (will fallback to no_proxy): {e}");
|
||||
|
||||
// 降级:忽略系统/环境代理,避免因代理配置问题导致整个应用崩溃
|
||||
match Client::builder()
|
||||
.timeout(Duration::from_secs(timeout_secs))
|
||||
.no_proxy()
|
||||
.build()
|
||||
{
|
||||
Ok(client) => {
|
||||
log::warn!("HTTP client created with no_proxy() fallback");
|
||||
(Some(client), Some(e.to_string()))
|
||||
}
|
||||
Err(fallback_err) => {
|
||||
log::error!(
|
||||
"Failed to create HTTP client even with no_proxy(): {fallback_err}"
|
||||
);
|
||||
(
|
||||
None,
|
||||
Some(format!(
|
||||
"Failed to create HTTP client: {e}; no_proxy fallback failed: {fallback_err}"
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
client,
|
||||
client_init_error,
|
||||
router,
|
||||
status,
|
||||
current_providers,
|
||||
@@ -503,7 +534,14 @@ impl RequestForwarder {
|
||||
);
|
||||
|
||||
// 构建请求
|
||||
let mut request = self.client.post(&url);
|
||||
let client = self.client.as_ref().ok_or_else(|| {
|
||||
ProxyError::ForwardFailed(
|
||||
self.client_init_error
|
||||
.clone()
|
||||
.unwrap_or_else(|| "HTTP client is not initialized".to_string()),
|
||||
)
|
||||
})?;
|
||||
let mut request = client.post(&url);
|
||||
|
||||
// ========== 详细 Headers 日志 ==========
|
||||
log::info!("[{}] ====== 客户端原始 Headers ======", adapter.name());
|
||||
@@ -599,14 +637,12 @@ impl RequestForwarder {
|
||||
);
|
||||
request = adapter.add_auth_headers(request, &auth);
|
||||
// 记录认证头(脱敏)
|
||||
let key_preview: String = auth.api_key.chars().take(8).collect();
|
||||
passed_headers.push((
|
||||
"authorization".to_string(),
|
||||
format!("Bearer {}...", &auth.api_key[..8.min(auth.api_key.len())]),
|
||||
));
|
||||
passed_headers.push((
|
||||
"x-api-key".to_string(),
|
||||
format!("{}...", &auth.api_key[..8.min(auth.api_key.len())]),
|
||||
format!("Bearer {}...", &key_preview),
|
||||
));
|
||||
passed_headers.push(("x-api-key".to_string(), format!("{}...", &key_preview)));
|
||||
} else {
|
||||
log::error!(
|
||||
"[{}] 未找到 API Key!Provider: {}",
|
||||
|
||||
@@ -291,7 +291,10 @@ async fn handle_claude_transform(
|
||||
);
|
||||
|
||||
let body = axum::body::Body::from(response_body);
|
||||
Ok(builder.body(body).unwrap())
|
||||
builder.body(body).map_err(|e| {
|
||||
log::error!("[Claude] 构建响应失败: {e}");
|
||||
ProxyError::Internal(format!("Failed to build response: {e}"))
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -9,7 +9,7 @@ use super::{
|
||||
usage::parser::TokenUsage,
|
||||
ProxyError,
|
||||
};
|
||||
use axum::response::Response;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use bytes::Bytes;
|
||||
use futures::stream::{Stream, StreamExt};
|
||||
use rust_decimal::Decimal;
|
||||
@@ -72,7 +72,13 @@ pub async fn handle_streaming(
|
||||
create_logged_passthrough_stream(stream, ctx.tag, Some(usage_collector), timeout_config);
|
||||
|
||||
let body = axum::body::Body::from_stream(logged_stream);
|
||||
builder.body(body).unwrap()
|
||||
match builder.body(body) {
|
||||
Ok(resp) => resp,
|
||||
Err(e) => {
|
||||
log::error!("[{}] 构建流式响应失败: {e}", ctx.tag);
|
||||
ProxyError::Internal(format!("Failed to build streaming response: {e}")).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理非流式响应
|
||||
@@ -155,7 +161,10 @@ pub async fn handle_non_streaming(
|
||||
}
|
||||
|
||||
let body = axum::body::Body::from(body_bytes);
|
||||
Ok(builder.body(body).unwrap())
|
||||
builder.body(body).map_err(|e| {
|
||||
log::error!("[{}] 构建响应失败: {e}", ctx.tag);
|
||||
ProxyError::Internal(format!("Failed to build response: {e}"))
|
||||
})
|
||||
}
|
||||
|
||||
/// 通用响应处理入口
|
||||
|
||||
@@ -65,8 +65,11 @@ impl<'a> UsageLogger<'a> {
|
||||
|
||||
let created_at = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
.map(|d| d.as_secs() as i64)
|
||||
.unwrap_or_else(|e| {
|
||||
log::warn!("SystemTime is before UNIX_EPOCH, falling back to 0: {e}");
|
||||
0
|
||||
});
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO proxy_request_logs (
|
||||
|
||||
Reference in New Issue
Block a user