feat: add network manager to optimize network request handling

This commit is contained in:
wonfen 2025-04-30 21:30:28 +08:00
parent 3ce5d3bf2d
commit 32b6821b8a
10 changed files with 340 additions and 96 deletions

View File

@ -30,6 +30,7 @@
- 定时自动订阅更新也能自动回退使用代理
- 订阅请求超时机制,防止订阅更新的时候卡死
- 订阅卡片点击时间可切换下次自动更新时间,自动更新触发后页面有明确的成功与否提示
- 添加网络管理器以优化网络请求处理,防止资源竞争导致的启动时 UI 卡死
#### 优化了:
- 系统代理 Bypass 设置

1
src-tauri/Cargo.lock generated
View File

@ -1060,6 +1060,7 @@ dependencies = [
"getrandom 0.3.2",
"image",
"imageproc",
"lazy_static",
"libc",
"log",
"log4rs",

View File

@ -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"

View File

@ -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) {

View File

@ -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<JsonResponse> {
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::<JsonResponse>()
@ -500,13 +500,13 @@ pub async fn check_service() -> Result<JsonResponse> {
/// check the service version
pub async fn check_service_version() -> Result<String> {
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::<VersionJsonResponse>()
@ -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

View File

@ -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<u32> {
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<u32> {
}
Err(err) => {
log::trace!(target: "app", "test_delay error: {:#?}", err);
Err(err.into())
Err(err)
}
}
}

View File

@ -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 {

View File

@ -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]"),
}
}
}

View File

@ -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;

View File

@ -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<Runtime>,
self_proxy_client: Arc<Mutex<Option<Client>>>,
system_proxy_client: Arc<Mutex<Option<Client>>>,
no_proxy_client: Arc<Mutex<Option<Client>>>,
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<u64>,
user_agent: Option<String>,
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<u64>,
user_agent: Option<String>,
accept_invalid_certs: bool,
) -> Result<Response> {
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,
}