fix(proxy): improve URL building and Gemini request handling

- Refactor URL construction with version path deduplication (/v1, /v1beta)
- Preserve query parameters for Gemini API requests
- Support GOOGLE_GEMINI_API_KEY field name (with fallback)
- Change default proxy port from 5000 to 15721
- Fix test: use Option type for is_proxy_target field
This commit is contained in:
YoVinchen
2025-12-01 11:38:16 +08:00
Unverified
parent c7ef4365b8
commit 94b83bfa3a
4 changed files with 45 additions and 24 deletions
+1 -1
View File
@@ -245,7 +245,7 @@ fn dry_run_validates_schema_compatibility() {
meta: None,
icon: None,
icon_color: None,
is_proxy_target: false,
is_proxy_target: Some(false),
},
);
+32 -18
View File
@@ -179,12 +179,8 @@ impl RequestForwarder {
// 提取 base_url
let base_url = self.extract_base_url(provider)?;
// 智能拼接 URL,避免重复的 /v1
let url = if base_url.ends_with("/v1") && endpoint.starts_with("/v1") {
format!("{}{}", base_url.trim_end_matches("/v1"), endpoint)
} else {
format!("{base_url}{endpoint}")
};
// 使用辅助函数构建完整 URL(自动去重版本路径)
let url = self.build_full_url(&base_url, endpoint);
// 构建请求
let mut request = self.client.post(&url);
@@ -277,6 +273,28 @@ impl RequestForwarder {
Ok(request)
}
/// 构建完整 URL(智能去重版本路径)
fn build_full_url(&self, base_url: &str, endpoint: &str) -> String {
let base_trimmed = base_url.trim_end_matches('/');
let endpoint_trimmed = endpoint.trim_start_matches('/');
// 检查是否存在版本路径重复
let version_patterns = ["/v1beta", "/v1"];
let mut final_url = format!("{base_trimmed}/{endpoint_trimmed}");
for pattern in &version_patterns {
let duplicate_pattern = format!("{pattern}{pattern}");
if final_url.contains(&duplicate_pattern) {
final_url = final_url.replace(&duplicate_pattern, pattern);
log::debug!(
"URL 去重: 移除重复的 {pattern} (base: {base_url}, endpoint: {endpoint})"
);
}
}
final_url
}
/// 从 Provider 配置中提取 base_url
fn extract_base_url(&self, provider: &Provider) -> Result<String, ProxyError> {
log::debug!("Extracting base_url for provider: {}", provider.name);
@@ -370,8 +388,12 @@ impl RequestForwarder {
return Some((key.to_string(), AuthType::Anthropic));
}
// Gemini
if let Some(key) = env.get("GEMINI_API_KEY").and_then(|v| v.as_str()) {
// Gemini (支持两种字段名,优先使用标准的 GOOGLE_GEMINI_API_KEY)
if let Some(key) = env
.get("GOOGLE_GEMINI_API_KEY")
.or_else(|| env.get("GEMINI_API_KEY"))
.and_then(|v| v.as_str())
{
return Some((key.to_string(), AuthType::Gemini));
}
@@ -540,11 +562,7 @@ impl RequestForwarder {
headers: &axum::http::HeaderMap,
) -> Result<Response, ProxyError> {
let base_url = self.extract_base_url(provider)?;
let url = if base_url.ends_with("/v1") && endpoint.starts_with("/v1") {
format!("{}{}", base_url.trim_end_matches("/v1"), endpoint)
} else {
format!("{base_url}{endpoint}")
};
let url = self.build_full_url(&base_url, endpoint);
log::info!("Proxy GET Request URL: {url}");
@@ -599,11 +617,7 @@ impl RequestForwarder {
headers: &axum::http::HeaderMap,
) -> Result<Response, ProxyError> {
let base_url = self.extract_base_url(provider)?;
let url = if base_url.ends_with("/v1") && endpoint.starts_with("/v1") {
format!("{}{}", base_url.trim_end_matches("/v1"), endpoint)
} else {
format!("{base_url}{endpoint}")
};
let url = self.build_full_url(&base_url, endpoint);
log::info!("Proxy DELETE Request URL: {url}");
+11 -4
View File
@@ -93,10 +93,10 @@ pub async fn handle_count_tokens(
Ok(builder.body(body).unwrap())
}
/// 处理 Gemini API 请求(透传)
/// 处理 Gemini API 请求(透传,包括查询参数
pub async fn handle_gemini(
State(state): State<ProxyState>,
axum::extract::Path(path): axum::extract::Path<String>,
uri: axum::http::Uri,
headers: axum::http::HeaderMap,
Json(body): Json<Value>,
) -> Result<axum::response::Response, ProxyError> {
@@ -108,9 +108,16 @@ pub async fn handle_gemini(
state.status.clone(),
);
let endpoint = format!("/{path}");
// 提取完整的路径和查询参数
let endpoint = uri
.path_and_query()
.map(|pq| pq.as_str())
.unwrap_or(uri.path());
log::debug!("Gemini request endpoint (with query): {endpoint}");
let response = forwarder
.forward_with_retry(&AppType::Gemini, &endpoint, body, headers)
.forward_with_retry(&AppType::Gemini, endpoint, body, headers)
.await?;
// 透传响应
+1 -1
View File
@@ -22,7 +22,7 @@ impl Default for ProxyConfig {
Self {
enabled: false,
listen_address: "127.0.0.1".to_string(),
listen_port: 5000,
listen_port: 15721, // 使用较少占用的高位端口
max_retries: 3,
request_timeout: 300,
enable_logging: true,