diff --git a/UPDATELOG.md b/UPDATELOG.md index 217f6b58..7ba6ad88 100644 --- a/UPDATELOG.md +++ b/UPDATELOG.md @@ -30,6 +30,7 @@ - 定时自动订阅更新也能自动回退使用代理 - 订阅请求超时机制,防止订阅更新的时候卡死 - 订阅卡片点击时间可切换下次自动更新时间,自动更新触发后页面有明确的成功与否提示 + - 添加网络管理器以优化网络请求处理,防止资源竞争导致的启动时 UI 卡死 #### 优化了: - 系统代理 Bypass 设置 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 7037b7bd..cf1b068f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1060,6 +1060,7 @@ dependencies = [ "getrandom 0.3.2", "image", "imageproc", + "lazy_static", "libc", "log", "log4rs", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a3488934..57d40cf8 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -30,6 +30,7 @@ boa_engine = "0.20.0" serde_json = "1.0" serde_yaml = "0.9" once_cell = "1.21.3" +lazy_static = "1.4.0" port_scanner = "0.1.5" delay_timer = "0.11.6" parking_lot = "0.12" diff --git a/src-tauri/src/config/prfitem.rs b/src-tauri/src/config/prfitem.rs index 372cf433..62ed8ab7 100644 --- a/src-tauri/src/config/prfitem.rs +++ b/src-tauri/src/config/prfitem.rs @@ -1,10 +1,10 @@ -use crate::utils::{dirs, help, resolve::VERSION, tmpl}; +use crate::utils::network::{NetworkManager, ProxyType}; +use crate::utils::{dirs, help, tmpl}; use anyhow::{bail, Context, Result}; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; use serde_yaml::Mapping; use std::fs; -use sysproxy::Sysproxy; use super::Config; @@ -254,58 +254,25 @@ impl PrfItem { let mut proxies = opt_ref.and_then(|o| o.proxies.clone()); let mut groups = opt_ref.and_then(|o| o.groups.clone()); - // 设置超时时间:连接超时10秒,请求总超时使用配置时间(默认60秒) - let mut builder = reqwest::ClientBuilder::new() - .use_rustls_tls() - .no_proxy() - .connect_timeout(std::time::Duration::from_secs(10)) - .timeout(std::time::Duration::from_secs(timeout)); - - // 使用软件自己的代理 - if self_proxy { - let port = Config::verge() - .latest() - .verge_mixed_port - .unwrap_or(Config::clash().data().get_mixed_port()); - - let proxy_scheme = format!("http://127.0.0.1:{port}"); - - if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) { - builder = builder.proxy(proxy); - } - if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) { - builder = builder.proxy(proxy); - } - if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) { - builder = builder.proxy(proxy); - } - } - // 使用系统代理 - else if with_proxy { - if let Ok(p @ Sysproxy { enable: true, .. }) = Sysproxy::get_system_proxy() { - let proxy_scheme = format!("http://{}:{}", p.host, p.port); - - if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) { - builder = builder.proxy(proxy); - } - if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) { - builder = builder.proxy(proxy); - } - if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) { - builder = builder.proxy(proxy); - } - } - } - - let version = match VERSION.get() { - Some(v) => format!("clash-verge/v{}", v), - None => "clash-verge/unknown".to_string(), + // 选择代理类型 + let proxy_type = if self_proxy { + ProxyType::SelfProxy + } else if with_proxy { + ProxyType::SystemProxy + } else { + ProxyType::NoProxy }; - builder = builder.danger_accept_invalid_certs(accept_invalid_certs); - builder = builder.user_agent(user_agent.unwrap_or(version)); - - let resp = builder.build()?.get(url).send().await?; + // 使用网络管理器发送请求 + let resp = NetworkManager::global() + .get( + url, + proxy_type, + Some(timeout), + user_agent.clone(), + accept_invalid_certs, + ) + .await?; let status_code = resp.status(); if !StatusCode::is_success(&status_code) { diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 03e69939..58ff4f81 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -12,7 +12,6 @@ use std::{ process::Command as StdCommand, time::{SystemTime, UNIX_EPOCH}, }; -use tokio::time::Duration; // Windows only @@ -49,7 +48,8 @@ impl ServiceState { latest.service_state = Some(self.clone()); *config.draft() = latest; config.apply(); - Config::verge().latest().save_file() + let result = config.latest().save_file(); + result } // 更新安装信息 @@ -482,13 +482,13 @@ pub async fn reinstall_service() -> Result<()> { /// check the windows service status pub async fn check_service() -> Result { + use crate::utils::network::{NetworkManager, ProxyType}; + let url = format!("{SERVICE_URL}/get_clash"); - let response = reqwest::ClientBuilder::new() - .no_proxy() - .timeout(Duration::from_secs(3)) - .build()? - .get(url) - .send() + + // 使用无代理模式和3秒超时检查服务 + let response = NetworkManager::global() + .get(&url, ProxyType::NoProxy, Some(3), None, false) .await .context("failed to connect to the Clash Verge Service")? .json::() @@ -500,13 +500,13 @@ pub async fn check_service() -> Result { /// check the service version pub async fn check_service_version() -> Result { + use crate::utils::network::{NetworkManager, ProxyType}; + let url = format!("{SERVICE_URL}/version"); - let response = reqwest::ClientBuilder::new() - .no_proxy() - .timeout(Duration::from_secs(3)) - .build()? - .get(url) - .send() + + // 使用无代理模式和3秒超时检查服务版本 + let response = NetworkManager::global() + .get(&url, ProxyType::NoProxy, Some(3), None, false) .await .context("failed to connect to the Clash Verge Service")? .json::() @@ -568,6 +568,8 @@ pub async fn check_service_needs_reinstall() -> bool { /// 尝试使用现有服务启动核心,不进行重装 pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result<()> { + use crate::utils::network::{NetworkManager, ProxyType}; + log::info!(target:"app", "attempting to start core with existing service"); let clash_core = { Config::verge().latest().clash_core.clone() }; @@ -596,9 +598,10 @@ pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result log::info!(target:"app", "start service: {:?}", map.clone()); let url = format!("{SERVICE_URL}/start_clash"); - let _ = reqwest::ClientBuilder::new() - .no_proxy() - .build()? + + // 使用网络管理器发送POST请求 + let client = NetworkManager::global().get_client(ProxyType::NoProxy); + let _ = client .post(url) .json(&map) .send() @@ -710,10 +713,13 @@ pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> { /// stop the clash by service pub(super) async fn stop_core_by_service() -> Result<()> { + use crate::utils::network::{NetworkManager, ProxyType}; + let url = format!("{SERVICE_URL}/stop_clash"); - let _ = reqwest::ClientBuilder::new() - .no_proxy() - .build()? + + // 使用网络管理器发送POST请求 + let client = NetworkManager::global().get_client(ProxyType::NoProxy); + let _ = client .post(url) .send() .await diff --git a/src-tauri/src/feat/clash.rs b/src-tauri/src/feat/clash.rs index e58b2453..4cf8d96b 100644 --- a/src-tauri/src/feat/clash.rs +++ b/src-tauri/src/feat/clash.rs @@ -91,36 +91,27 @@ pub fn change_clash_mode(mode: String) { /// Test connection delay to a URL pub async fn test_delay(url: String) -> anyhow::Result { - use tokio::time::{Duration, Instant}; - let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy(); + use crate::utils::network::{NetworkManager, ProxyType}; + use tokio::time::Instant; - let port = Config::verge() - .latest() - .verge_mixed_port - .unwrap_or(Config::clash().data().get_mixed_port()); let tun_mode = Config::verge().latest().enable_tun_mode.unwrap_or(false); - let proxy_scheme = format!("http://127.0.0.1:{port}"); + // 如果是TUN模式,不使用代理,否则使用自身代理 + let proxy_type = if !tun_mode { + ProxyType::SelfProxy + } else { + ProxyType::NoProxy + }; - if !tun_mode { - if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) { - builder = builder.proxy(proxy); - } - if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) { - builder = builder.proxy(proxy); - } - if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) { - builder = builder.proxy(proxy); - } - } + let user_agent = Some("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0".to_string()); - let request = builder - .timeout(Duration::from_millis(10000)) - .build()? - .get(url).header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0"); let start = Instant::now(); - let response = request.send().await; + // 使用网络管理器发送请求,设置10秒超时 + let response = NetworkManager::global() + .get(&url, proxy_type, Some(10), user_agent, false) + .await; + match response { Ok(response) => { log::trace!(target: "app", "test_delay response: {:#?}", response); @@ -132,7 +123,7 @@ pub async fn test_delay(url: String) -> anyhow::Result { } Err(err) => { log::trace!(target: "app", "test_delay error: {:#?}", err); - Err(err.into()) + Err(err) } } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c00e865b..2d0430cd 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -86,6 +86,9 @@ impl AppHandleManager { #[allow(clippy::panic)] pub fn run() { + // 初始化网络管理器 + utils::network::NetworkManager::global().init(); + // 单例检测 - 使用超时机制防止阻塞 let app_exists: bool = AsyncHandler::block_on(move || async move { match timeout(Duration::from_secs(3), server::check_singleton()).await { diff --git a/src-tauri/src/utils/logging.rs b/src-tauri/src/utils/logging.rs index c4a64027..3255c1c3 100644 --- a/src-tauri/src/utils/logging.rs +++ b/src-tauri/src/utils/logging.rs @@ -15,6 +15,7 @@ pub enum Type { Frontend, Backup, Lightweight, + Network, } impl fmt::Display for Type { @@ -33,6 +34,7 @@ impl fmt::Display for Type { Type::Frontend => write!(f, "[Frontend]"), Type::Backup => write!(f, "[Backup]"), Type::Lightweight => write!(f, "[Lightweight]"), + Type::Network => write!(f, "[Network]"), } } } diff --git a/src-tauri/src/utils/mod.rs b/src-tauri/src/utils/mod.rs index 7d3edcd0..698b499a 100644 --- a/src-tauri/src/utils/mod.rs +++ b/src-tauri/src/utils/mod.rs @@ -5,6 +5,7 @@ pub mod help; pub mod i18n; pub mod init; pub mod logging; +pub mod network; pub mod resolve; pub mod server; pub mod tmpl; diff --git a/src-tauri/src/utils/network.rs b/src-tauri/src/utils/network.rs new file mode 100644 index 00000000..f65749a4 --- /dev/null +++ b/src-tauri/src/utils/network.rs @@ -0,0 +1,271 @@ +use anyhow::{Context, Result}; +use lazy_static::lazy_static; +use reqwest::{Client, ClientBuilder, Proxy, RequestBuilder, Response}; +use std::sync::{Arc, Mutex, Once}; +use std::time::Duration; +use tokio::runtime::{Builder, Runtime}; + +use crate::{config::Config, logging, utils::logging::Type}; + +/// 网络管理器 +pub struct NetworkManager { + runtime: Arc, + self_proxy_client: Arc>>, + system_proxy_client: Arc>>, + no_proxy_client: Arc>>, + init: Once, +} + +lazy_static! { + static ref NETWORK_MANAGER: NetworkManager = NetworkManager::new(); +} + +impl NetworkManager { + fn new() -> Self { + // 创建专用的异步运行时,线程数限制为4个 + let runtime = Builder::new_multi_thread() + .worker_threads(4) + .thread_name("clash-verge-network") + .enable_io() + .enable_time() + .build() + .expect("Failed to create network runtime"); + + NetworkManager { + runtime: Arc::new(runtime), + self_proxy_client: Arc::new(Mutex::new(None)), + system_proxy_client: Arc::new(Mutex::new(None)), + no_proxy_client: Arc::new(Mutex::new(None)), + init: Once::new(), + } + } + + pub fn global() -> &'static Self { + &NETWORK_MANAGER + } + + /// 初始化网络客户端 + pub fn init(&self) { + self.init.call_once(|| { + self.runtime.spawn(async { + logging!(info, Type::Network, true, "初始化网络管理器"); + + // 创建无代理客户端 + let no_proxy_client = ClientBuilder::new() + .use_rustls_tls() + .no_proxy() + .pool_max_idle_per_host(5) + .pool_idle_timeout(Duration::from_secs(30)) + .connect_timeout(Duration::from_secs(10)) + .timeout(Duration::from_secs(30)) + .build() + .expect("Failed to build no_proxy client"); + + let mut no_proxy_guard = NETWORK_MANAGER.no_proxy_client.lock().unwrap(); + *no_proxy_guard = Some(no_proxy_client); + + logging!(info, Type::Network, true, "网络管理器初始化完成"); + }); + }); + } + + /// 获取或创建自代理客户端 + fn get_or_create_self_proxy_client(&self) -> Client { + let mut client_guard = self.self_proxy_client.lock().unwrap(); + + if client_guard.is_none() { + let port = Config::verge() + .latest() + .verge_mixed_port + .unwrap_or(Config::clash().data().get_mixed_port()); + + let proxy_scheme = format!("http://127.0.0.1:{port}"); + + let mut builder = ClientBuilder::new() + .use_rustls_tls() + .pool_max_idle_per_host(5) + .pool_idle_timeout(Duration::from_secs(30)) + .connect_timeout(Duration::from_secs(10)) + .timeout(Duration::from_secs(60)); + + // 添加所有代理类型 + if let Ok(proxy) = Proxy::http(&proxy_scheme) { + builder = builder.proxy(proxy); + } + if let Ok(proxy) = Proxy::https(&proxy_scheme) { + builder = builder.proxy(proxy); + } + if let Ok(proxy) = Proxy::all(&proxy_scheme) { + builder = builder.proxy(proxy); + } + + let client = builder.build().expect("Failed to build self_proxy client"); + *client_guard = Some(client); + } + + client_guard.as_ref().unwrap().clone() + } + + /// 获取或创建系统代理客户端 + fn get_or_create_system_proxy_client(&self) -> Client { + let mut client_guard = self.system_proxy_client.lock().unwrap(); + + if client_guard.is_none() { + use sysproxy::Sysproxy; + + let mut builder = ClientBuilder::new() + .use_rustls_tls() + .pool_max_idle_per_host(5) + .pool_idle_timeout(Duration::from_secs(30)) + .connect_timeout(Duration::from_secs(10)) + .timeout(Duration::from_secs(60)); + + if let Ok(p @ Sysproxy { enable: true, .. }) = Sysproxy::get_system_proxy() { + let proxy_scheme = format!("http://{}:{}", p.host, p.port); + + if let Ok(proxy) = Proxy::http(&proxy_scheme) { + builder = builder.proxy(proxy); + } + if let Ok(proxy) = Proxy::https(&proxy_scheme) { + builder = builder.proxy(proxy); + } + if let Ok(proxy) = Proxy::all(&proxy_scheme) { + builder = builder.proxy(proxy); + } + } + + let client = builder + .build() + .expect("Failed to build system_proxy client"); + *client_guard = Some(client); + } + + client_guard.as_ref().unwrap().clone() + } + + /// 根据代理设置选择合适的客户端 + pub fn get_client(&self, proxy_type: ProxyType) -> Client { + match proxy_type { + ProxyType::NoProxy => { + let client_guard = self.no_proxy_client.lock().unwrap(); + client_guard.as_ref().unwrap().clone() + } + ProxyType::SelfProxy => self.get_or_create_self_proxy_client(), + ProxyType::SystemProxy => self.get_or_create_system_proxy_client(), + } + } + + /// 创建带有自定义选项的HTTP请求 + pub fn create_request( + &self, + url: &str, + proxy_type: ProxyType, + timeout_secs: Option, + user_agent: Option, + accept_invalid_certs: bool, + ) -> RequestBuilder { + let mut builder = ClientBuilder::new() + .use_rustls_tls() + .connect_timeout(Duration::from_secs(10)); + + // 超时 + if let Some(timeout) = timeout_secs { + builder = builder.timeout(Duration::from_secs(timeout)); + } else { + builder = builder.timeout(Duration::from_secs(60)); + } + + // 设置代理 + match proxy_type { + ProxyType::NoProxy => { + builder = builder.no_proxy(); + } + ProxyType::SelfProxy => { + let port = Config::verge() + .latest() + .verge_mixed_port + .unwrap_or(Config::clash().data().get_mixed_port()); + + let proxy_scheme = format!("http://127.0.0.1:{port}"); + + if let Ok(proxy) = Proxy::http(&proxy_scheme) { + builder = builder.proxy(proxy); + } + if let Ok(proxy) = Proxy::https(&proxy_scheme) { + builder = builder.proxy(proxy); + } + if let Ok(proxy) = Proxy::all(&proxy_scheme) { + builder = builder.proxy(proxy); + } + } + ProxyType::SystemProxy => { + use sysproxy::Sysproxy; + + if let Ok(p @ Sysproxy { enable: true, .. }) = Sysproxy::get_system_proxy() { + let proxy_scheme = format!("http://{}:{}", p.host, p.port); + + if let Ok(proxy) = Proxy::http(&proxy_scheme) { + builder = builder.proxy(proxy); + } + if let Ok(proxy) = Proxy::https(&proxy_scheme) { + builder = builder.proxy(proxy); + } + if let Ok(proxy) = Proxy::all(&proxy_scheme) { + builder = builder.proxy(proxy); + } + } + } + } + + // 证书验证选项 + builder = builder.danger_accept_invalid_certs(accept_invalid_certs); + + // 用户代理 + if let Some(ua) = user_agent { + builder = builder.user_agent(ua); + } else { + use crate::utils::resolve::VERSION; + + let version = match VERSION.get() { + Some(v) => format!("clash-verge/v{}", v), + None => "clash-verge/unknown".to_string(), + }; + + builder = builder.user_agent(version); + } + + // 构建请求 + let client = builder.build().expect("Failed to build custom HTTP client"); + + client.get(url) + } + + /// 执行GET请求 + pub async fn get( + &self, + url: &str, + proxy_type: ProxyType, + timeout_secs: Option, + user_agent: Option, + accept_invalid_certs: bool, + ) -> Result { + self.create_request( + url, + proxy_type, + timeout_secs, + user_agent, + accept_invalid_certs, + ) + .send() + .await + .context("Failed to send HTTP request") + } +} + +/// 代理类型 +#[derive(Debug, Clone, Copy)] +pub enum ProxyType { + NoProxy, + SelfProxy, + SystemProxy, +}