fix(gateway): restore HTTP and WS upstream support

This commit is contained in:
elky
2026-09-07 22:15:05 +08:00
parent 7847ae98c6
commit 7aa0c89244
5 changed files with 105 additions and 49 deletions
@@ -129,9 +129,6 @@ pub(crate) fn normalize_admin_base_url(base_url: &str) -> Result<String, String>
if parsed.host_str().is_none() {
return Err("base_url 必须包含有效主机".to_string());
}
if !aether_http::is_https_or_loopback_http_url(&parsed) {
return Err("base_url 必须使用 HTTPS;HTTP 仅允许字面量 loopback 主机".to_string());
}
if !parsed.username().is_empty() || parsed.password().is_some() {
return Err("base_url 不允许包含用户名或密码".to_string());
}
@@ -154,16 +151,42 @@ mod normalize_admin_base_url_tests {
"https://user:[email protected]/v1",
"https://api.example.test/v1?key=secret",
"https://api.example.test/v1#secret",
"http://api.example.test/v1",
"http://10.0.0.1/v1",
"http://[::ffff:127.0.0.1]/v1",
"http://user:password@api.example.test/v1",
"http://api.example.test/v1?key=secret",
"http://api.example.test/v1#secret",
"ftp://api.example.test/v1",
"file:///v1",
"api.example.test/v1",
"",
"https://",
"http://",
"https://api.example.test:invalid/v1",
] {
assert!(normalize_admin_base_url(value).is_err(), "accepted {value}");
}
}
#[test]
fn endpoint_base_url_accepts_remote_http_hosts() {
for (raw_url, expected) in [
(
" HTTP://API.EXAMPLE.TEST:8080/v1/ ",
"http://api.example.test:8080/v1",
),
("http://8.8.8.8:8080/v1/", "http://8.8.8.8:8080/v1"),
("http://10.0.0.1:8080/v1/", "http://10.0.0.1:8080/v1"),
(
"http://[2606:4700:4700::1111]:8080/v1/",
"http://[2606:4700:4700::1111]:8080/v1",
),
] {
assert_eq!(
normalize_admin_base_url(raw_url).expect("HTTP base URL should be accepted"),
expected,
);
}
}
#[test]
fn endpoint_base_url_is_parsed_and_normalized() {
assert_eq!(
@@ -53,9 +53,6 @@ async fn resolve_test_connection_target(
{
return Err("provider endpoint must be an HTTP(S) URL without credentials or fragment");
}
if url.scheme() == "http" && !(allow_private_targets && literal_loopback) {
return Err("provider endpoint must use HTTPS");
}
let host = url
.host_str()
.ok_or("provider endpoint is missing a host")?
@@ -574,10 +571,11 @@ mod tests {
async fn test_connection_target_rejects_private_addresses_in_production_mode() {
for raw_url in [
"http://127.0.0.1:8080/v1/chat/completions",
"http://10.0.0.1/v1/chat/completions",
"http://169.254.169.254/v1/chat/completions",
"https://10.0.0.1/v1/chat/completions",
"https://[::1]/v1/chat/completions",
"https://localhost/v1/chat/completions",
"http://8.8.8.8/v1/chat/completions",
] {
assert!(
resolve_test_connection_target(raw_url, false)
@@ -588,6 +586,26 @@ mod tests {
}
}
#[tokio::test]
async fn test_connection_target_accepts_public_http_and_https_addresses() {
for allow_private_targets in [false, true] {
for (raw_url, expected_port) in [
("http://8.8.8.8/v1/chat", 80),
("http://8.8.8.8:8080/v1/chat", 8080),
("https://8.8.8.8/v1/chat", 443),
] {
let target = resolve_test_connection_target(raw_url, allow_private_targets)
.await
.expect("public HTTP(S) provider target should resolve");
assert_eq!(target.url.as_str(), raw_url);
assert_eq!(target.host, "8.8.8.8");
assert_eq!(target.addresses.len(), 1);
assert_eq!(target.addresses[0].ip().to_string(), "8.8.8.8");
assert_eq!(target.addresses[0].port(), expected_port);
}
}
}
#[tokio::test]
async fn test_connection_target_allows_loopback_only_for_test_fixtures() {
let target = resolve_test_connection_target("http://127.0.0.1:8080/v1/chat", true)
@@ -596,10 +614,10 @@ mod tests {
assert_eq!(target.host, "127.0.0.1");
assert_eq!(target.addresses.len(), 1);
assert!(
resolve_test_connection_target("http://8.8.8.8/v1/chat", true)
resolve_test_connection_target("http://10.0.0.1/v1/chat", true)
.await
.is_err(),
"test mode must not make cleartext public endpoints acceptable"
"test mode must not make private non-loopback HTTP endpoints acceptable"
);
assert!(
resolve_test_connection_target("https://10.0.0.1/v1/chat", true)
@@ -620,6 +638,9 @@ mod tests {
for raw_url in [
"https://user:[email protected]/v1/chat",
"https://example.com/v1/chat#fragment",
"http://user:[email protected]/v1/chat",
"http://example.com/v1/chat#fragment",
"ftp://example.com/v1/chat",
] {
assert!(
resolve_test_connection_target(raw_url, false)