请求路径也拦住写歪的 Tripo 网关地址

- server-rs/crates/api-server/src/tripo3d/provider.rs:URL 判据(合法 URL + HTTP(S) + 有主机名)抽成 validate_tripo_base_url_syntax,启动期门禁与 tripo_settings 共用;非空但缺 scheme / 非 HTTP(S) / 无主机名的地址在请求前就映射 503,不再拖到 SDK 里变成一条指向连接失败的报错
- server-rs/crates/api-server/src/tripo3d/provider.rs:补请求期拒绝写歪地址的用例
This commit is contained in:
2026-09-24 17:38:28 +08:00
parent 62600fe881
commit 110354fad9
@@ -54,17 +54,7 @@ pub(crate) fn validate_tripo_startup_values(values: TripoStartupValues<'_>) -> R
"{TRIPO_BASE_URL_ENV} 未配置:3D 生成要求显式给出 Tripo 网关地址,缺失时 api-server 拒绝启动。"
));
}
let url = reqwest::Url::parse(base_url)
.map_err(|error| format!("{TRIPO_BASE_URL_ENV} 不是合法 URL{error})。"))?;
if !matches!(url.scheme(), "http" | "https") {
return Err(format!(
"{TRIPO_BASE_URL_ENV} 必须是 HTTP/HTTPS 地址,当前为 {}",
url.scheme()
));
}
if url.host_str().is_none() {
return Err(format!("{TRIPO_BASE_URL_ENV} 缺少主机名。"));
}
validate_tripo_base_url_syntax(base_url)?;
if values
.api_key
.map(str::trim)
@@ -99,6 +89,25 @@ pub(crate) fn validate_tripo_startup_values(values: TripoStartupValues<'_>) -> R
Ok(())
}
/// 网关地址写法校验:必须是带主机名的 HTTP(S) 绝对地址。
///
/// 启动期门禁与请求期兜底共用这一份判据:门禁挡的是环境变量,请求期挡的是
/// 「启动后才被改坏或不由 `from_env` 构造」的配置,两边口径必须一致。
fn validate_tripo_base_url_syntax(base_url: &str) -> Result<(), String> {
let url = reqwest::Url::parse(base_url)
.map_err(|error| format!("{TRIPO_BASE_URL_ENV} 不是合法 URL{error})。"))?;
if !matches!(url.scheme(), "http" | "https") {
return Err(format!(
"{TRIPO_BASE_URL_ENV} 必须是 HTTP/HTTPS 地址,当前为 {}",
url.scheme()
));
}
if url.host_str().is_none() {
return Err(format!("{TRIPO_BASE_URL_ENV} 缺少主机名。"));
}
Ok(())
}
/// 只把「显式声明过」的取值当输入;空串与纯空白视同未声明。
fn read_declared_env(raw: Option<&str>) -> Option<&str> {
raw.map(str::trim).filter(|value| !value.is_empty())
@@ -111,6 +120,9 @@ pub(crate) fn tripo_settings(config: &AppConfig) -> Result<TripoSettings, AppErr
"TRIPO_BASE_URL 未配置,无法调用 Tripo 3D 生成服务。",
));
}
// 非空但写歪的网关地址(缺 scheme、非 HTTP(S)、没有主机名)在这里就判 503:
// 交给 SDK 只会在请求期变成一条指向连接失败的报错,看不出是配置写错了。
validate_tripo_base_url_syntax(base_url).map_err(|message| not_configured(&message))?;
let api_key = config
.tripo_api_key
.as_deref()
@@ -281,6 +293,22 @@ mod tests {
assert_eq!(error.status_code(), StatusCode::SERVICE_UNAVAILABLE);
}
/// 非空但写歪的网关地址在请求期同样以 503 收口,不留到 SDK 里报连接失败。
#[test]
fn request_path_rejects_unusable_gateway_addresses() {
for base_url in ["openapi.tripo3d.com", "ftp://example.com/v3", "https://"] {
let mut config = AppConfig::default();
config.tripo_base_url = base_url.to_string();
config.tripo_api_key = Some("test-key".to_string());
let error = tripo_settings(&config).expect_err("写歪的网关地址必须拒绝");
assert_eq!(
error.status_code(),
StatusCode::SERVICE_UNAVAILABLE,
"{base_url}"
);
}
}
/// 越过重试上限的配置必须在请求前就 503,不能落进 artifact_retries 拉长挂起时间。
#[test]
fn request_path_rejects_out_of_range_retries() {