Files
clash-proxy/src-tauri/src/utils/network.rs

266 lines
7.6 KiB
Rust
Raw Normal View History

use crate::config::Config;
2025-05-03 23:17:51 +08:00
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 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 {
status: StatusCode,
headers: HeaderMap,
body: String,
}
impl HttpResponse {
pub fn new(status: StatusCode, headers: HeaderMap, body: String) -> Self {
Self {
status,
headers,
body,
}
}
pub fn status(&self) -> StatusCode {
self.status
}
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
pub fn text_with_charset(&self) -> Result<&str> {
Ok(&self.body)
}
}
#[derive(Debug, Clone, Copy)]
pub enum ProxyType {
None,
Localhost,
System,
}
pub struct NetworkManager {
self_proxy_client: Mutex<Option<HttpClient>>,
system_proxy_client: Mutex<Option<HttpClient>>,
no_proxy_client: Mutex<Option<HttpClient>>,
last_connection_error: Mutex<Option<(Instant, String)>>,
connection_error_count: Mutex<usize>,
}
impl Default for NetworkManager {
fn default() -> Self {
Self::new()
}
}
impl NetworkManager {
pub fn new() -> Self {
Self {
self_proxy_client: Mutex::new(None),
system_proxy_client: Mutex::new(None),
no_proxy_client: Mutex::new(None),
last_connection_error: Mutex::new(None),
connection_error_count: Mutex::new(0),
}
}
async fn record_connection_error(&self, error: &str) {
*self.last_connection_error.lock().await = Some((Instant::now(), error.into()));
2025-05-03 23:17:51 +08:00
let mut count = self.connection_error_count.lock().await;
*count += 1;
2025-05-03 23:17:51 +08:00
}
async fn should_reset_clients(&self) -> bool {
let count = *self.connection_error_count.lock().await;
if count > 5 {
2025-05-03 23:17:51 +08:00
return true;
}
if let Some((time, _)) = &*self.last_connection_error.lock().await
&& time.elapsed() < Duration::from_secs(30)
&& count > 2
{
return true;
2025-05-03 23:17:51 +08:00
}
false
}
pub async fn reset_clients(&self) {
*self.self_proxy_client.lock().await = None;
*self.system_proxy_client.lock().await = None;
*self.no_proxy_client.lock().await = None;
*self.connection_error_count.lock().await = 0;
}
fn build_client(
&self,
proxy_uri: Option<Uri>,
default_headers: HeaderMap,
accept_invalid_certs: bool,
timeout_secs: Option<u64>,
) -> Result<HttpClient> {
let proxy_uri_clone = proxy_uri.clone();
let headers_clone = default_headers.clone();
{
let mut builder = HttpClient::builder();
builder = match proxy_uri_clone {
Some(uri) => builder.proxy(Some(uri)),
None => builder.proxy(None),
};
for (name, value) in headers_clone.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()?)
}
2025-05-03 23:17:51 +08:00
}
refactor(async): migrate from sync-blocking async execution to true async with unified AsyncHandler::spawn (#4502) * feat: replace all tokio::spawn with unified AsyncHandler::spawn - 🚀 Core Improvements: * Replace all tokio::spawn calls with AsyncHandler::spawn for unified Tauri async task management * Prioritize converting sync functions to async functions to reduce spawn usage * Use .await directly in async contexts instead of spawn - 🔧 Major Changes: * core/hotkey.rs: Use AsyncHandler::spawn for hotkey callback functions * module/lightweight.rs: Async lightweight mode switching * feat/window.rs: Convert window operation functions to async, use .await internally * feat/proxy.rs, feat/clash.rs: Async proxy and mode switching functions * lib.rs: Window focus handling with AsyncHandler::spawn * core/tray/mod.rs: Complete async tray event handling - ✨ Technical Advantages: * Unified task tracking and debugging capabilities (via tokio-trace feature) * Better error handling and task management * Consistency with Tauri runtime * Reduced async boundaries for better performance - 🧪 Verification: * Compilation successful with 0 errors, 0 warnings * Maintains complete original functionality * Optimized async execution flow * feat: complete tokio fs migration and replace tokio::spawn with AsyncHandler 🚀 Major achievements: - Migrate 8 core modules from std::fs to tokio::fs - Create 6 Send-safe wrapper functions using spawn_blocking pattern - Replace all tokio::spawn calls with AsyncHandler::spawn for unified async task management - Solve all 19 Send trait compilation errors through innovative spawn_blocking architecture 🔧 Core changes: - config/profiles.rs: Add profiles_*_safe functions to handle Send trait constraints - cmd/profile.rs: Update all Tauri commands to use Send-safe operations - config/prfitem.rs: Replace append_item calls with profiles_append_item_safe - utils/help.rs: Convert YAML operations to async (read_yaml, save_yaml) - Multiple modules: Replace tokio::task::spawn_blocking with AsyncHandler::spawn_blocking ✅ Technical innovations: - spawn_blocking wrapper pattern resolves parking_lot RwLock Send trait conflicts - Maintain parking_lot performance while achieving Tauri async command compatibility - Preserve backwards compatibility with gradual migration strategy 🎯 Results: - Zero compilation errors - Zero warnings - All async file operations working correctly - Complete Send trait compliance for Tauri commands * feat: refactor app handle and command functions to use async/await for improved performance * feat: update async handling in profiles and logging functions for improved error handling and performance * fix: update TRACE_MINI_SIZE constant to improve task logging threshold * fix(windows): convert service management functions to async for improved performance * fix: convert service management functions to async for improved responsiveness * fix(ubuntu): convert install and reinstall service functions to async for improved performance * fix(linux): convert uninstall_service function to async for improved performance * fix: convert uninstall_service call to async for improved performance * fix: convert file and directory creation calls to async for improved performance * fix: convert hotkey functions to async for improved responsiveness * chore: update UPDATELOG.md for v2.4.1 with major improvements and performance optimizations
2025-08-26 01:49:51 +08:00
pub async fn create_request(
&self,
proxy_type: ProxyType,
timeout_secs: Option<u64>,
user_agent: Option<String>,
accept_invalid_certs: bool,
) -> Result<HttpClient> {
let proxy_uri = match proxy_type {
ProxyType::None => None,
ProxyType::Localhost => {
refactor(async): migrate from sync-blocking async execution to true async with unified AsyncHandler::spawn (#4502) * feat: replace all tokio::spawn with unified AsyncHandler::spawn - 🚀 Core Improvements: * Replace all tokio::spawn calls with AsyncHandler::spawn for unified Tauri async task management * Prioritize converting sync functions to async functions to reduce spawn usage * Use .await directly in async contexts instead of spawn - 🔧 Major Changes: * core/hotkey.rs: Use AsyncHandler::spawn for hotkey callback functions * module/lightweight.rs: Async lightweight mode switching * feat/window.rs: Convert window operation functions to async, use .await internally * feat/proxy.rs, feat/clash.rs: Async proxy and mode switching functions * lib.rs: Window focus handling with AsyncHandler::spawn * core/tray/mod.rs: Complete async tray event handling - ✨ Technical Advantages: * Unified task tracking and debugging capabilities (via tokio-trace feature) * Better error handling and task management * Consistency with Tauri runtime * Reduced async boundaries for better performance - 🧪 Verification: * Compilation successful with 0 errors, 0 warnings * Maintains complete original functionality * Optimized async execution flow * feat: complete tokio fs migration and replace tokio::spawn with AsyncHandler 🚀 Major achievements: - Migrate 8 core modules from std::fs to tokio::fs - Create 6 Send-safe wrapper functions using spawn_blocking pattern - Replace all tokio::spawn calls with AsyncHandler::spawn for unified async task management - Solve all 19 Send trait compilation errors through innovative spawn_blocking architecture 🔧 Core changes: - config/profiles.rs: Add profiles_*_safe functions to handle Send trait constraints - cmd/profile.rs: Update all Tauri commands to use Send-safe operations - config/prfitem.rs: Replace append_item calls with profiles_append_item_safe - utils/help.rs: Convert YAML operations to async (read_yaml, save_yaml) - Multiple modules: Replace tokio::task::spawn_blocking with AsyncHandler::spawn_blocking ✅ Technical innovations: - spawn_blocking wrapper pattern resolves parking_lot RwLock Send trait conflicts - Maintain parking_lot performance while achieving Tauri async command compatibility - Preserve backwards compatibility with gradual migration strategy 🎯 Results: - Zero compilation errors - Zero warnings - All async file operations working correctly - Complete Send trait compliance for Tauri commands * feat: refactor app handle and command functions to use async/await for improved performance * feat: update async handling in profiles and logging functions for improved error handling and performance * fix: update TRACE_MINI_SIZE constant to improve task logging threshold * fix(windows): convert service management functions to async for improved performance * fix: convert service management functions to async for improved responsiveness * fix(ubuntu): convert install and reinstall service functions to async for improved performance * fix(linux): convert uninstall_service function to async for improved performance * fix: convert uninstall_service call to async for improved performance * fix: convert file and directory creation calls to async for improved performance * fix: convert hotkey functions to async for improved responsiveness * chore: update UPDATELOG.md for v2.4.1 with major improvements and performance optimizations
2025-08-26 01:49:51 +08:00
let port = {
let verge_port = Config::verge().await.latest_arc().verge_mixed_port;
refactor(async): migrate from sync-blocking async execution to true async with unified AsyncHandler::spawn (#4502) * feat: replace all tokio::spawn with unified AsyncHandler::spawn - 🚀 Core Improvements: * Replace all tokio::spawn calls with AsyncHandler::spawn for unified Tauri async task management * Prioritize converting sync functions to async functions to reduce spawn usage * Use .await directly in async contexts instead of spawn - 🔧 Major Changes: * core/hotkey.rs: Use AsyncHandler::spawn for hotkey callback functions * module/lightweight.rs: Async lightweight mode switching * feat/window.rs: Convert window operation functions to async, use .await internally * feat/proxy.rs, feat/clash.rs: Async proxy and mode switching functions * lib.rs: Window focus handling with AsyncHandler::spawn * core/tray/mod.rs: Complete async tray event handling - ✨ Technical Advantages: * Unified task tracking and debugging capabilities (via tokio-trace feature) * Better error handling and task management * Consistency with Tauri runtime * Reduced async boundaries for better performance - 🧪 Verification: * Compilation successful with 0 errors, 0 warnings * Maintains complete original functionality * Optimized async execution flow * feat: complete tokio fs migration and replace tokio::spawn with AsyncHandler 🚀 Major achievements: - Migrate 8 core modules from std::fs to tokio::fs - Create 6 Send-safe wrapper functions using spawn_blocking pattern - Replace all tokio::spawn calls with AsyncHandler::spawn for unified async task management - Solve all 19 Send trait compilation errors through innovative spawn_blocking architecture 🔧 Core changes: - config/profiles.rs: Add profiles_*_safe functions to handle Send trait constraints - cmd/profile.rs: Update all Tauri commands to use Send-safe operations - config/prfitem.rs: Replace append_item calls with profiles_append_item_safe - utils/help.rs: Convert YAML operations to async (read_yaml, save_yaml) - Multiple modules: Replace tokio::task::spawn_blocking with AsyncHandler::spawn_blocking ✅ Technical innovations: - spawn_blocking wrapper pattern resolves parking_lot RwLock Send trait conflicts - Maintain parking_lot performance while achieving Tauri async command compatibility - Preserve backwards compatibility with gradual migration strategy 🎯 Results: - Zero compilation errors - Zero warnings - All async file operations working correctly - Complete Send trait compliance for Tauri commands * feat: refactor app handle and command functions to use async/await for improved performance * feat: update async handling in profiles and logging functions for improved error handling and performance * fix: update TRACE_MINI_SIZE constant to improve task logging threshold * fix(windows): convert service management functions to async for improved performance * fix: convert service management functions to async for improved responsiveness * fix(ubuntu): convert install and reinstall service functions to async for improved performance * fix(linux): convert uninstall_service function to async for improved performance * fix: convert uninstall_service call to async for improved performance * fix: convert file and directory creation calls to async for improved performance * fix: convert hotkey functions to async for improved responsiveness * chore: update UPDATELOG.md for v2.4.1 with major improvements and performance optimizations
2025-08-26 01:49:51 +08:00
match verge_port {
Some(port) => port,
None => Config::clash().await.latest_arc().get_mixed_port(),
refactor(async): migrate from sync-blocking async execution to true async with unified AsyncHandler::spawn (#4502) * feat: replace all tokio::spawn with unified AsyncHandler::spawn - 🚀 Core Improvements: * Replace all tokio::spawn calls with AsyncHandler::spawn for unified Tauri async task management * Prioritize converting sync functions to async functions to reduce spawn usage * Use .await directly in async contexts instead of spawn - 🔧 Major Changes: * core/hotkey.rs: Use AsyncHandler::spawn for hotkey callback functions * module/lightweight.rs: Async lightweight mode switching * feat/window.rs: Convert window operation functions to async, use .await internally * feat/proxy.rs, feat/clash.rs: Async proxy and mode switching functions * lib.rs: Window focus handling with AsyncHandler::spawn * core/tray/mod.rs: Complete async tray event handling - ✨ Technical Advantages: * Unified task tracking and debugging capabilities (via tokio-trace feature) * Better error handling and task management * Consistency with Tauri runtime * Reduced async boundaries for better performance - 🧪 Verification: * Compilation successful with 0 errors, 0 warnings * Maintains complete original functionality * Optimized async execution flow * feat: complete tokio fs migration and replace tokio::spawn with AsyncHandler 🚀 Major achievements: - Migrate 8 core modules from std::fs to tokio::fs - Create 6 Send-safe wrapper functions using spawn_blocking pattern - Replace all tokio::spawn calls with AsyncHandler::spawn for unified async task management - Solve all 19 Send trait compilation errors through innovative spawn_blocking architecture 🔧 Core changes: - config/profiles.rs: Add profiles_*_safe functions to handle Send trait constraints - cmd/profile.rs: Update all Tauri commands to use Send-safe operations - config/prfitem.rs: Replace append_item calls with profiles_append_item_safe - utils/help.rs: Convert YAML operations to async (read_yaml, save_yaml) - Multiple modules: Replace tokio::task::spawn_blocking with AsyncHandler::spawn_blocking ✅ Technical innovations: - spawn_blocking wrapper pattern resolves parking_lot RwLock Send trait conflicts - Maintain parking_lot performance while achieving Tauri async command compatibility - Preserve backwards compatibility with gradual migration strategy 🎯 Results: - Zero compilation errors - Zero warnings - All async file operations working correctly - Complete Send trait compliance for Tauri commands * feat: refactor app handle and command functions to use async/await for improved performance * feat: update async handling in profiles and logging functions for improved error handling and performance * fix: update TRACE_MINI_SIZE constant to improve task logging threshold * fix(windows): convert service management functions to async for improved performance * fix: convert service management functions to async for improved responsiveness * fix(ubuntu): convert install and reinstall service functions to async for improved performance * fix(linux): convert uninstall_service function to async for improved performance * fix: convert uninstall_service call to async for improved performance * fix: convert file and directory creation calls to async for improved performance * fix: convert hotkey functions to async for improved responsiveness * chore: update UPDATELOG.md for v2.4.1 with major improvements and performance optimizations
2025-08-26 01:49:51 +08:00
}
};
let proxy_scheme = format!("http://127.0.0.1:{port}");
proxy_scheme.parse::<Uri>().ok()
}
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::<Uri>().ok()
} else {
None
}
}
};
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)?;
Ok(client)
}
2025-05-03 23:17:51 +08:00
pub async fn get_with_interrupt(
&self,
url: &str,
proxy_type: ProxyType,
timeout_secs: Option<u64>,
user_agent: Option<String>,
accept_invalid_certs: bool,
) -> Result<HttpResponse> {
if self.should_reset_clients().await {
self.reset_clients().await;
}
let parsed = Url::parse(url)?;
let mut extra_headers = HeaderMap::new();
if !parsed.username().is_empty()
&& let Some(pass) = parsed.password()
{
let auth_str = format!("{}:{}", parsed.username(), pass);
let encoded = general_purpose::STANDARD.encode(auth_str);
extra_headers.insert(
"Authorization",
HeaderValue::from_str(&format!("Basic {}", encoded))?,
);
}
let clean_url = {
let mut no_auth = parsed.clone();
no_auth.set_username("").ok();
no_auth.set_password(None).ok();
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);
for (k, v) in extra_headers.iter() {
req = req.header(k, v);
}
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))
.await;
return Err(anyhow::anyhow!(
"Request interrupted after {}s",
timeout_duration.as_secs()
));
}
2025-05-03 23:17:51 +08:00
};
Ok(response)
}
}