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:
Jason
2026-01-09 00:21:44 +08:00
parent d954df0c76
commit 37239f292c
4 changed files with 73 additions and 22 deletions
+52 -16
View File
@@ -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 KeyProvider: {}",
+4 -1
View File
@@ -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}"))
})
}
// ============================================================================
+12 -3
View File
@@ -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}"))
})
}
/// 通用响应处理入口
+5 -2
View File
@@ -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 (