From 854f19d0e14780c315306b416e5393e735a103f8 Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Sat, 28 Mar 2026 13:05:55 +0800 Subject: [PATCH] chore(proxy): remove dead code, redundant tests and debug scaffolding - Inline should_force_identity_encoding() (was just `needs_transform`) and delete its 5 test cases - Remove ExtensionDebugMarker diagnostic type - Remove unused has_system_proxy_env() and its test - Remove strip_entity_headers test - Simplify hyper path: remove redundant is_socks_proxy ternary - Update hyper_client module doc to reflect CONNECT tunnel support --- src-tauri/src/proxy/forwarder.rs | 98 +---------------------- src-tauri/src/proxy/http_client.rs | 36 --------- src-tauri/src/proxy/hyper_client.rs | 16 +--- src-tauri/src/proxy/response_processor.rs | 16 ---- 4 files changed, 6 insertions(+), 160 deletions(-) diff --git a/src-tauri/src/proxy/forwarder.rs b/src-tauri/src/proxy/forwarder.rs index cf30e2d69..4ef2d53d0 100644 --- a/src-tauri/src/proxy/forwarder.rs +++ b/src-tauri/src/proxy/forwarder.rs @@ -794,12 +794,7 @@ impl RequestForwarder { // 过滤私有参数(以 `_` 开头的字段),防止内部信息泄露到上游 // 默认使用空白名单,过滤所有 _ 前缀字段 let filtered_body = filter_private_params_with_whitelist(request_body, &[]); - let force_identity_encoding = should_force_identity_encoding( - effective_endpoint, - &filtered_body, - headers, - needs_transform, - ); + let force_identity_encoding = needs_transform; // 获取认证头(提前准备,用于内联替换) let auth_headers = if let Some(mut auth) = adapter.extract_auth(provider) { @@ -1124,21 +1119,14 @@ impl RequestForwarder { } else { // HTTP 代理或直连:走 hyper raw write(保持 header 大小写) // 如果有 HTTP 代理,hyper_client 会用 CONNECT 隧道穿过代理 - let http_proxy = if is_socks_proxy { - None - } else { - upstream_proxy_url.as_deref() - }; - let mut ext = extensions.clone(); - ext.insert(super::hyper_client::ExtensionDebugMarker); super::hyper_client::send_request( uri, http::Method::POST, ordered_headers, - ext, + extensions.clone(), body_bytes, timeout, - http_proxy, + upstream_proxy_url.as_deref(), ) .await? }; @@ -1305,22 +1293,6 @@ fn extract_json_error_message(body: &Value) -> Option { .find_map(|value| value.as_str().map(ToString::to_string)) } -/// Determine whether to force `accept-encoding: identity` on the upstream request. -/// -/// Only forced for **transform** paths where the proxy must decompress and re-encode -/// the response body. For transparent passthrough (including streaming / SSE), the -/// client's original `accept-encoding` is preserved so the wire behaviour matches a -/// direct connection. SSE usage parsing may fail on compressed streams, but the bytes -/// are still forwarded correctly to the client. -fn should_force_identity_encoding( - _endpoint: &str, - _body: &Value, - _headers: &axum::http::HeaderMap, - needs_transform: bool, -) -> bool { - needs_transform -} - fn summarize_text_for_log(text: &str, max_chars: usize) -> String { let normalized = text.split_whitespace().collect::>().join(" "); let trimmed = normalized.trim(); @@ -1337,7 +1309,6 @@ fn summarize_text_for_log(text: &str, max_chars: usize) -> String { #[cfg(test)] mod tests { use super::*; - use axum::http::{header::ACCEPT, HeaderMap, HeaderValue}; use serde_json::json; #[test] @@ -1405,67 +1376,4 @@ mod tests { assert_eq!(summary, "line1 line2..."); } - #[test] - fn force_identity_for_transform_requests() { - let headers = HeaderMap::new(); - - assert!(should_force_identity_encoding( - "/v1/messages", - &json!({ "model": "gpt-5" }), - &headers, - true, - )); - } - - #[test] - fn transparent_encoding_for_streaming_passthrough() { - let headers = HeaderMap::new(); - - // Streaming passthrough should NOT force identity — transparent proxy - assert!(!should_force_identity_encoding( - "/v1/responses", - &json!({ "stream": true }), - &headers, - false, - )); - } - - #[test] - fn transparent_encoding_for_gemini_stream_endpoints() { - let headers = HeaderMap::new(); - - // SSE endpoints without transform: transparent - assert!(!should_force_identity_encoding( - "/v1beta/models/gemini-2.5-pro:streamGenerateContent?alt=sse", - &json!({ "model": "gemini-2.5-pro" }), - &headers, - false, - )); - } - - #[test] - fn transparent_encoding_for_sse_accept_header() { - let mut headers = HeaderMap::new(); - headers.insert(ACCEPT, HeaderValue::from_static("text/event-stream")); - - // SSE accept header without transform: transparent - assert!(!should_force_identity_encoding( - "/v1/responses", - &json!({ "model": "gpt-5" }), - &headers, - false, - )); - } - - #[test] - fn non_streaming_requests_keep_original_encoding_behavior() { - let headers = HeaderMap::new(); - - assert!(!should_force_identity_encoding( - "/v1/responses", - &json!({ "model": "gpt-5" }), - &headers, - false, - )); - } } diff --git a/src-tauri/src/proxy/http_client.rs b/src-tauri/src/proxy/http_client.rs index 147fdadf2..3fababd64 100644 --- a/src-tauri/src/proxy/http_client.rs +++ b/src-tauri/src/proxy/http_client.rs @@ -280,28 +280,6 @@ fn system_proxy_points_to_loopback() -> bool { .any(|value| proxy_points_to_loopback(&value)) } -/// 检查当前环境是否存在可用的系统代理配置。 -#[allow(dead_code)] -pub fn has_system_proxy_env() -> bool { - const KEYS: [&str; 6] = [ - "HTTP_PROXY", - "http_proxy", - "HTTPS_PROXY", - "https_proxy", - "ALL_PROXY", - "all_proxy", - ]; - - if system_proxy_points_to_loopback() { - return false; - } - - KEYS.iter() - .filter_map(|key| env::var(key).ok()) - .map(|value| value.trim().to_string()) - .any(|value| !value.is_empty()) -} - fn proxy_points_to_loopback(value: &str) -> bool { fn host_is_loopback(host: &str) -> bool { if host.eq_ignore_ascii_case("localhost") { @@ -567,18 +545,4 @@ mod tests { } } - #[test] - fn test_has_system_proxy_env_ignores_own_loopback_proxy() { - let _guard = env_lock().lock().unwrap(); - set_proxy_port(15721); - std::env::remove_var("HTTP_PROXY"); - - std::env::set_var("HTTP_PROXY", "http://127.0.0.1:15721"); - assert!(!has_system_proxy_env()); - - std::env::set_var("HTTP_PROXY", "http://127.0.0.1:7890"); - assert!(has_system_proxy_env()); - - std::env::remove_var("HTTP_PROXY"); - } } diff --git a/src-tauri/src/proxy/hyper_client.rs b/src-tauri/src/proxy/hyper_client.rs index d412ebbb5..c2af8f523 100644 --- a/src-tauri/src/proxy/hyper_client.rs +++ b/src-tauri/src/proxy/hyper_client.rs @@ -1,12 +1,8 @@ //! Hyper-based HTTP client for proxy forwarding //! -//! Uses hyper directly (instead of reqwest) to support: -//! - `preserve_header_case(true)` — keeps original header name casing -//! - Header order preservation via `HeaderCaseMap` extension transfer -//! - `title_case_headers(true)` — fallback if HeaderCaseMap is absent -//! -//! Falls back to reqwest when an upstream proxy (HTTP/SOCKS5) is configured, -//! since hyper-util's legacy client doesn't natively support proxy tunneling. +//! Uses raw TCP/TLS writes to preserve exact original header name casing. +//! Supports HTTP CONNECT tunneling through upstream proxies. +//! Falls back to hyper-util Client (title-case headers) when raw write is not feasible. use super::ProxyError; use bytes::Bytes; @@ -16,12 +12,6 @@ use hyper_rustls::HttpsConnectorBuilder; use hyper_util::{client::legacy::Client, rt::TokioExecutor}; use std::sync::OnceLock; -/// Debug marker inserted into extensions to verify they survive the chain. -/// If this marker is found in hyper_client but HeaderCaseMap is not used, -/// the issue is in hyper's encoder, not in extension passing. -#[derive(Clone, Debug)] -pub(crate) struct ExtensionDebugMarker; - /// Our own header case map: maps lowercase header name → original wire-casing bytes. /// /// This is a backup mechanism independent of hyper's internal `HeaderCaseMap` (which is diff --git a/src-tauri/src/proxy/response_processor.rs b/src-tauri/src/proxy/response_processor.rs index d9baa24c1..bdbd339af 100644 --- a/src-tauri/src/proxy/response_processor.rs +++ b/src-tauri/src/proxy/response_processor.rs @@ -691,22 +691,6 @@ mod tests { assert_eq!(super::strip_sse_field("id:1", "data"), None); } - #[test] - fn strip_entity_headers_removes_encoding_and_length_headers() { - let mut headers = HeaderMap::new(); - headers.insert("content-encoding", "gzip".parse().unwrap()); - headers.insert("content-length", "123".parse().unwrap()); - headers.insert("transfer-encoding", "chunked".parse().unwrap()); - headers.insert("content-type", "application/json".parse().unwrap()); - - strip_entity_headers_for_rebuilt_body(&mut headers); - - assert!(!headers.contains_key("content-encoding")); - assert!(!headers.contains_key("content-length")); - assert!(!headers.contains_key("transfer-encoding")); - assert_eq!(headers.get("content-type").unwrap(), "application/json"); - } - fn build_state(db: Arc) -> ProxyState { ProxyState { db: db.clone(),