Compare commits

...

2 Commits

Author SHA1 Message Date
Tunglies
b3aa15f473 chore: bump netx-rs to v3.0.0-pre.9 2025-12-21 00:47:13 +08:00
Tunglies
06bdef184c feat: replace warp with ntex for server functionality 2025-12-21 00:39:18 +08:00
8 changed files with 412 additions and 174 deletions

400
Cargo.lock generated
View File

@@ -1216,6 +1216,7 @@ dependencies = [
"log",
"nanoid",
"network-interface",
"ntex",
"once_cell",
"open",
"parking_lot",
@@ -1253,7 +1254,6 @@ dependencies = [
"tauri-plugin-window-state",
"tokio",
"tokio-stream",
"warp",
"winapi",
"winreg 0.55.0",
"zip 7.0.0",
@@ -1582,6 +1582,17 @@ dependencies = [
"libc",
]
[[package]]
name = "core_affinity"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a034b3a7b624016c6e13f5df875747cc25f884156aad2abd12b6c46797971342"
dependencies = [
"libc",
"num_cpus",
"winapi",
]
[[package]]
name = "cow-utils"
version = "0.1.3"
@@ -1784,6 +1795,17 @@ dependencies = [
"cipher",
]
[[package]]
name = "ctrlc"
version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790"
dependencies = [
"dispatch2",
"nix 0.30.1",
"windows-sys 0.61.2",
]
[[package]]
name = "dark-light"
version = "2.0.0"
@@ -2309,6 +2331,25 @@ dependencies = [
"syn 2.0.111",
]
[[package]]
name = "env_filter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"
dependencies = [
"log",
]
[[package]]
name = "env_logger"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"env_filter",
"log",
]
[[package]]
name = "equator"
version = "0.4.2"
@@ -2735,6 +2776,12 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-timer"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
[[package]]
name = "futures-util"
version = "0.3.31"
@@ -3255,30 +3302,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "headers"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb"
dependencies = [
"base64 0.22.1",
"bytes",
"headers-core",
"http 1.4.0",
"httpdate",
"mime",
"sha1",
]
[[package]]
name = "headers-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
dependencies = [
"http 1.4.0",
]
[[package]]
name = "heck"
version = "0.4.1"
@@ -4387,16 +4410,6 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@@ -4490,6 +4503,12 @@ dependencies = [
"rand 0.8.5",
]
[[package]]
name = "nanorand"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e3d189da485332e96ba8a5ef646a311871abd7915bf06ac848a9117f19cf6e4"
[[package]]
name = "native-tls"
version = "0.2.14"
@@ -4674,6 +4693,255 @@ dependencies = [
"winapi",
]
[[package]]
name = "ntex"
version = "3.0.0-pre.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e9f2445ee6caa58f028cc5164e3bea0002dc8ddf11c56db7d75e628411eb618"
dependencies = [
"base64 0.22.1",
"bitflags 2.10.0",
"encoding_rs",
"env_logger",
"httparse",
"httpdate",
"log",
"mime",
"nanorand",
"ntex-bytes",
"ntex-codec",
"ntex-h2",
"ntex-http",
"ntex-io",
"ntex-macros",
"ntex-net",
"ntex-router",
"ntex-rt",
"ntex-server",
"ntex-service",
"ntex-tls",
"ntex-util",
"percent-encoding",
"pin-project-lite",
"regex",
"serde",
"serde_json",
"serde_urlencoded",
"sha1",
"thiserror 2.0.17",
"uuid",
"variadics_please",
]
[[package]]
name = "ntex-bytes"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb9eb0e05f22d68cae27049eb6ec055cae3e7e5e459ece774b11d03369a6ae3"
dependencies = [
"bytes",
"serde",
]
[[package]]
name = "ntex-codec"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efe7504ae0a8b2b5dc11b9519953725a7b3fa3edc510ae4bfc80e6771ed06e99"
dependencies = [
"ntex-bytes",
]
[[package]]
name = "ntex-h2"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c6bc7791879687e482382bc8f24a7b69c9b224330ccdb4c3122678b8b9c78ba"
dependencies = [
"bitflags 2.10.0",
"foldhash 0.2.0",
"log",
"nanorand",
"ntex-bytes",
"ntex-codec",
"ntex-http",
"ntex-io",
"ntex-net",
"ntex-service",
"ntex-util",
"pin-project-lite",
"thiserror 2.0.17",
]
[[package]]
name = "ntex-http"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb1f0643064d8ab21b70e0c9660b513ad3253744700c874668e6d7693ab3a39"
dependencies = [
"foldhash 0.2.0",
"futures-core",
"http 1.4.0",
"itoa",
"log",
"ntex-bytes",
"serde",
"thiserror 2.0.17",
]
[[package]]
name = "ntex-io"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8924c3dd123a9ad22e91295e3e3cdc16d1e612cf684b417801f6f99ec4fea2ef"
dependencies = [
"bitflags 2.10.0",
"log",
"ntex-bytes",
"ntex-codec",
"ntex-service",
"ntex-util",
"pin-project-lite",
]
[[package]]
name = "ntex-macros"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d4e3df338a8b6e8305a1a85e9fc559db1b7020e5d5a9f30ab991d2af14b1f7a"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "ntex-net"
version = "3.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd094b5839d42f0217bfe57a820a1971eb74d53d4d56b3e2f47fc45657c660d"
dependencies = [
"bitflags 2.10.0",
"cfg-if",
"libc",
"log",
"ntex-bytes",
"ntex-http",
"ntex-io",
"ntex-rt",
"ntex-service",
"ntex-tokio",
"ntex-util",
"thiserror 2.0.17",
]
[[package]]
name = "ntex-router"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "203fc4ba8fd22db656cde62cfbfe705f776aa652ce8d7ce5c6093b7c37185f70"
dependencies = [
"http 1.4.0",
"log",
"ntex-bytes",
"regex",
"serde",
]
[[package]]
name = "ntex-rt"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7525cce87fef7f3951d3195288e565d2233313e814690cb91cd6840ca10b4dc3"
dependencies = [
"async-channel 2.5.0",
"futures-timer",
"log",
"oneshot",
"tokio",
]
[[package]]
name = "ntex-server"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b95fdda46b53fd8168cd3b720819550fc7385f486542c35fcd1e4713ca2e7299"
dependencies = [
"async-channel 2.5.0",
"atomic-waker",
"core_affinity",
"ctrlc",
"log",
"ntex-io",
"ntex-net",
"ntex-rt",
"ntex-service",
"ntex-util",
"oneshot",
"polling 3.11.0",
"signal-hook 0.3.18",
"socket2 0.6.1",
"uuid",
]
[[package]]
name = "ntex-service"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "179cfd5ab6b56445ca1b4146ee1d7a87e7f7bb1b7d4973f02327619c6aa90bcc"
dependencies = [
"foldhash 0.2.0",
"log",
"slab",
]
[[package]]
name = "ntex-tls"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9621cd8aea4c0d1bc29793c1e172fdb95c23a75c654490cba036c1ad6d9c3c38"
dependencies = [
"log",
"ntex-bytes",
"ntex-io",
"ntex-net",
"ntex-service",
"ntex-util",
]
[[package]]
name = "ntex-tokio"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35a8a029f40a98a5f9ba95eb0e0b215c17f111897a3711b692d2ac3ed49a23d1"
dependencies = [
"log",
"ntex-bytes",
"ntex-io",
"ntex-service",
"ntex-util",
"tokio",
]
[[package]]
name = "ntex-util"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "092a46f60e64950809011a95d5ec6f634394b80d984bc44d8057b89a6a78c1f1"
dependencies = [
"bitflags 2.10.0",
"foldhash 0.2.0",
"futures-core",
"futures-timer",
"log",
"ntex-bytes",
"ntex-rt",
"ntex-service",
"pin-project-lite",
"slab",
"thiserror 2.0.17",
]
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
@@ -4729,6 +4997,16 @@ dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
dependencies = [
"hermit-abi 0.5.2",
"libc",
]
[[package]]
name = "num_enum"
version = "0.7.5"
@@ -5040,6 +5318,12 @@ dependencies = [
"parking_lot_core",
]
[[package]]
name = "oneshot"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce411919553d3f9fa53a0880544cda985a112117a0444d5ff1e870a893d6ea"
[[package]]
name = "oorandom"
version = "11.1.5"
@@ -8914,12 +9198,6 @@ dependencies = [
"unic-common",
]
[[package]]
name = "unicase"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]]
name = "unicode-ident"
version = "1.0.22"
@@ -9038,6 +9316,17 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0"
[[package]]
name = "variadics_please"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
@@ -9110,35 +9399,6 @@ dependencies = [
"try-lock",
]
[[package]]
name = "warp"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d06d9202adc1f15d709c4f4a2069be5428aa912cc025d6f268ac441ab066b0"
dependencies = [
"bytes",
"futures-util",
"headers",
"http 1.4.0",
"http-body 1.0.1",
"http-body-util",
"hyper 1.8.1",
"hyper-util",
"log",
"mime",
"mime_guess",
"percent-encoding",
"pin-project",
"scoped-tls",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-util",
"tower-service",
"tracing",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"

View File

@@ -19,4 +19,6 @@
<details>
<summary><strong> 🚀 优化改进 </strong></summary>
- 替换内置服务实现方案
</details>

View File

@@ -54,7 +54,7 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
serde_yaml_ng = { workspace = true }
smartstring = { workspace = true, features = ["serde"] }
warp = { version = "0.4.2", features = ["server"] }
ntex = { version = "3.0.0-pre.9", features = ["tokio"] }
open = "5.3.3"
dunce = "1.0.5"
nanoid = "0.4"

View File

@@ -3,7 +3,7 @@ use crate::{
core::{CoreManager, handle, tray},
feat::clean_async,
process::AsyncHandler,
utils::{self, resolve::reset_resolve_done},
utils::resolve::reset_resolve_done,
};
use clash_verge_logging::{Type, logging, logging_error};
use serde_yaml_ng::{Mapping, Value};
@@ -29,7 +29,6 @@ pub async fn restart_app() {
// 设置退出标志
handle::Handle::global().set_is_exiting();
utils::server::shutdown_embedded_server();
Config::apply_all_and_save_file().await;
logging!(info, Type::System, "开始异步清理资源");

View File

@@ -1,7 +1,6 @@
use crate::config::Config;
use crate::core::{CoreManager, handle, sysopt};
use crate::module::lightweight;
use crate::utils;
use crate::utils::window_manager::WindowManager;
use clash_verge_logging::{Type, logging};
use tokio::time::{Duration, timeout};
@@ -23,7 +22,6 @@ pub async fn quit() {
// 设置退出标志
handle::Handle::global().set_is_exiting();
utils::server::shutdown_embedded_server();
Config::apply_all_and_save_file().await;
logging!(info, Type::System, "开始异步清理资源");

View File

@@ -254,6 +254,7 @@ pub fn run() {
resolve::resolve_setup_handle();
resolve::resolve_setup_async();
resolve::resolve_setup_sync();
resolve::resolve_embed_server();
resolve::init_signal();
resolve::resolve_done();

View File

@@ -2,6 +2,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use anyhow::Result;
use flexi_logger::LoggerHandle;
use ntex::rt::System;
use crate::{
config::Config,
@@ -52,7 +53,6 @@ pub fn resolve_setup_handle() {
pub fn resolve_setup_sync() {
AsyncHandler::spawn(|| async {
AsyncHandler::spawn_blocking(init_scheme);
AsyncHandler::spawn_blocking(init_embed_server);
});
}
@@ -88,6 +88,16 @@ pub fn resolve_setup_async() {
});
}
pub fn resolve_embed_server() {
AsyncHandler::spawn_blocking(|| {
logging!(info, Type::Setup, "Starting embedded singleton server runtime");
let system = System::new("clash-verge-embed");
system.block_on(async {
init_embed_server().await;
});
});
}
pub async fn resolve_reset_async() -> Result<(), anyhow::Error> {
sysopt::Sysopt::global().reset_sysproxy().await?;
CoreManager::global().stop_core().await?;
@@ -114,8 +124,8 @@ pub async fn resolve_scheme(param: &str) -> Result<()> {
Ok(())
}
pub(super) fn init_embed_server() {
server::embed_server();
async fn init_embed_server() {
logging_error!(Type::Setup, server::embed_server().await);
}
pub(super) async fn init_resources() {

View File

@@ -1,30 +1,69 @@
use super::resolve;
use std::time::Duration;
use anyhow::{Result, bail};
use clash_verge_logging::{Type, logging};
use ntex::web;
use port_scanner::local_port_available;
use reqwest::ClientBuilder;
use serde::{Deserialize, Serialize};
use crate::{
config::{Config, DEFAULT_PAC, IVerge},
module::lightweight,
process::AsyncHandler,
utils::window_manager::WindowManager,
utils::{resolve, window_manager::WindowManager},
};
use anyhow::{Result, bail};
use clash_verge_logging::{Type, logging, logging_error};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use port_scanner::local_port_available;
use reqwest::ClientBuilder;
use smartstring::alias::String;
use std::time::Duration;
use tokio::sync::oneshot;
use warp::Filter as _;
#[derive(serde::Deserialize, Debug)]
#[derive(Deserialize, Serialize)]
struct QueryParam {
param: String,
}
// 关闭 embedded server 的信号发送端
static SHUTDOWN_SENDER: OnceCell<Mutex<Option<oneshot::Sender<()>>>> = OnceCell::new();
#[web::get("hello")]
async fn current_hello() -> impl web::Responder {
web::HttpResponse::Ok().body("hello")
}
#[web::get("commands/visible")]
async fn current_visible() -> impl web::Responder {
if !lightweight::exit_lightweight_mode().await {
WindowManager::show_main_window().await;
}
web::HttpResponse::Ok().body("ok")
}
#[web::get("commands/pac")]
async fn current_pac_content() -> impl web::Responder {
let verge = Config::verge().await;
let clash = Config::clash().await;
let pac_content = verge
.data_arc()
.pac_file_content
.clone()
.unwrap_or_else(|| DEFAULT_PAC.into());
let pac_port = verge
.data_arc()
.verge_mixed_port
.unwrap_or_else(|| clash.data_arc().get_mixed_port());
let processed_content = pac_content.replace("%mixed-port%", &format!("{pac_port}"));
web::HttpResponse::Ok()
.content_type("application/x-ns-proxy-autoconfig")
.body(processed_content)
}
#[web::get("commands/scheme")]
async fn current_scheme(param: web::types::Query<QueryParam>) -> impl web::Responder {
match resolve::resolve_scheme(&param.param).await {
Ok(_) => web::HttpResponse::Ok().body("ok"),
Err(e) => {
logging!(error, Type::Setup, "failed to resolve scheme: {}", e);
web::HttpResponse::InternalServerError().body("failed to resolve scheme")
}
}
}
/// check whether there is already exists
pub async fn check_singleton() -> Result<()> {
let port = IVerge::get_singleton_port();
if !local_port_available(port) {
@@ -55,84 +94,13 @@ pub async fn check_singleton() -> Result<()> {
Ok(())
}
/// The embed server only be used to implement singleton process
/// maybe it can be used as pac server later
pub fn embed_server() {
let (shutdown_tx, shutdown_rx) = oneshot::channel();
#[allow(clippy::expect_used)]
SHUTDOWN_SENDER
.set(Mutex::new(Some(shutdown_tx)))
.expect("failed to set shutdown signal for embedded server");
pub async fn embed_server() -> std::io::Result<()> {
let port = IVerge::get_singleton_port();
let visible = warp::path!("commands" / "visible").and_then(|| async {
logging!(info, Type::Window, "检测到从单例模式恢复应用窗口");
if !lightweight::exit_lightweight_mode().await {
WindowManager::show_main_window().await;
} else {
logging!(error, Type::Window, "轻量模式退出失败,无法恢复应用窗口");
};
Ok::<_, warp::Rejection>(warp::reply::with_status::<std::string::String>(
"ok".to_string(),
warp::http::StatusCode::OK,
))
});
let pac = warp::path!("commands" / "pac").and_then(|| async move {
let verge_config = Config::verge().await;
let clash_config = Config::clash().await;
let pac_content = verge_config
.data_arc()
.pac_file_content
.clone()
.unwrap_or_else(|| DEFAULT_PAC.into());
let pac_port = verge_config
.data_arc()
.verge_mixed_port
.unwrap_or_else(|| clash_config.data_arc().get_mixed_port());
let processed_content = pac_content.replace("%mixed-port%", &format!("{pac_port}"));
Ok::<_, warp::Rejection>(
warp::http::Response::builder()
.header("Content-Type", "application/x-ns-proxy-autoconfig")
.body(processed_content)
.unwrap_or_default(),
)
});
// Use map instead of and_then to avoid Send issues
let scheme = warp::path!("commands" / "scheme")
.and(warp::query::<QueryParam>())
.and_then(|query: QueryParam| async move {
AsyncHandler::spawn(|| async move {
logging_error!(Type::Setup, resolve::resolve_scheme(&query.param).await);
});
Ok::<_, warp::Rejection>(warp::reply::with_status::<std::string::String>(
"ok".to_string(),
warp::http::StatusCode::OK,
))
});
let commands = visible.or(scheme).or(pac);
AsyncHandler::spawn(move || async move {
warp::serve(commands)
.bind(([127, 0, 0, 1], port))
.await
.graceful(async {
shutdown_rx.await.ok();
})
.run()
.await;
});
}
pub fn shutdown_embedded_server() {
logging!(info, Type::Window, "shutting down embedded server");
if let Some(sender) = SHUTDOWN_SENDER.get()
&& let Some(sender) = sender.lock().take()
{
sender.send(()).ok();
}
web::HttpServer::new(|| {
web::App::new().service((current_hello, current_visible, current_pac_content, current_scheme))
})
.workers(1)
.bind(("127.0.0.1", port))?
.run()
.await
}