mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 07:03:45 +08:00
platform: windows support
This commit is contained in:
parent
acf139a400
commit
670333fcc2
@ -4,18 +4,18 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = "0.1.88"
|
async-trait = "0.1.88"
|
||||||
|
futures = "0.3.31"
|
||||||
http-body-util = "0.1.3"
|
http-body-util = "0.1.3"
|
||||||
hyper = { version = "1.6.0", features = ["http1", "client"] }
|
hyper = { version = "1.6.0", features = ["http1", "client"] }
|
||||||
hyper-util = "0.1.11"
|
hyper-util = "0.1.11"
|
||||||
hyperlocal = "0.9.1"
|
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
tokio = { version = "1.44.1", features = [
|
time = "0.3.41"
|
||||||
"rt",
|
tokio = { version = "1.44.1", features = ["rt", "macros", "rt-multi-thread", "io-std", "net", "io-util", "time"] }
|
||||||
"macros",
|
tokio-util = { version = "0.7.14", features = ["codec"] }
|
||||||
"rt-multi-thread",
|
|
||||||
"io-std",
|
|
||||||
] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
hyperlocal = "0.9.1"
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
$pipeName = "\\.\pipe\mihomo"
|
||||||
|
$pipe = new-object System.IO.Pipes.NamedPipeClientStream(".", "mihomo", [System.IO.Pipes.PipeDirection]::InOut)
|
||||||
|
$pipe.Connect(1000) # 尝试连接 1 秒
|
||||||
|
if ($pipe.IsConnected) {
|
||||||
|
Write-Host "成功连接到管道"
|
||||||
|
# 示例写入或读取可以加上如下内容
|
||||||
|
# $writer = new-object System.IO.StreamWriter($pipe)
|
||||||
|
# $writer.WriteLine("hello pipe")
|
||||||
|
# $writer.Flush()
|
||||||
|
$pipe.Close()
|
||||||
|
} else {
|
||||||
|
Write-Host "连接失败"
|
||||||
|
}
|
@ -46,3 +46,4 @@ pub use model::E;
|
|||||||
pub use model::MihomoData;
|
pub use model::MihomoData;
|
||||||
pub use model::MihomoManager;
|
pub use model::MihomoManager;
|
||||||
pub mod sock;
|
pub mod sock;
|
||||||
|
pub mod platform;
|
@ -1,8 +1,5 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use http_body_util::Full;
|
use hyper::Method;
|
||||||
use hyper::{Method, body::Bytes};
|
|
||||||
use hyper_util::client::legacy::Client;
|
|
||||||
use hyperlocal::{UnixConnector, Uri};
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{error::Error, sync::Arc};
|
use std::{error::Error, sync::Arc};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@ -29,13 +26,14 @@ pub trait MihomoClient: Sized {
|
|||||||
async fn set_data_providers_proxies(&self, data: Value);
|
async fn set_data_providers_proxies(&self, data: Value);
|
||||||
async fn get_data_proxies(&self) -> Value;
|
async fn get_data_proxies(&self) -> Value;
|
||||||
async fn get_data_providers_proxies(&self) -> Value;
|
async fn get_data_providers_proxies(&self) -> Value;
|
||||||
async fn generate_unix_path(&self, path: &str) -> Uri;
|
// async fn generate_unix_path(&self, path: &str) -> Uri;
|
||||||
async fn send_request(
|
async fn send_request(
|
||||||
&self,
|
&self,
|
||||||
path: &str,
|
path: &str,
|
||||||
method: Method,
|
method: Method,
|
||||||
body: Option<Value>,
|
body: Option<Value>,
|
||||||
) -> Result<Value, E>;
|
) -> Result<Value, E>;
|
||||||
|
async fn get_version(&self) -> Result<Value, E>;
|
||||||
async fn is_mihomo_running(&self) -> Result<(), E>;
|
async fn is_mihomo_running(&self) -> Result<(), E>;
|
||||||
async fn put_configs_force(&self, clash_config_path: &str) -> Result<(), E>;
|
async fn put_configs_force(&self, clash_config_path: &str) -> Result<(), E>;
|
||||||
async fn patch_configs(&self, config: Value) -> Result<(), E>;
|
async fn patch_configs(&self, config: Value) -> Result<(), E>;
|
||||||
@ -51,8 +49,9 @@ pub trait MihomoClient: Sized {
|
|||||||
) -> Result<Value, E>;
|
) -> Result<Value, E>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use crate::platform::Client;
|
||||||
pub struct MihomoManager {
|
pub struct MihomoManager {
|
||||||
pub(super) socket_path: String,
|
pub(super) socket_path: String,
|
||||||
pub(super) client: Arc<Mutex<Client<UnixConnector, Full<Bytes>>>>,
|
pub(super) client: Arc<Mutex<Client>>,
|
||||||
pub(super) data: Arc<Mutex<MihomoData>>,
|
pub(super) data: Arc<Mutex<MihomoData>>,
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
#[cfg(unix)] pub mod unix;
|
||||||
|
#[cfg(unix)] pub use unix::UnixClient as Client;
|
||||||
|
#[cfg(windows)] pub mod windows;
|
||||||
|
#[cfg(windows)] pub use windows::WindowsClient as Client;
|
60
src-tauri/src_crates/crate_mihomo_api/src/platform/unix.rs
Normal file
60
src-tauri/src_crates/crate_mihomo_api/src/platform/unix.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// #[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
|
use crate::model::E;
|
||||||
|
use http_body_util::{BodyExt, Full};
|
||||||
|
use hyper::{
|
||||||
|
Method, Request,
|
||||||
|
body::Bytes,
|
||||||
|
header::{HeaderName, HeaderValue},
|
||||||
|
};
|
||||||
|
use hyper_util::client::legacy::Client;
|
||||||
|
use hyperlocal::{UnixClientExt, Uri};
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
pub struct UnixClient {
|
||||||
|
client: Arc<Mutex<Client<hyperlocal::UnixConnector, Full<Bytes>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnixClient {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let client: Client<_, Full<Bytes>> = Client::unix();
|
||||||
|
Self {
|
||||||
|
client: Arc::new(Mutex::new(client)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn generate_unix_path(&self, socket_path: &str, path: &str) -> Uri {
|
||||||
|
Uri::new(socket_path, path).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_request(
|
||||||
|
&self,
|
||||||
|
socket_path: &str,
|
||||||
|
path: &str,
|
||||||
|
method: Method,
|
||||||
|
body: Option<Value>,
|
||||||
|
) -> Result<Value, E> {
|
||||||
|
let uri = self.generate_unix_path(socket_path, path).await;
|
||||||
|
|
||||||
|
let mut request_builder = Request::builder().method(method).uri(uri);
|
||||||
|
|
||||||
|
let body_bytes = if let Some(body) = body {
|
||||||
|
request_builder = request_builder.header(
|
||||||
|
HeaderName::from_static("Content-Type"),
|
||||||
|
HeaderValue::from_static("application/json"),
|
||||||
|
);
|
||||||
|
Bytes::from(serde_json::to_vec(&body)?)
|
||||||
|
} else {
|
||||||
|
Bytes::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let request = request_builder.body(Full::new(body_bytes))?;
|
||||||
|
|
||||||
|
let response = self.client.lock().await.request(request).await?;
|
||||||
|
let body_bytes = response.into_body().collect().await?.to_bytes();
|
||||||
|
let json_value = serde_json::from_slice(&body_bytes)?;
|
||||||
|
|
||||||
|
Ok(json_value)
|
||||||
|
}
|
||||||
|
}
|
129
src-tauri/src_crates/crate_mihomo_api/src/platform/windows.rs
Normal file
129
src-tauri/src_crates/crate_mihomo_api/src/platform/windows.rs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
use crate::{model::E, sock};
|
||||||
|
use hyper::Method;
|
||||||
|
use serde_json::Value;
|
||||||
|
use tokio_util::codec::{Framed, LinesCodec};
|
||||||
|
use std::{sync::Arc, time::Duration};
|
||||||
|
use tokio::{
|
||||||
|
time::timeout,
|
||||||
|
sync::Mutex,
|
||||||
|
};
|
||||||
|
use futures::{SinkExt, StreamExt};
|
||||||
|
use tokio::net::windows::named_pipe::ClientOptions;
|
||||||
|
|
||||||
|
pub struct WindowsClient {
|
||||||
|
lock: Arc<Mutex<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowsClient {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
lock: Arc::new(Mutex::new(())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_request(
|
||||||
|
&self,
|
||||||
|
socket_path: String,
|
||||||
|
path: &str,
|
||||||
|
method: Method,
|
||||||
|
body: Option<Value>,
|
||||||
|
) -> Result<Value, E> {
|
||||||
|
// Acquire lock before opening pipe
|
||||||
|
// let _guard = self.lock.lock().await;
|
||||||
|
|
||||||
|
// Attempt to open the pipe with retry logic
|
||||||
|
let mut retries = 0;
|
||||||
|
let pipe = loop {
|
||||||
|
match ClientOptions::new().open(socket_path.clone()) {
|
||||||
|
Ok(pipe) => break pipe,
|
||||||
|
Err(e) if e.raw_os_error() == Some(231) && retries < 5 => {
|
||||||
|
retries += 1;
|
||||||
|
let delay = Duration::from_millis(200 * retries);
|
||||||
|
tokio::time::sleep(delay).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use a scope to ensure the pipe is dropped when done
|
||||||
|
let result = async {
|
||||||
|
let mut framed = Framed::new(pipe, LinesCodec::new());
|
||||||
|
|
||||||
|
// Build request
|
||||||
|
let mut request = format!(
|
||||||
|
"{} {} HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/json\r\n",
|
||||||
|
method.as_str(),
|
||||||
|
path
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(ref json_body) = body {
|
||||||
|
let body_str = json_body.to_string();
|
||||||
|
request += &format!("Content-Length: {}\r\n\r\n{}", body_str.len(), body_str);
|
||||||
|
} else {
|
||||||
|
request += "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
framed.send(request).await?;
|
||||||
|
|
||||||
|
// Parse headers
|
||||||
|
let mut headers_done = false;
|
||||||
|
let mut is_chunked = false;
|
||||||
|
|
||||||
|
while let Ok(Some(Ok(line))) = timeout(Duration::from_secs(5), framed.next()).await {
|
||||||
|
if line.is_empty() {
|
||||||
|
headers_done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if line.starts_with("HTTP/1.1 4") || line.starts_with("HTTP/1.1 5") {
|
||||||
|
return Err(format!("Server error: {}", line).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if line.eq_ignore_ascii_case("Transfer-Encoding: chunked") {
|
||||||
|
is_chunked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !headers_done {
|
||||||
|
return Err("Malformed response: no headers end".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut response_body = String::new();
|
||||||
|
|
||||||
|
if is_chunked {
|
||||||
|
// Handle chunked encoding
|
||||||
|
loop {
|
||||||
|
// Read chunk size line
|
||||||
|
let chunk_size_line = match timeout(Duration::from_secs(5), framed.next()).await {
|
||||||
|
Ok(Some(Ok(line))) => line,
|
||||||
|
_ => break,
|
||||||
|
};
|
||||||
|
|
||||||
|
let chunk_size = match usize::from_str_radix(chunk_size_line.trim(), 16) {
|
||||||
|
Ok(0) => break, // End of chunks
|
||||||
|
Ok(_) => (), // We don't actually need the size with LinesCodec
|
||||||
|
Err(_) => return Err("Invalid chunk size".into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read chunk data line
|
||||||
|
if let Ok(Some(Ok(data_line))) = timeout(Duration::from_secs(5), framed.next()).await {
|
||||||
|
response_body.push_str(&data_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip trailing CRLF (empty line)
|
||||||
|
let _ = framed.next().await;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle normal content
|
||||||
|
while let Ok(Some(Ok(line))) = timeout(Duration::from_secs(5), framed.next()).await {
|
||||||
|
response_body.push_str(&line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_json::from_str(&response_body).map_err(|e| e.into())
|
||||||
|
}.await;
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,10 @@
|
|||||||
use crate::model::E;
|
use crate::model::E;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use http_body_util::{BodyExt, Full};
|
use hyper::Method;
|
||||||
use hyper::{
|
|
||||||
Method, Request,
|
|
||||||
body::Bytes,
|
|
||||||
header::{HeaderName, HeaderValue},
|
|
||||||
};
|
|
||||||
use hyper_util::client::legacy::Client;
|
|
||||||
use hyperlocal::{UnixClientExt, Uri};
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
use crate::platform::Client;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
MihomoData,
|
MihomoData,
|
||||||
@ -19,7 +13,7 @@ use crate::{
|
|||||||
|
|
||||||
impl MihomoManager {
|
impl MihomoManager {
|
||||||
pub fn new(socket_path: String) -> Self {
|
pub fn new(socket_path: String) -> Self {
|
||||||
let client: Client<_, Full<Bytes>> = Client::unix();
|
let client = Client::new();
|
||||||
Self {
|
Self {
|
||||||
socket_path,
|
socket_path,
|
||||||
client: Arc::new(Mutex::new(client)),
|
client: Arc::new(Mutex::new(client)),
|
||||||
@ -46,40 +40,23 @@ impl MihomoClient for MihomoManager {
|
|||||||
self.data.lock().await.providers_proxies.clone()
|
self.data.lock().await.providers_proxies.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn generate_unix_path(&self, path: &str) -> Uri {
|
|
||||||
Uri::new(self.socket_path.clone(), path).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_request(
|
async fn send_request(
|
||||||
&self,
|
&self,
|
||||||
path: &str,
|
path: &str,
|
||||||
method: Method,
|
method: Method,
|
||||||
body: Option<Value>,
|
body: Option<Value>,
|
||||||
) -> Result<Value, E> {
|
) -> Result<Value, E> {
|
||||||
let uri = self.generate_unix_path(path).await;
|
let client = self.client.lock().await;
|
||||||
|
client.send_request(self.socket_path.clone(), path, method, body).await
|
||||||
let mut request_builder = Request::builder().method(method).uri(uri);
|
|
||||||
|
|
||||||
let body_bytes = if let Some(body) = body {
|
|
||||||
request_builder = request_builder.header(
|
|
||||||
HeaderName::from_static("Content-Type"),
|
|
||||||
HeaderValue::from_static("application/json"),
|
|
||||||
);
|
|
||||||
Bytes::from(serde_json::to_vec(&body)?)
|
|
||||||
} else {
|
|
||||||
Bytes::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
let request = request_builder.body(Full::new(body_bytes))?;
|
|
||||||
|
|
||||||
let response = self.client.lock().await.request(request).await?;
|
|
||||||
let body_bytes = response.into_body().collect().await?.to_bytes();
|
|
||||||
let json_value = serde_json::from_slice(&body_bytes)?;
|
|
||||||
|
|
||||||
Ok(json_value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_version(&self) -> Result<Value, E> {
|
||||||
|
let data = self.send_request("/version", Method::GET, None).await?;
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
async fn is_mihomo_running(&self) -> Result<(), E> {
|
async fn is_mihomo_running(&self) -> Result<(), E> {
|
||||||
let _ = self.send_request("/version", Method::GET, None).await?;
|
self.get_version().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,12 +4,12 @@ use std::env;
|
|||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref LOCAL_SOCK: String = {
|
static ref LOCAL_SOCK: String = {
|
||||||
// 加载 .env 文件
|
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
|
|
||||||
// 从 .env 或系统环境变量读取
|
|
||||||
env::var("LOCAL_SOCK")
|
env::var("LOCAL_SOCK")
|
||||||
.expect("LOCAL_SOCK must be set in .env or environment variables")
|
.expect("LOCAL_SOCK must be set in .env or environment variables")
|
||||||
|
.trim_matches('"')
|
||||||
|
.to_string()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +27,16 @@ async fn test_mihomo_manager_init() {
|
|||||||
assert_eq!(providers, serde_json::Value::Null);
|
assert_eq!(providers, serde_json::Value::Null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_version() {
|
||||||
|
let manager = mihomo_api::MihomoManager::new(LOCAL_SOCK.to_string());
|
||||||
|
let version = manager.get_version().await;
|
||||||
|
assert!(version.is_ok());
|
||||||
|
if let Ok(version) = version {
|
||||||
|
assert!(!version.get("version").is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_refresh_proxies() {
|
async fn test_refresh_proxies() {
|
||||||
let manager = mihomo_api::MihomoManager::new(LOCAL_SOCK.to_string());
|
let manager = mihomo_api::MihomoManager::new(LOCAL_SOCK.to_string());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user