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
This commit is contained in:
YoVinchen
2026-03-28 13:05:55 +08:00
parent 905f7ccbfe
commit 854f19d0e1
4 changed files with 6 additions and 160 deletions
+3 -95
View File
@@ -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<String> {
.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::<Vec<_>>().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,
));
}
}
-36
View File
@@ -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");
}
}
+3 -13
View File
@@ -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
-16
View File
@@ -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<Database>) -> ProxyState {
ProxyState {
db: db.clone(),