From c4e558d377e152b3a516f483c2fb4ee79aa43611 Mon Sep 17 00:00:00 2001 From: wonfen <96291150+wonfen@users.noreply.github.com> Date: Fri, 14 Nov 2025 23:10:56 +0800 Subject: [PATCH] refactor: replace isahc with reqwest (#5463) --- src-tauri/Cargo.lock | 160 +++------------------------------ src-tauri/Cargo.toml | 4 - src-tauri/src/utils/network.rs | 154 +++++++++++++++---------------- 3 files changed, 88 insertions(+), 230 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 861530e0c..f763b7302 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -145,7 +145,7 @@ dependencies = [ "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", - "parking_lot 0.12.5", + "parking_lot", "percent-encoding", "windows-sys 0.60.2", "wl-clipboard-rs", @@ -967,12 +967,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" -[[package]] -name = "castaway" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" - [[package]] name = "castaway" version = "0.2.4" @@ -1133,14 +1127,13 @@ dependencies = [ "futures", "gethostname", "getrandom 0.3.4", - "isahc", "libc", "log", "nanoid", "network-interface", "once_cell", "open", - "parking_lot 0.12.5", + "parking_lot", "percent-encoding", "port_scanner", "regex", @@ -1276,7 +1269,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ - "castaway 0.2.4", + "castaway", "cfg-if", "itoa", "rkyv", @@ -1648,36 +1641,6 @@ dependencies = [ "cipher", ] -[[package]] -name = "curl" -version = "0.4.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79fc3b6dd0b87ba36e565715bf9a2ced221311db47bd18011676f24a6066edbc" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2 0.6.1", - "windows-sys 0.59.0", -] - -[[package]] -name = "curl-sys" -version = "0.4.83+curl-8.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5830daf304027db10c82632a464879d46a3f7c4ba17a31592657ad16c719b483" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "windows-sys 0.59.0", -] - [[package]] name = "darling" version = "0.21.3" @@ -1723,7 +1686,7 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.12", + "parking_lot_core", ] [[package]] @@ -1737,7 +1700,7 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.12", + "parking_lot_core", ] [[package]] @@ -3730,34 +3693,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "isahc" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" -dependencies = [ - "async-channel 1.9.0", - "castaway 0.1.2", - "crossbeam-utils", - "curl", - "curl-sys", - "encoding_rs", - "event-listener 2.5.3", - "futures-lite 1.13.0", - "http 0.2.12", - "log", - "mime", - "once_cell", - "parking_lot 0.11.2", - "polling 2.8.0", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] - [[package]] name = "itertools" version = "0.11.0" @@ -3910,7 +3845,7 @@ dependencies = [ "httparse", "interprocess", "libc", - "parking_lot 0.12.5", + "parking_lot", "path-tree", "pin-project-lite", "rand 0.9.2", @@ -4008,7 +3943,7 @@ checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.5.18", + "redox_syscall", ] [[package]] @@ -4020,18 +3955,6 @@ dependencies = [ "zlib-rs", ] -[[package]] -name = "libz-sys" -version = "1.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -4917,7 +4840,7 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" dependencies = [ - "parking_lot_core 0.9.12", + "parking_lot_core", ] [[package]] @@ -5078,17 +5001,6 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - [[package]] name = "parking_lot" version = "0.12.5" @@ -5096,21 +5008,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", - "parking_lot_core 0.9.12", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -5121,7 +5019,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link 0.2.1", ] @@ -6038,15 +5936,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.18" @@ -6993,17 +6882,6 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel 1.9.0", - "futures-core", - "futures-io", -] - [[package]] name = "small_btree" version = "0.1.0" @@ -7084,7 +6962,7 @@ dependencies = [ "objc2-foundation 0.2.2", "objc2-quartz-core 0.2.2", "raw-window-handle", - "redox_syscall 0.5.18", + "redox_syscall", "wasm-bindgen", "web-sys", "windows-sys 0.59.0", @@ -7141,7 +7019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", - "parking_lot 0.12.5", + "parking_lot", "phf_shared 0.11.3", "precomputed-hash", "serde", @@ -7354,7 +7232,7 @@ dependencies = [ "objc2-app-kit", "objc2-foundation 0.3.2", "once_cell", - "parking_lot 0.12.5", + "parking_lot", "raw-window-handle", "scopeguard", "tao-macros", @@ -8169,7 +8047,7 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot 0.12.5", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2 0.6.1", @@ -8589,16 +8467,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-log" version = "0.2.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 14ae5b2f0..158efb45a 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -70,10 +70,6 @@ gethostname = "1.1.0" scopeguard = "1.2.0" tauri-plugin-notification = "2.3.3" tokio-stream = "0.1.17" -isahc = { version = "1.7.2", default-features = false, features = [ - "text-decoding", - "parking_lot", -] } backoff = { version = "0.4.0", features = ["tokio"] } compact_str = { version = "0.9.0", features = ["serde"] } tauri-plugin-http = "2.5.4" diff --git a/src-tauri/src/utils/network.rs b/src-tauri/src/utils/network.rs index 400bf21c3..d48bb2a6f 100644 --- a/src-tauri/src/utils/network.rs +++ b/src-tauri/src/utils/network.rs @@ -1,22 +1,15 @@ use crate::config::Config; use anyhow::Result; use base64::{Engine as _, engine::general_purpose}; -use isahc::config::DnsCache; -use isahc::prelude::*; -use isahc::{HttpClient, config::SslOption}; -use isahc::{ - config::RedirectPolicy, - http::{ - StatusCode, Uri, - header::{HeaderMap, HeaderValue, USER_AGENT}, - }, +use reqwest::{ + Client, Proxy, StatusCode, + header::{HeaderMap, HeaderValue, USER_AGENT}, }; use smartstring::alias::String; use std::time::{Duration, Instant}; use sysproxy::Sysproxy; use tauri::Url; use tokio::sync::Mutex; -use tokio::time::timeout; #[derive(Debug)] pub struct HttpResponse { @@ -55,9 +48,9 @@ pub enum ProxyType { } pub struct NetworkManager { - self_proxy_client: Mutex>, - system_proxy_client: Mutex>, - no_proxy_client: Mutex>, + self_proxy_client: Mutex>, + system_proxy_client: Mutex>, + no_proxy_client: Mutex>, last_connection_error: Mutex>, connection_error_count: Mutex, } @@ -111,41 +104,42 @@ impl NetworkManager { fn build_client( &self, - proxy_uri: Option, + proxy_url: Option, default_headers: HeaderMap, accept_invalid_certs: bool, timeout_secs: Option, - ) -> Result { - { - let mut builder = HttpClient::builder(); + ) -> Result { + let mut builder = Client::builder() + .redirect(reqwest::redirect::Policy::limited(10)) + .tcp_keepalive(Duration::from_secs(60)) + .pool_max_idle_per_host(0) + .pool_idle_timeout(None); - builder = match proxy_uri { - Some(uri) => builder.proxy(Some(uri)), - None => builder.proxy(None), - }; - - for (name, value) in default_headers.iter() { - builder = builder.default_header(name, value); - } - - if accept_invalid_certs { - builder = builder.ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS); - } - - if let Some(secs) = timeout_secs { - builder = builder.timeout(Duration::from_secs(secs)); - } - - builder = builder.redirect_policy(RedirectPolicy::Follow); - - // 禁用缓存,不关心连接复用 - builder = builder.connection_cache_size(0); - - // 禁用 DNS 缓存,避免因 DNS 变化导致的问题 - builder = builder.dns_cache(DnsCache::Disable); - - Ok(builder.build()?) + // 设置代理 + if let Some(proxy_str) = proxy_url { + let proxy = Proxy::all(proxy_str)?; + builder = builder.proxy(proxy); + } else { + builder = builder.no_proxy(); } + + builder = builder.default_headers(default_headers); + + // SSL/TLS + if accept_invalid_certs { + builder = builder + .danger_accept_invalid_certs(true) + .danger_accept_invalid_hostnames(true); + } + + // 超时设置 + if let Some(secs) = timeout_secs { + builder = builder + .timeout(Duration::from_secs(secs)) + .connect_timeout(Duration::from_secs(secs.min(30))); + } + + Ok(builder.build()?) } pub async fn create_request( @@ -154,8 +148,8 @@ impl NetworkManager { timeout_secs: Option, user_agent: Option, accept_invalid_certs: bool, - ) -> Result { - let proxy_uri = match proxy_type { + ) -> Result { + let proxy_url: Option = match proxy_type { ProxyType::None => None, ProxyType::Localhost => { let port = { @@ -165,13 +159,11 @@ impl NetworkManager { None => Config::clash().await.data_arc().get_mixed_port(), } }; - let proxy_scheme = format!("http://127.0.0.1:{port}"); - proxy_scheme.parse::().ok() + Some(format!("http://127.0.0.1:{port}")) } ProxyType::System => { if let Ok(p @ Sysproxy { enable: true, .. }) = Sysproxy::get_system_proxy() { - let proxy_scheme = format!("http://{}:{}", p.host, p.port); - proxy_scheme.parse::().ok() + Some(format!("http://{}:{}", p.host, p.port)) } else { None } @@ -179,16 +171,18 @@ impl NetworkManager { }; let mut headers = HeaderMap::new(); - headers.insert( - USER_AGENT, - HeaderValue::from_str( - &user_agent.unwrap_or_else(|| { - format!("clash-verge/v{}", env!("CARGO_PKG_VERSION")).into() - }), - )?, - ); - let client = self.build_client(proxy_uri, headers, accept_invalid_certs, timeout_secs)?; + // 设置 User-Agent + if let Some(ua) = user_agent { + headers.insert(USER_AGENT, HeaderValue::from_str(ua.as_str())?); + } else { + headers.insert( + USER_AGENT, + HeaderValue::from_str(&format!("clash-verge/v{}", env!("CARGO_PKG_VERSION")))?, + ); + } + + let client = self.build_client(proxy_url, headers, accept_invalid_certs, timeout_secs)?; Ok(client) } @@ -226,37 +220,37 @@ impl NetworkManager { no_auth.to_string() }; + // 创建请求 let client = self .create_request(proxy_type, timeout_secs, user_agent, accept_invalid_certs) .await?; - let timeout_duration = Duration::from_secs(timeout_secs.unwrap_or(20)); - let response = match timeout(timeout_duration, async { - let mut req = isahc::Request::get(&clean_url); + let mut request_builder = client.get(&clean_url); - for (k, v) in extra_headers.iter() { - req = req.header(k, v); - } + for (key, value) in extra_headers.iter() { + request_builder = request_builder.header(key, value); + } - let mut response = client.send_async(req.body(())?).await?; - let status = response.status(); - let headers = response.headers().clone(); - let body = response.text().await?; - Ok::<_, anyhow::Error>(HttpResponse::new(status, headers, body.into())) - }) - .await - { - Ok(res) => res?, - Err(_) => { - self.record_connection_error(&format!("Request interrupted: {}", url)) + let response = match request_builder.send().await { + Ok(resp) => resp, + Err(e) => { + self.record_connection_error(&format!("Request failed: {}", e)) .await; - return Err(anyhow::anyhow!( - "Request interrupted after {}s", - timeout_duration.as_secs() - )); + return Err(anyhow::anyhow!("Request failed: {}", e)); } }; - Ok(response) + let status = response.status(); + let headers = response.headers().clone(); + let body = match response.text().await { + Ok(text) => text.into(), + Err(e) => { + self.record_connection_error(&format!("Failed to read response body: {}", e)) + .await; + return Err(anyhow::anyhow!("Failed to read response body: {}", e)); + } + }; + + Ok(HttpResponse::new(status, headers, body)) } }