fix(proxy): resolve 404 error and auto-setup proxy targets

Issues fixed:
1. Route prefix mismatch - Live config was set to use /claude, /codex,
   /gemini prefixes but server only had routes without prefixes
2. Proxy targets not auto-configured - start_with_takeover only modified
   Live config but didn't set is_proxy_target=true for current providers

Changes:
- Add prefixed routes (/claude/v1/messages, /codex/v1/*, /gemini/v1beta/*)
  to server.rs for backward compatibility
- Remove URL prefixes from takeover_live_configs() - server can route
  by API endpoint path alone (/v1/messages=Claude, /v1/chat/completions=Codex)
- Add setup_proxy_targets() method that automatically sets current providers
  as proxy targets when starting proxy with takeover
- Unify proxy toggle in SettingsPage to use takeover mode (same as main UI)
- Fix error message extraction in useProxyStatus hooks
- Fix provider switch logic to check both takeover flag AND proxy running state
This commit is contained in:
Jason
2025-12-10 19:58:43 +08:00
parent 3cdce2eced
commit 5cc864c6aa
17 changed files with 831 additions and 47 deletions
+55 -21
View File
@@ -525,7 +525,10 @@ fn validate_request_url(request_url: &str, base_url: &str) -> Result<(), AppErro
// 检查端口是否匹配(考虑默认端口)
// 使用 port_or_known_default() 会自动处理默认端口(http->80, https->443
match (parsed_request.port_or_known_default(), parsed_base.port_or_known_default()) {
match (
parsed_request.port_or_known_default(),
parsed_base.port_or_known_default(),
) {
(Some(request_port), Some(base_port)) if request_port == base_port => {
// 端口匹配,继续执行
}
@@ -534,13 +537,11 @@ fn validate_request_url(request_url: &str, base_url: &str) -> Result<(), AppErro
"usage_script.request_port_mismatch",
format!(
"请求端口 {} 必须与 base_url 端口 {} 匹配",
request_port,
base_port
request_port, base_port
),
format!(
"Request port {} must match base_url port {}",
request_port,
base_port
request_port, base_port
),
));
}
@@ -774,22 +775,25 @@ mod tests {
fn test_https_bypass_prevention() {
// 非本地域名的 HTTP 应该被拒绝
let result = validate_base_url("http://127.0.0.1.evil.com/api");
assert!(result.is_err(), "Should reject HTTP for non-localhost domains");
assert!(
result.is_err(),
"Should reject HTTP for non-localhost domains"
);
}
#[test]
fn test_edge_cases() {
// 边界情况测试
assert!(is_private_ip("172.16.0.0")); // RFC1918起始
assert!(is_private_ip("172.16.0.0")); // RFC1918起始
assert!(is_private_ip("172.31.255.255")); // RFC1918结束
assert!(is_private_ip("10.0.0.0")); // 10.0.0.0/8起始
assert!(is_private_ip("10.0.0.0")); // 10.0.0.0/8起始
assert!(is_private_ip("10.255.255.255")); // 10.0.0.0/8结束
assert!(is_private_ip("192.168.0.0")); // 192.168.0.0/16起始
assert!(is_private_ip("192.168.0.0")); // 192.168.0.0/16起始
assert!(is_private_ip("192.168.255.255")); // 192.168.0.0/16结束
// 紧邻RFC1918的公网地址 - 应该返回false
assert!(!is_private_ip("172.15.255.255")); // 172.16.0.0的前一个
assert!(!is_private_ip("172.32.0.0")); // 172.31.255.255的后一个
assert!(!is_private_ip("172.32.0.0")); // 172.31.255.255的后一个
}
#[test]
@@ -815,27 +819,57 @@ mod tests {
// 测试用例:(base_url, request_url, should_match)
let test_cases = vec![
// HTTPS默认端口测试
("https://api.example.com", "https://api.example.com/v1/test", true),
("https://api.example.com", "https://api.example.com:443/v1/test", true),
("https://api.example.com:443", "https://api.example.com/v1/test", true),
("https://api.example.com:443", "https://api.example.com:443/v1/test", true),
(
"https://api.example.com",
"https://api.example.com/v1/test",
true,
),
(
"https://api.example.com",
"https://api.example.com:443/v1/test",
true,
),
(
"https://api.example.com:443",
"https://api.example.com/v1/test",
true,
),
(
"https://api.example.com:443",
"https://api.example.com:443/v1/test",
true,
),
// 端口不匹配测试
("https://api.example.com", "https://api.example.com:8443/v1/test", false),
("https://api.example.com:443", "https://api.example.com:8443/v1/test", false),
(
"https://api.example.com",
"https://api.example.com:8443/v1/test",
false,
),
(
"https://api.example.com:443",
"https://api.example.com:8443/v1/test",
false,
),
];
for (base_url, request_url, should_match) in test_cases {
let result = validate_request_url(request_url, base_url);
if should_match {
assert!(result.is_ok(),
assert!(
result.is_ok(),
"应该匹配的URL被拒绝: base_url={}, request_url={}, error={}",
base_url, request_url, result.unwrap_err());
base_url,
request_url,
result.unwrap_err()
);
} else {
assert!(result.is_err(),
assert!(
result.is_err(),
"应该不匹配的URL被允许: base_url={}, request_url={}",
base_url, request_url);
base_url,
request_url
);
}
}
}