diff --git a/src-tauri/src/proxy/forwarder.rs b/src-tauri/src/proxy/forwarder.rs index a36b31a57..90605f4e2 100644 --- a/src-tauri/src/proxy/forwarder.rs +++ b/src-tauri/src/proxy/forwarder.rs @@ -81,7 +81,8 @@ pub struct ForwardError { } pub struct RequestForwarder { - client: Client, + client: Option, + client_init_error: Option, /// 共享的 ProviderRouter(持有熔断器状态) router: Arc, status: Arc>, @@ -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: {}", diff --git a/src-tauri/src/proxy/handlers.rs b/src-tauri/src/proxy/handlers.rs index 0e29aff74..8284483bb 100644 --- a/src-tauri/src/proxy/handlers.rs +++ b/src-tauri/src/proxy/handlers.rs @@ -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}")) + }) } // ============================================================================ diff --git a/src-tauri/src/proxy/response_processor.rs b/src-tauri/src/proxy/response_processor.rs index 0df50e039..550a7f456 100644 --- a/src-tauri/src/proxy/response_processor.rs +++ b/src-tauri/src/proxy/response_processor.rs @@ -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}")) + }) } /// 通用响应处理入口 diff --git a/src-tauri/src/proxy/usage/logger.rs b/src-tauri/src/proxy/usage/logger.rs index 6ff78b39d..a1f001d87 100644 --- a/src-tauri/src/proxy/usage/logger.rs +++ b/src-tauri/src/proxy/usage/logger.rs @@ -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 (