mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-09-20 03:22:05 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
025605afc0 | ||
|
|
73bea01f40 | ||
|
|
0378191783 | ||
|
|
d5fa6a608d | ||
|
|
811f151155 |
Generated
+2
-23
@@ -2264,7 +2264,6 @@ dependencies = [
|
||||
"derivative",
|
||||
"derive_builder",
|
||||
"derive_more 2.1.1",
|
||||
"easytier-rpc-build",
|
||||
"encoding",
|
||||
"flume 0.12.0",
|
||||
"forwarded-header-value",
|
||||
@@ -2310,6 +2309,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"pnet",
|
||||
"prefix-trie",
|
||||
"proc-macro2",
|
||||
"prost",
|
||||
"prost-build",
|
||||
"prost-reflect",
|
||||
@@ -2319,6 +2319,7 @@ dependencies = [
|
||||
"prost-wkt-types",
|
||||
"quinn",
|
||||
"quinn-plaintext",
|
||||
"quote",
|
||||
"rand 0.8.5",
|
||||
"rcgen",
|
||||
"regex",
|
||||
@@ -2358,7 +2359,6 @@ dependencies = [
|
||||
"tokio-util",
|
||||
"tokio-websockets",
|
||||
"toml 0.8.19",
|
||||
"tonic-build",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"tun-easytier",
|
||||
@@ -2437,14 +2437,6 @@ dependencies = [
|
||||
"windows 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "easytier-rpc-build"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"prost-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "easytier-uptime"
|
||||
version = "0.1.0"
|
||||
@@ -10055,19 +10047,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic-build"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "568392c5a2bd0020723e3f387891176aabafe36fd9fcd074ad309dfa0c8eb964"
|
||||
dependencies = [
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"prost-build",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
|
||||
@@ -3,7 +3,6 @@ resolver = "2"
|
||||
members = [
|
||||
"easytier",
|
||||
"easytier-gui/src-tauri",
|
||||
"easytier-rpc-build",
|
||||
"easytier-web",
|
||||
"easytier-contrib/easytier-ffi",
|
||||
"easytier-contrib/easytier-uptime",
|
||||
|
||||
@@ -99,7 +99,7 @@ while true; do
|
||||
# 启动后的扫尾工作
|
||||
if pgrep -f "${EASYTIER}" >/dev/null; then
|
||||
|
||||
if ! ip rule show | grep -q "lookup main"; then
|
||||
if ! ip rule show | grep -qE '^[0-9]+:[[:space:]]+from all lookup main$'; then
|
||||
ip rule add from all lookup main
|
||||
fi
|
||||
|
||||
@@ -109,4 +109,4 @@ while true; do
|
||||
fi
|
||||
|
||||
sleep 10s
|
||||
done
|
||||
done
|
||||
|
||||
+53
-24
@@ -156,6 +156,17 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "1.1.1"
|
||||
@@ -655,6 +666,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.1"
|
||||
@@ -1181,7 +1201,6 @@ dependencies = [
|
||||
"derivative",
|
||||
"derive_builder",
|
||||
"derive_more",
|
||||
"easytier-rpc-build",
|
||||
"encoding",
|
||||
"flume",
|
||||
"forwarded-header-value",
|
||||
@@ -1205,6 +1224,7 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"kcp-sys",
|
||||
"machine-uid",
|
||||
"moka",
|
||||
"multimap",
|
||||
"natpmp",
|
||||
"netlink-packet-core",
|
||||
@@ -1222,6 +1242,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"pnet",
|
||||
"prefix-trie",
|
||||
"proc-macro2",
|
||||
"prost",
|
||||
"prost-build",
|
||||
"prost-reflect",
|
||||
@@ -1231,6 +1252,7 @@ dependencies = [
|
||||
"prost-wkt-types",
|
||||
"quinn",
|
||||
"quinn-plaintext",
|
||||
"quote",
|
||||
"rand 0.8.5",
|
||||
"rcgen",
|
||||
"regex",
|
||||
@@ -1263,7 +1285,6 @@ dependencies = [
|
||||
"tokio-util",
|
||||
"tokio-websockets",
|
||||
"toml",
|
||||
"tonic-build",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"tun-easytier",
|
||||
@@ -1311,14 +1332,6 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "easytier-rpc-build"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"prost-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
@@ -1469,6 +1482,27 @@ dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
@@ -2817,9 +2851,12 @@ version = "0.12.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"event-listener",
|
||||
"futures-util",
|
||||
"loom",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
@@ -3236,6 +3273,12 @@ dependencies = [
|
||||
"unicode-width 0.1.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.4"
|
||||
@@ -4991,20 +5034,6 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "tonic-build"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11"
|
||||
dependencies = [
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"prost-build",
|
||||
"prost-types",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.2"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::config::types::stored_config::{StoredConfigList, StoredConfigMeta};
|
||||
use ohos_hilog_binding::{hilog_debug, hilog_error};
|
||||
use rusqlite::{Connection, OptionalExtension, params};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
@@ -101,6 +101,110 @@ fn load_meta_record(conn: &Connection, config_id: &str) -> Option<StoredConfigMe
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn validate_snapshot_schema(conn: &Connection) -> bool {
|
||||
let has_stored_configs = conn
|
||||
.query_row(
|
||||
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'stored_configs'",
|
||||
[],
|
||||
|row| row.get::<_, i64>(0),
|
||||
)
|
||||
.optional()
|
||||
.ok()
|
||||
.flatten()
|
||||
.is_some();
|
||||
let has_stored_fields = conn
|
||||
.query_row(
|
||||
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'stored_config_fields'",
|
||||
[],
|
||||
|row| row.get::<_, i64>(0),
|
||||
)
|
||||
.optional()
|
||||
.ok()
|
||||
.flatten()
|
||||
.is_some();
|
||||
has_stored_configs && has_stored_fields
|
||||
}
|
||||
|
||||
fn copy_snapshot_tables(src: &Connection, dst: &mut Connection) -> rusqlite::Result<()> {
|
||||
let mut meta_rows = Vec::<StoredConfigMetaRecord>::new();
|
||||
{
|
||||
let mut stmt = src.prepare(
|
||||
"SELECT config_id, display_name, created_at, updated_at, favorite, temporary
|
||||
FROM stored_configs",
|
||||
)?;
|
||||
let rows = stmt.query_map([], row_to_meta)?;
|
||||
for row in rows {
|
||||
meta_rows.push(row?);
|
||||
}
|
||||
}
|
||||
|
||||
let mut field_rows = Vec::<(String, String, String, String)>::new();
|
||||
{
|
||||
let mut stmt = src.prepare(
|
||||
"SELECT config_id, field_name, field_json, updated_at
|
||||
FROM stored_config_fields",
|
||||
)?;
|
||||
let rows = stmt.query_map([], |row| {
|
||||
Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
row.get::<_, String>(3)?,
|
||||
))
|
||||
})?;
|
||||
for row in rows {
|
||||
field_rows.push(row?);
|
||||
}
|
||||
}
|
||||
|
||||
let tx = dst.unchecked_transaction()?;
|
||||
tx.execute("DELETE FROM stored_config_fields", [])?;
|
||||
tx.execute("DELETE FROM stored_configs", [])?;
|
||||
|
||||
for row in meta_rows {
|
||||
tx.execute(
|
||||
"INSERT INTO stored_configs (
|
||||
config_id, display_name, created_at, updated_at, favorite, temporary
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
||||
params![
|
||||
row.config_id,
|
||||
row.display_name,
|
||||
row.created_at,
|
||||
row.updated_at,
|
||||
if row.favorite { 1 } else { 0 },
|
||||
if row.temporary { 1 } else { 0 }
|
||||
],
|
||||
)?;
|
||||
}
|
||||
|
||||
for (config_id, field_name, field_json, updated_at) in field_rows {
|
||||
tx.execute(
|
||||
"INSERT INTO stored_config_fields (config_id, field_name, field_json, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4)",
|
||||
params![config_id, field_name, field_json, updated_at],
|
||||
)?;
|
||||
}
|
||||
|
||||
tx.commit()
|
||||
}
|
||||
|
||||
fn ensure_parent_dir(path: &Path) -> bool {
|
||||
match path.parent() {
|
||||
Some(parent) => match std::fs::create_dir_all(parent) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
hilog_error!(
|
||||
"[Rust] failed to create snapshot parent {}: {}",
|
||||
parent.display(),
|
||||
e
|
||||
);
|
||||
false
|
||||
}
|
||||
},
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_meta(record: StoredConfigMetaRecord) -> StoredConfigMeta {
|
||||
StoredConfigMeta {
|
||||
config_id: record.config_id,
|
||||
@@ -142,6 +246,79 @@ pub fn init_config_meta_store(root_dir: String) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn export_config_store_snapshot(target_path: String) -> bool {
|
||||
let target = PathBuf::from(target_path);
|
||||
if !ensure_parent_dir(&target) {
|
||||
return false;
|
||||
}
|
||||
let Some(src) = open_db() else {
|
||||
return false;
|
||||
};
|
||||
let mut dst = match Connection::open(&target) {
|
||||
Ok(conn) => conn,
|
||||
Err(e) => {
|
||||
hilog_error!(
|
||||
"[Rust] failed to open snapshot target {}: {}",
|
||||
target.display(),
|
||||
e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if let Err(e) = init_schema(&dst) {
|
||||
hilog_error!(
|
||||
"[Rust] failed to init snapshot schema {}: {}",
|
||||
target.display(),
|
||||
e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
match copy_snapshot_tables(&src, &mut dst) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
hilog_error!(
|
||||
"[Rust] failed to export snapshot {}: {}",
|
||||
target.display(),
|
||||
e
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn import_config_store_snapshot(source_path: String) -> bool {
|
||||
let source = PathBuf::from(source_path);
|
||||
let src = match Connection::open(&source) {
|
||||
Ok(conn) => conn,
|
||||
Err(e) => {
|
||||
hilog_error!(
|
||||
"[Rust] failed to open snapshot source {}: {}",
|
||||
source.display(),
|
||||
e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if !validate_snapshot_schema(&src) {
|
||||
hilog_error!("[Rust] invalid snapshot schema {}", source.display());
|
||||
return false;
|
||||
}
|
||||
let Some(mut dst) = open_db() else {
|
||||
return false;
|
||||
};
|
||||
match copy_snapshot_tables(&src, &mut dst) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
hilog_error!(
|
||||
"[Rust] failed to import snapshot {}: {}",
|
||||
source.display(),
|
||||
e
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_config_meta_entries() -> StoredConfigList {
|
||||
let Some(conn) = open_db() else {
|
||||
return StoredConfigList { configs: vec![] };
|
||||
|
||||
@@ -44,3 +44,11 @@ pub(crate) fn import_toml(toml_text: String, display_name: Option<String>) -> Op
|
||||
pub(crate) fn export_toml(config_id: String) -> Option<String> {
|
||||
config::repository::export_config_toml(&config_id).map(|ret| ret.toml_text)
|
||||
}
|
||||
|
||||
pub(crate) fn export_config_store_snapshot(target_path: String) -> bool {
|
||||
config::storage::config_meta::export_config_store_snapshot(target_path)
|
||||
}
|
||||
|
||||
pub(crate) fn import_config_store_snapshot(source_path: String) -> bool {
|
||||
config::storage::config_meta::import_config_store_snapshot(source_path)
|
||||
}
|
||||
|
||||
@@ -363,6 +363,16 @@ pub fn export_toml(config_id: String) -> Option<String> {
|
||||
exports::config_api::export_toml(config_id)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn export_config_store_snapshot(target_path: String) -> bool {
|
||||
exports::config_api::export_config_store_snapshot(target_path)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn import_config_store_snapshot(source_path: String) -> bool {
|
||||
exports::config_api::import_config_store_snapshot(source_path)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn start_kernel(config_id: String) -> bool {
|
||||
exports::runtime_api::start_kernel(config_id, start_kernel_with_config_id)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "easytier-rpc-build"
|
||||
description = "Protobuf RPC Service Generator for EasyTier"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
homepage = "https://github.com/EasyTier/EasyTier"
|
||||
repository = "https://github.com/EasyTier/EasyTier"
|
||||
authors = ["kkrainbow"]
|
||||
keywords = ["vpn", "p2p", "network", "easytier"]
|
||||
categories = ["network-programming", "command-line-utilities"]
|
||||
license-file = "LICENSE"
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
heck = "0.5"
|
||||
prost-build = "0.13"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
internal-namespace = []
|
||||
@@ -1 +0,0 @@
|
||||
../LICENSE
|
||||
@@ -1,3 +0,0 @@
|
||||
# Introduction
|
||||
|
||||
This is a protobuf rpc service stub generator for [EasyTier](https://github.com/EasyTier/EasyTier) project.
|
||||
@@ -1,449 +0,0 @@
|
||||
extern crate heck;
|
||||
extern crate prost_build;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
#[cfg(feature = "internal-namespace")]
|
||||
const NAMESPACE: &str = "crate::proto::rpc_types";
|
||||
|
||||
#[cfg(not(feature = "internal-namespace"))]
|
||||
const NAMESPACE: &str = "easytier::proto::rpc_types";
|
||||
|
||||
/// The service generator to be used with `prost-build` to generate RPC implementations for
|
||||
/// `prost-simple-rpc`.
|
||||
///
|
||||
/// See the crate-level documentation for more info.
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ServiceGenerator {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl prost_build::ServiceGenerator for ServiceGenerator {
|
||||
fn generate(&mut self, service: prost_build::Service, mut buf: &mut String) {
|
||||
use std::fmt::Write;
|
||||
|
||||
let descriptor_name = format!("{}Descriptor", service.name);
|
||||
let server_name = format!("{}Server", service.name);
|
||||
let client_name = format!("{}Client", service.name);
|
||||
let method_descriptor_name = format!("{}MethodDescriptor", service.name);
|
||||
|
||||
let mut trait_methods = String::new();
|
||||
let mut weak_impl_methods = String::new();
|
||||
let mut enum_methods = String::new();
|
||||
let mut list_enum_methods = String::new();
|
||||
let mut client_methods = String::new();
|
||||
let mut client_own_methods = String::new();
|
||||
let mut match_name_methods = String::new();
|
||||
let mut match_proto_name_methods = String::new();
|
||||
let mut match_input_type_methods = String::new();
|
||||
let mut match_input_proto_type_methods = String::new();
|
||||
let mut match_output_type_methods = String::new();
|
||||
let mut match_output_proto_type_methods = String::new();
|
||||
let mut match_handle_methods = String::new();
|
||||
// generate trait default method Xxx::json_call_method match branch
|
||||
let mut match_trait_json_methods = String::new();
|
||||
|
||||
let mut match_method_try_from = String::new();
|
||||
|
||||
for (idx, method) in service.methods.iter().enumerate() {
|
||||
assert!(
|
||||
!method.client_streaming,
|
||||
"Client streaming not yet supported for method {}",
|
||||
method.proto_name
|
||||
);
|
||||
assert!(
|
||||
!method.server_streaming,
|
||||
"Server streaming not yet supported for method {}",
|
||||
method.proto_name
|
||||
);
|
||||
|
||||
ServiceGenerator::write_comments(&mut trait_methods, 4, &method.comments).unwrap();
|
||||
writeln!(
|
||||
trait_methods,
|
||||
r#" async fn {name}(&self, ctrl: Self::Controller, input: {input_type}) -> {namespace}::error::Result<{output_type}>;"#,
|
||||
name = method.name,
|
||||
input_type = method.input_type,
|
||||
output_type = method.output_type,
|
||||
namespace = NAMESPACE,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
writeln!(
|
||||
weak_impl_methods,
|
||||
r#" async fn {method_name}(&self, ctrl: Self::Controller, input: {input_type}) -> {namespace}::error::Result<{output_type}> {{
|
||||
let Some(service) = self.upgrade() else {{
|
||||
return Err({namespace}::error::Error::Shutdown);
|
||||
}};
|
||||
service.{method_name}(ctrl, input).await
|
||||
}}"#,
|
||||
method_name = method.name,
|
||||
input_type = method.input_type,
|
||||
output_type = method.output_type,
|
||||
namespace = NAMESPACE,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
ServiceGenerator::write_comments(&mut enum_methods, 4, &method.comments).unwrap();
|
||||
writeln!(
|
||||
enum_methods,
|
||||
" {name} = {index},",
|
||||
name = method.proto_name,
|
||||
index = idx + 1
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
writeln!(
|
||||
match_method_try_from,
|
||||
" {index} => Ok({service_name}MethodDescriptor::{name}),",
|
||||
service_name = service.name,
|
||||
name = method.proto_name,
|
||||
index = idx + 1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
writeln!(
|
||||
list_enum_methods,
|
||||
" {service_name}MethodDescriptor::{name},",
|
||||
service_name = service.name,
|
||||
name = method.proto_name
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
writeln!(
|
||||
client_methods,
|
||||
r#" async fn {name}(&self, ctrl: H::Controller, input: {input_type}) -> {namespace}::error::Result<{output_type}> {{
|
||||
{client_name}Client::{name}_inner(self.0.clone(), ctrl, input).await
|
||||
}}"#,
|
||||
name = method.name,
|
||||
input_type = method.input_type,
|
||||
output_type = method.output_type,
|
||||
client_name = service.name,
|
||||
namespace = NAMESPACE,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
writeln!(
|
||||
client_own_methods,
|
||||
r#" async fn {name}_inner(handler: H, ctrl: H::Controller, input: {input_type}) -> {namespace}::error::Result<{output_type}> {{
|
||||
{namespace}::__rt::call_method(handler, ctrl, {method_descriptor_name}::{proto_name}, input).await
|
||||
}}"#,
|
||||
name = method.name,
|
||||
method_descriptor_name = method_descriptor_name,
|
||||
proto_name = method.proto_name,
|
||||
input_type = method.input_type,
|
||||
output_type = method.output_type,
|
||||
namespace = NAMESPACE,
|
||||
).unwrap();
|
||||
|
||||
let case = format!(
|
||||
" {service_name}MethodDescriptor::{proto_name} => ",
|
||||
service_name = service.name,
|
||||
proto_name = method.proto_name
|
||||
);
|
||||
|
||||
writeln!(match_name_methods, "{}{:?},", case, method.name).unwrap();
|
||||
writeln!(match_proto_name_methods, "{}{:?},", case, method.proto_name).unwrap();
|
||||
writeln!(
|
||||
match_input_type_methods,
|
||||
"{}::std::any::TypeId::of::<{}>(),",
|
||||
case, method.input_type
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
match_input_proto_type_methods,
|
||||
"{}{:?},",
|
||||
case, method.input_proto_type
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
match_output_type_methods,
|
||||
"{}::std::any::TypeId::of::<{}>(),",
|
||||
case, method.output_type
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
match_output_proto_type_methods,
|
||||
"{}{:?},",
|
||||
case, method.output_proto_type
|
||||
)
|
||||
.unwrap();
|
||||
write!(
|
||||
match_handle_methods,
|
||||
r#"{} {{
|
||||
let decoded: {input_type} = {namespace}::__rt::decode(input)?;
|
||||
let ret = service.{name}(ctrl, decoded).await?;
|
||||
{namespace}::__rt::encode(ret)
|
||||
}}
|
||||
"#,
|
||||
case,
|
||||
input_type = method.input_type,
|
||||
name = method.name,
|
||||
namespace = NAMESPACE,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
write!(
|
||||
match_trait_json_methods,
|
||||
r#" "{name}" | "{proto_name}" => {{
|
||||
let req: {input_type} = ::serde_json::from_value(json).map_err(|e| {namespace}::error::Error::MalformatRpcPacket(format!("json error: {{}}", e)))?;
|
||||
let resp = self.{typed_method}(ctrl, req).await?;
|
||||
Ok(::serde_json::to_value(resp).map_err(|e| {namespace}::error::Error::MalformatRpcPacket(format!("json error: {{}}", e)))?)
|
||||
}}
|
||||
"#,
|
||||
name = method.name,
|
||||
proto_name = method.proto_name,
|
||||
input_type = method.input_type,
|
||||
typed_method = method.name,
|
||||
namespace = NAMESPACE,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
ServiceGenerator::write_comments(&mut buf, 0, &service.comments).unwrap();
|
||||
write!(
|
||||
buf,
|
||||
r#"
|
||||
#[async_trait::async_trait]
|
||||
#[auto_impl::auto_impl(&, Arc, Box)]
|
||||
pub trait {name} {{
|
||||
type Controller: {namespace}::controller::Controller;
|
||||
|
||||
{trait_methods}
|
||||
|
||||
async fn json_call_method(
|
||||
&self,
|
||||
ctrl: Self::Controller,
|
||||
method_name: &str,
|
||||
json: ::serde_json::Value,
|
||||
) -> {namespace}::error::Result<::serde_json::Value> {{
|
||||
match method_name {{
|
||||
{match_trait_json_methods}
|
||||
_ => Err({namespace}::error::Error::InvalidMethodIndex(0, method_name.to_string())),
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<T> {name} for ::std::sync::Weak<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
::std::sync::Arc<T>: {name},
|
||||
{{
|
||||
type Controller = <::std::sync::Arc<T> as {name}>::Controller;
|
||||
|
||||
{weak_impl_methods}
|
||||
}}
|
||||
|
||||
/// A service descriptor for a `{name}`.
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Default)]
|
||||
pub struct {descriptor_name};
|
||||
|
||||
/// Methods available on a `{name}`.
|
||||
///
|
||||
/// This can be used as a key when routing requests for servers/clients of a `{name}`.
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
#[repr(u8)]
|
||||
pub enum {method_descriptor_name} {{
|
||||
{enum_methods}
|
||||
}}
|
||||
|
||||
impl std::convert::TryFrom<u8> for {method_descriptor_name} {{
|
||||
type Error = {namespace}::error::Error;
|
||||
fn try_from(value: u8) -> {namespace}::error::Result<Self> {{
|
||||
match value {{
|
||||
{match_method_try_from}
|
||||
_ => Err({namespace}::error::Error::InvalidMethodIndex(value, "{name}".to_string())),
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
/// A client for a `{name}`.
|
||||
///
|
||||
/// This implements the `{name}` trait by dispatching all method calls to the supplied `Handler`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct {client_name}<H>(H) where H: {namespace}::handler::Handler;
|
||||
|
||||
impl<H> {client_name}<H> where H: {namespace}::handler::Handler<Descriptor = {descriptor_name}> {{
|
||||
/// Creates a new client instance that delegates all method calls to the supplied handler.
|
||||
pub fn new(handler: H) -> {client_name}<H> {{
|
||||
{client_name}(handler)
|
||||
}}
|
||||
}}
|
||||
|
||||
impl<H> {client_name}<H> where H: {namespace}::handler::Handler<Descriptor = {descriptor_name}> {{
|
||||
{client_own_methods}
|
||||
}}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<H> {name} for {client_name}<H> where H: {namespace}::handler::Handler<Descriptor = {descriptor_name}> {{
|
||||
type Controller = H::Controller;
|
||||
|
||||
{client_methods}
|
||||
}}
|
||||
|
||||
pub struct {client_name}Factory<C: {namespace}::controller::Controller>(std::marker::PhantomData<C>);
|
||||
|
||||
impl<C: {namespace}::controller::Controller> Clone for {client_name}Factory<C> {{
|
||||
fn clone(&self) -> Self {{
|
||||
Self(std::marker::PhantomData)
|
||||
}}
|
||||
}}
|
||||
|
||||
impl<C> {namespace}::__rt::RpcClientFactory for {client_name}Factory<C> where C: {namespace}::controller::Controller {{
|
||||
type Descriptor = {descriptor_name};
|
||||
type ClientImpl = Box<dyn {name}<Controller = C> + Send + Sync + 'static>;
|
||||
type Controller = C;
|
||||
|
||||
fn new(handler: impl {namespace}::handler::Handler<Descriptor = Self::Descriptor, Controller = Self::Controller>) -> Self::ClientImpl {{
|
||||
Box::new({client_name}::new(handler))
|
||||
}}
|
||||
}}
|
||||
|
||||
/// A server for a `{name}`.
|
||||
///
|
||||
/// This implements the `Server` trait by handling requests and dispatch them to methods on the
|
||||
/// supplied `{name}`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct {server_name}<A>(A) where A: {name} + Clone + Send + 'static;
|
||||
|
||||
impl<T> {server_name}<::std::sync::Weak<T>>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
::std::sync::Arc<T>: {name},
|
||||
{{
|
||||
pub fn new_arc(service: ::std::sync::Arc<T>) -> {server_name}<::std::sync::Weak<T>> {{
|
||||
{server_name}(::std::sync::Arc::downgrade(&service))
|
||||
}}
|
||||
}}
|
||||
|
||||
impl<A> {server_name}<A> where A: {name} + Clone + Send + 'static {{
|
||||
/// Creates a new server instance that dispatches all calls to the supplied service.
|
||||
pub fn new(service: A) -> {server_name}<A> {{
|
||||
{server_name}(service)
|
||||
}}
|
||||
|
||||
async fn call_inner(
|
||||
service: A,
|
||||
method: {method_descriptor_name},
|
||||
ctrl: A::Controller,
|
||||
input: ::bytes::Bytes)
|
||||
-> {namespace}::error::Result<::bytes::Bytes> {{
|
||||
match method {{
|
||||
{match_handle_methods}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
impl {namespace}::descriptor::ServiceDescriptor for {descriptor_name} {{
|
||||
type Method = {method_descriptor_name};
|
||||
fn name(&self) -> &'static str {{ {name:?} }}
|
||||
fn proto_name(&self) -> &'static str {{ {proto_name:?} }}
|
||||
fn package(&self) -> &'static str {{ {package:?} }}
|
||||
fn methods(&self) -> &'static [Self::Method] {{
|
||||
&[ {list_enum_methods} ]
|
||||
}}
|
||||
}}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<A> {namespace}::handler::Handler for {server_name}<A>
|
||||
where
|
||||
A: {name} + Clone + Send + Sync + 'static {{
|
||||
type Descriptor = {descriptor_name};
|
||||
type Controller = A::Controller;
|
||||
|
||||
async fn call(
|
||||
&self,
|
||||
ctrl: A::Controller,
|
||||
method: {method_descriptor_name},
|
||||
input: ::bytes::Bytes)
|
||||
-> {namespace}::error::Result<::bytes::Bytes> {{
|
||||
{server_name}::call_inner(self.0.clone(), method, ctrl, input).await
|
||||
}}
|
||||
}}
|
||||
|
||||
impl {namespace}::descriptor::MethodDescriptor for {method_descriptor_name} {{
|
||||
fn name(&self) -> &'static str {{
|
||||
match *self {{
|
||||
{match_name_methods}
|
||||
}}
|
||||
}}
|
||||
|
||||
fn proto_name(&self) -> &'static str {{
|
||||
match *self {{
|
||||
{match_proto_name_methods}
|
||||
}}
|
||||
}}
|
||||
|
||||
fn input_type(&self) -> ::std::any::TypeId {{
|
||||
match *self {{
|
||||
{match_input_type_methods}
|
||||
}}
|
||||
}}
|
||||
|
||||
fn input_proto_type(&self) -> &'static str {{
|
||||
match *self {{
|
||||
{match_input_proto_type_methods}
|
||||
}}
|
||||
}}
|
||||
|
||||
fn output_type(&self) -> ::std::any::TypeId {{
|
||||
match *self {{
|
||||
{match_output_type_methods}
|
||||
}}
|
||||
}}
|
||||
|
||||
fn output_proto_type(&self) -> &'static str {{
|
||||
match *self {{
|
||||
{match_output_proto_type_methods}
|
||||
}}
|
||||
}}
|
||||
|
||||
fn index(&self) -> u8 {{
|
||||
*self as u8
|
||||
}}
|
||||
}}
|
||||
"#,
|
||||
name = service.name,
|
||||
descriptor_name = descriptor_name,
|
||||
server_name = server_name,
|
||||
client_name = client_name,
|
||||
method_descriptor_name = method_descriptor_name,
|
||||
proto_name = service.proto_name,
|
||||
package = service.package,
|
||||
trait_methods = trait_methods,
|
||||
weak_impl_methods = weak_impl_methods,
|
||||
enum_methods = enum_methods,
|
||||
list_enum_methods = list_enum_methods,
|
||||
client_own_methods = client_own_methods,
|
||||
client_methods = client_methods,
|
||||
match_name_methods = match_name_methods,
|
||||
match_proto_name_methods = match_proto_name_methods,
|
||||
match_input_type_methods = match_input_type_methods,
|
||||
match_input_proto_type_methods = match_input_proto_type_methods,
|
||||
match_output_type_methods = match_output_type_methods,
|
||||
match_output_proto_type_methods = match_output_proto_type_methods,
|
||||
match_handle_methods = match_handle_methods,
|
||||
match_trait_json_methods = match_trait_json_methods,
|
||||
namespace = NAMESPACE,
|
||||
).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceGenerator {
|
||||
fn write_comments<W>(
|
||||
mut write: W,
|
||||
indent: usize,
|
||||
comments: &prost_build::Comments,
|
||||
) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
for comment in &comments.leading {
|
||||
for line in comment.lines().filter(|s| !s.is_empty()) {
|
||||
writeln!(write, "{}///{}", " ".repeat(indent), line)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ clap = { version = "4.4.8", features = [
|
||||
"unicode",
|
||||
"derive",
|
||||
"wrap_help",
|
||||
"env",
|
||||
] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
@@ -41,23 +41,32 @@ rust_i18n::i18n!("locales", fallback = "en");
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "easytier-web", author, version = EASYTIER_VERSION , about, long_about = None)]
|
||||
struct Cli {
|
||||
#[arg(short, long, default_value = "et.db", help = t!("cli.db").to_string())]
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
env = "ET_WEB_DB",
|
||||
default_value = "et.db",
|
||||
help = t!("cli.db").to_string()
|
||||
)]
|
||||
db: String,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_WEB_CONSOLE_LOG_LEVEL",
|
||||
help = t!("cli.console_log_level").to_string(),
|
||||
)]
|
||||
console_log_level: Option<String>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_WEB_FILE_LOG_LEVEL",
|
||||
help = t!("cli.file_log_level").to_string(),
|
||||
)]
|
||||
file_log_level: Option<String>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_WEB_FILE_LOG_DIR",
|
||||
help = t!("cli.file_log_dir").to_string(),
|
||||
)]
|
||||
file_log_dir: Option<String>,
|
||||
@@ -65,6 +74,7 @@ struct Cli {
|
||||
#[arg(
|
||||
long,
|
||||
short='c',
|
||||
env = "ET_CONFIG_SERVER_PORT",
|
||||
default_value = "22020",
|
||||
help = t!("cli.config_server_port").to_string(),
|
||||
)]
|
||||
@@ -73,6 +83,7 @@ struct Cli {
|
||||
#[arg(
|
||||
long,
|
||||
short='p',
|
||||
env = "ET_CONFIG_SERVER_PROTOCOL",
|
||||
default_value = "udp",
|
||||
help = t!("cli.config_server_protocol").to_string(),
|
||||
)]
|
||||
@@ -81,6 +92,7 @@ struct Cli {
|
||||
#[arg(
|
||||
long,
|
||||
short='a',
|
||||
env = "ET_API_SERVER_PORT",
|
||||
default_value = "11211",
|
||||
help = t!("cli.api_server_port").to_string(),
|
||||
)]
|
||||
@@ -88,6 +100,7 @@ struct Cli {
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_API_SERVER_ADDR",
|
||||
default_value = "0.0.0.0",
|
||||
help = t!("cli.api_server_addr").to_string(),
|
||||
)]
|
||||
@@ -95,6 +108,7 @@ struct Cli {
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_GEOIP_DB",
|
||||
help = t!("cli.geoip_db").to_string(),
|
||||
)]
|
||||
geoip_db: Option<String>,
|
||||
@@ -103,6 +117,7 @@ struct Cli {
|
||||
#[arg(
|
||||
long,
|
||||
short='l',
|
||||
env = "ET_WEB_SERVER_PORT",
|
||||
help = t!("cli.web_server_port").to_string(),
|
||||
)]
|
||||
web_server_port: Option<u16>,
|
||||
@@ -110,6 +125,7 @@ struct Cli {
|
||||
#[cfg(feature = "embed")]
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_WEB_SERVER_ADDR",
|
||||
default_value = "0.0.0.0",
|
||||
help = t!("cli.web_server_addr").to_string(),
|
||||
)]
|
||||
@@ -118,6 +134,7 @@ struct Cli {
|
||||
#[cfg(feature = "embed")]
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_NO_WEB",
|
||||
help = t!("cli.no_web").to_string(),
|
||||
default_value = "false"
|
||||
)]
|
||||
@@ -126,6 +143,7 @@ struct Cli {
|
||||
#[cfg(feature = "embed")]
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_API_HOST",
|
||||
help = t!("cli.api_host").to_string()
|
||||
)]
|
||||
api_host: Option<url::Url>,
|
||||
@@ -144,35 +162,45 @@ struct Cli {
|
||||
pub struct WebhookOptions {
|
||||
/// Base URL of the webhook endpoint for token validation and event delivery.
|
||||
/// When set, incoming tokens are validated via this webhook before local fallback.
|
||||
#[arg(long)]
|
||||
#[arg(long, env = "ET_WEBHOOK_URL")]
|
||||
pub webhook_url: Option<String>,
|
||||
|
||||
/// Shared secret used to authenticate outbound webhook calls.
|
||||
#[arg(long)]
|
||||
#[arg(long, env = "ET_WEBHOOK_SECRET", hide_env_values = true)]
|
||||
pub webhook_secret: Option<String>,
|
||||
|
||||
/// Token for X-Internal-Auth header. When set, API requests with this header
|
||||
/// bypass session authentication.
|
||||
#[arg(long)]
|
||||
#[arg(long, env = "ET_INTERNAL_AUTH_TOKEN", hide_env_values = true)]
|
||||
pub internal_auth_token: Option<String>,
|
||||
|
||||
/// Stable identifier for this easytier-web instance when routing webhook callbacks.
|
||||
#[arg(long)]
|
||||
#[arg(long, env = "ET_WEB_INSTANCE_ID")]
|
||||
pub web_instance_id: Option<String>,
|
||||
|
||||
/// Reachable base URL for this easytier-web instance's internal REST API.
|
||||
#[arg(long)]
|
||||
#[arg(long, env = "ET_WEB_INSTANCE_API_BASE_URL")]
|
||||
pub web_instance_api_base_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, clap::Args)]
|
||||
pub struct FeatureFlags {
|
||||
/// Whether user registration via the web UI is disabled.
|
||||
#[arg(long, default_value = "false", help = t!("cli.disable_registration").to_string())]
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_DISABLE_REGISTRATION",
|
||||
default_value = "false",
|
||||
help = t!("cli.disable_registration").to_string()
|
||||
)]
|
||||
pub disable_registration: bool,
|
||||
|
||||
/// Whether to auto-create users when they connect via heartbeat with an unknown token.
|
||||
#[arg(long, default_value = "false", help = t!("cli.allow_auto_create_user").to_string())]
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_ALLOW_AUTO_CREATE_USER",
|
||||
default_value = "false",
|
||||
help = t!("cli.allow_auto_create_user").to_string()
|
||||
)]
|
||||
pub allow_auto_create_user: bool,
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -11,6 +11,7 @@ keywords = ["vpn", "p2p", "network", "easytier"]
|
||||
categories = ["network-programming", "command-line-utilities"]
|
||||
license-file = "LICENSE"
|
||||
readme = "README.md"
|
||||
build = "build/main.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -316,15 +317,14 @@ jemalloc-sys = { package = "tikv-jemalloc-sys", version = "0.6.0", features = [
|
||||
|
||||
[build-dependencies]
|
||||
cfg_aliases = "0.2.1"
|
||||
tonic-build = "0.12"
|
||||
indoc = "2.0"
|
||||
globwalk = "0.8.1"
|
||||
regex = "1"
|
||||
prost-build = "0.13.5"
|
||||
prost-wkt-build = "0.6"
|
||||
easytier-rpc-build = { path = "../easytier-rpc-build", features = [
|
||||
"internal-namespace",
|
||||
] }
|
||||
prost-reflect-build = { version = "0.14.0" }
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
thunk-rs = { git = "https://github.com/easytier/thunk.git", default-features = false, features = [
|
||||
"win7",
|
||||
] }
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
mod rpc;
|
||||
|
||||
use crate::rpc::ServiceGenerator;
|
||||
use cfg_aliases::cfg_aliases;
|
||||
use prost_wkt_build::{FileDescriptorSet, Message as _};
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -197,7 +200,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.type_attribute("acl.Rule", "#[serde(default)]")
|
||||
.type_attribute("acl.GroupInfo", "#[serde(default)]")
|
||||
.field_attribute(".api.manage.NetworkConfig", "#[serde(default)]")
|
||||
.service_generator(Box::new(easytier_rpc_build::ServiceGenerator::default()))
|
||||
.service_generator(Box::new(ServiceGenerator::default()))
|
||||
.btree_map(["."])
|
||||
.skip_debug([".common.Ipv4Addr", ".common.Ipv6Addr", ".common.UUID"]);
|
||||
|
||||
@@ -0,0 +1,720 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use indoc::formatdoc;
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use std::str::FromStr;
|
||||
|
||||
fn parse(value: &str) -> TokenStream {
|
||||
TokenStream::from_str(value)
|
||||
.unwrap_or_else(|err| panic!("Failed to parse tokens: {} ({})", value, err))
|
||||
}
|
||||
|
||||
fn doc(comments: &prost_build::Comments) -> TokenStream {
|
||||
let doc = comments
|
||||
.leading
|
||||
.iter()
|
||||
.flat_map(|c| c.lines().filter(|s| !s.is_empty()));
|
||||
quote! { #( #[doc = #doc] )* }
|
||||
}
|
||||
|
||||
const NAMESPACE: &str = "crate::proto::rpc_types";
|
||||
|
||||
struct Method {
|
||||
index: u8,
|
||||
doc: TokenStream,
|
||||
method: Ident,
|
||||
method_inner: Ident,
|
||||
method_str: String,
|
||||
method_proto: Ident,
|
||||
method_proto_str: String,
|
||||
Input: TokenStream,
|
||||
Input_proto_str: String,
|
||||
Output: TokenStream,
|
||||
Output_proto_str: String,
|
||||
}
|
||||
|
||||
impl Method {
|
||||
fn new(index: u8, method: prost_build::Method) -> Self {
|
||||
assert!(
|
||||
!method.client_streaming,
|
||||
"Client streaming not yet supported for method {}",
|
||||
method.proto_name
|
||||
);
|
||||
assert!(
|
||||
!method.server_streaming,
|
||||
"Server streaming not yet supported for method {}",
|
||||
method.proto_name
|
||||
);
|
||||
Self {
|
||||
index,
|
||||
doc: doc(&method.comments),
|
||||
method: format_ident!("{}", method.name),
|
||||
method_inner: format_ident!("{}_inner", method.name),
|
||||
method_str: method.name,
|
||||
method_proto: format_ident!("{}", method.proto_name),
|
||||
method_proto_str: method.proto_name,
|
||||
Input: parse(&method.input_type),
|
||||
Input_proto_str: method.input_proto_type,
|
||||
Output: parse(&method.output_type),
|
||||
Output_proto_str: method.output_proto_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Service {
|
||||
namespace: TokenStream,
|
||||
doc: TokenStream,
|
||||
Service: Ident,
|
||||
ServiceDescriptor: Ident,
|
||||
ServiceServer: Ident,
|
||||
ServiceClient: Ident,
|
||||
ServiceClientFactory: Ident,
|
||||
ServiceMethodDescriptor: Ident,
|
||||
Service_str: String,
|
||||
Service_proto_str: String,
|
||||
Service_package_str: String,
|
||||
methods: Vec<Method>,
|
||||
}
|
||||
|
||||
impl Service {
|
||||
fn new(service: prost_build::Service) -> Self {
|
||||
let methods = service
|
||||
.methods
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, method)| Method::new((i + 1) as u8, method))
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
namespace: parse(NAMESPACE),
|
||||
doc: doc(&service.comments),
|
||||
Service: format_ident!("{}", service.name),
|
||||
ServiceDescriptor: format_ident!("{}Descriptor", service.name),
|
||||
ServiceServer: format_ident!("{}Server", service.name),
|
||||
ServiceClient: format_ident!("{}Client", service.name),
|
||||
ServiceClientFactory: format_ident!("{}ClientFactory", service.name),
|
||||
ServiceMethodDescriptor: format_ident!("{}MethodDescriptor", service.name),
|
||||
Service_str: service.name,
|
||||
Service_proto_str: service.proto_name,
|
||||
Service_package_str: service.package,
|
||||
methods,
|
||||
}
|
||||
}
|
||||
|
||||
fn trait_Service(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
doc,
|
||||
Service,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let match_json_call_method = methods.iter().map(
|
||||
|Method {
|
||||
method,
|
||||
method_str,
|
||||
method_proto_str,
|
||||
Input,
|
||||
..
|
||||
}| {
|
||||
quote! {
|
||||
#method_str | #method_proto_str => {
|
||||
let req: #Input = ::serde_json::from_value(json)
|
||||
.map_err(|e| #namespace::error::Error::MalformatRpcPacket(format!("json error: {}", e)))?;
|
||||
let resp = self.#method(ctrl, req).await?;
|
||||
Ok(::serde_json::to_value(resp)
|
||||
.map_err(|e| #namespace::error::Error::MalformatRpcPacket(format!("json error: {}", e)))?)
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let methods = methods.iter().map(
|
||||
|Method {
|
||||
doc,
|
||||
method,
|
||||
Input,
|
||||
Output,
|
||||
..
|
||||
}| {
|
||||
quote! {
|
||||
#doc
|
||||
async fn #method(&self, ctrl: Self::Controller, input: #Input) -> #namespace::error::Result<#Output>;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
#doc
|
||||
#[async_trait::async_trait]
|
||||
#[auto_impl::auto_impl(&, Arc, Box)]
|
||||
pub trait #Service {
|
||||
type Controller: #namespace::controller::Controller;
|
||||
|
||||
#(#methods)*
|
||||
|
||||
async fn json_call_method(
|
||||
&self,
|
||||
ctrl: Self::Controller,
|
||||
method: &str,
|
||||
json: ::serde_json::Value,
|
||||
) -> #namespace::error::Result<::serde_json::Value> {
|
||||
match method {
|
||||
#(#match_json_call_method)*
|
||||
_ => Err(#namespace::error::Error::InvalidMethodIndex(0, method.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_Service_for_Weak(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
Service,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
let methods = methods.iter().map(
|
||||
|Method {
|
||||
method,
|
||||
Input,
|
||||
Output,
|
||||
..
|
||||
}| {
|
||||
quote! {
|
||||
async fn #method(&self, ctrl: Self::Controller, input: #Input) -> #namespace::error::Result<#Output> {
|
||||
let Some(service) = self.upgrade() else {
|
||||
return Err(#namespace::error::Error::Shutdown);
|
||||
};
|
||||
service.#method(ctrl, input).await
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
#[async_trait::async_trait]
|
||||
impl<T> #Service for ::std::sync::Weak<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
::std::sync::Arc<T>: #Service,
|
||||
{
|
||||
type Controller = <::std::sync::Arc<T> as #Service>::Controller;
|
||||
|
||||
#(#methods)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn struct_ServiceDescriptor(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
ServiceDescriptor,
|
||||
ServiceMethodDescriptor,
|
||||
Service_str,
|
||||
Service_proto_str,
|
||||
Service_package_str,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let doc = format!("A service descriptor for a `{}`.", Service_str);
|
||||
|
||||
let methods = methods.iter().map(|Method { method_proto, .. }| {
|
||||
quote! { #ServiceMethodDescriptor::#method_proto, }
|
||||
});
|
||||
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Default)]
|
||||
pub struct #ServiceDescriptor;
|
||||
|
||||
impl #namespace::descriptor::ServiceDescriptor for #ServiceDescriptor {
|
||||
type Method = #ServiceMethodDescriptor;
|
||||
fn name(&self) -> &'static str { #Service_str }
|
||||
fn proto_name(&self) -> &'static str { #Service_proto_str }
|
||||
fn package(&self) -> &'static str { #Service_package_str }
|
||||
fn methods(&self) -> &'static [Self::Method] {
|
||||
&[ #(#methods)* ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enum_ServiceMethodDescriptor(&self) -> TokenStream {
|
||||
let Self {
|
||||
ServiceMethodDescriptor,
|
||||
Service_str,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let doc = formatdoc! {"
|
||||
Methods available on a `{Service_str}`.
|
||||
|
||||
This can be used as a key when routing requests for servers/clients of a `{Service_str}`.
|
||||
"};
|
||||
|
||||
let variants = methods.iter().map(
|
||||
|Method {
|
||||
method_proto,
|
||||
index,
|
||||
..
|
||||
}| {
|
||||
quote! { #method_proto = #index, }
|
||||
},
|
||||
);
|
||||
|
||||
let impl_MethodDescriptor = self.impl_MethodDescriptor_for_ServiceMethodDescriptor();
|
||||
let impl_TryFrom = self.impl_TryFrom_for_ServiceMethodDescriptor();
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
#[repr(u8)]
|
||||
pub enum #ServiceMethodDescriptor {
|
||||
#(#variants)*
|
||||
}
|
||||
|
||||
#impl_MethodDescriptor
|
||||
|
||||
#impl_TryFrom
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_MethodDescriptor_for_ServiceMethodDescriptor(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
ServiceMethodDescriptor,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let name = {
|
||||
let arms = methods.iter().map(
|
||||
|Method {
|
||||
method_proto,
|
||||
method_str,
|
||||
..
|
||||
}| {
|
||||
quote! { #ServiceMethodDescriptor::#method_proto => #method_str, }
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
fn name(&self) -> &'static str {
|
||||
match *self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let proto_name = {
|
||||
let arms = methods.iter().map(
|
||||
|Method {
|
||||
method_proto,
|
||||
method_proto_str,
|
||||
..
|
||||
}| {
|
||||
quote! { #ServiceMethodDescriptor::#method_proto => #method_proto_str, }
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
fn proto_name(&self) -> &'static str {
|
||||
match *self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let input_type = {
|
||||
let arms = methods.iter().map(|Method { method_proto, Input, .. }| {
|
||||
quote! { #ServiceMethodDescriptor::#method_proto => ::std::any::TypeId::of::<#Input>(), }
|
||||
});
|
||||
|
||||
quote! {
|
||||
fn input_type(&self) -> ::std::any::TypeId {
|
||||
match *self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let input_proto_type = {
|
||||
let arms = methods.iter().map(
|
||||
|Method {
|
||||
method_proto,
|
||||
Input_proto_str,
|
||||
..
|
||||
}| {
|
||||
quote! { #ServiceMethodDescriptor::#method_proto => #Input_proto_str, }
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
fn input_proto_type(&self) -> &'static str {
|
||||
match *self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let output_type = {
|
||||
let arms = methods.iter().map(|Method { method_proto, Output, .. }| {
|
||||
quote! { #ServiceMethodDescriptor::#method_proto => ::std::any::TypeId::of::<#Output>(), }
|
||||
});
|
||||
|
||||
quote! {
|
||||
fn output_type(&self) -> ::std::any::TypeId {
|
||||
match *self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let output_proto_type = {
|
||||
let arms = methods.iter().map(
|
||||
|Method {
|
||||
method_proto,
|
||||
Output_proto_str,
|
||||
..
|
||||
}| {
|
||||
quote! { #ServiceMethodDescriptor::#method_proto => #Output_proto_str, }
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
fn output_proto_type(&self) -> &'static str {
|
||||
match *self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
quote! {
|
||||
impl #namespace::descriptor::MethodDescriptor for #ServiceMethodDescriptor {
|
||||
#name
|
||||
|
||||
#proto_name
|
||||
|
||||
#input_type
|
||||
|
||||
#input_proto_type
|
||||
|
||||
#output_type
|
||||
|
||||
#output_proto_type
|
||||
|
||||
fn index(&self) -> u8 {
|
||||
*self as u8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_TryFrom_for_ServiceMethodDescriptor(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
ServiceMethodDescriptor,
|
||||
Service_str,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let arms = methods.iter().map(
|
||||
|Method {
|
||||
method_proto,
|
||||
index,
|
||||
..
|
||||
}| {
|
||||
quote! { #index => Ok(#ServiceMethodDescriptor::#method_proto), }
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
impl std::convert::TryFrom<u8> for #ServiceMethodDescriptor {
|
||||
type Error = #namespace::error::Error;
|
||||
fn try_from(value: u8) -> #namespace::error::Result<Self> {
|
||||
match value {
|
||||
#(#arms)*
|
||||
_ => Err(#namespace::error::Error::InvalidMethodIndex(value, #Service_str.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn struct_ServiceClient(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
ServiceDescriptor,
|
||||
ServiceClient,
|
||||
Service_str,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let doc = formatdoc! {"
|
||||
A client for a `{Service_str}`.
|
||||
|
||||
This implements the `{Service_str}` trait by dispatching all method calls to the supplied `Handler`.
|
||||
"};
|
||||
|
||||
let impl_service_client = self.impl_ServiceClient();
|
||||
let impl_service_for_client = self.impl_Service_for_ServiceClient();
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct #ServiceClient<H>(H) where H: #namespace::handler::Handler;
|
||||
|
||||
impl<H> #ServiceClient<H> where H: #namespace::handler::Handler<Descriptor = #ServiceDescriptor> {
|
||||
/// Creates a new client instance that delegates all method calls to the supplied handler.
|
||||
pub fn new(handler: H) -> Self {
|
||||
Self(handler)
|
||||
}
|
||||
}
|
||||
|
||||
#impl_service_client
|
||||
|
||||
#impl_service_for_client
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_ServiceClient(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
ServiceClient,
|
||||
ServiceDescriptor,
|
||||
ServiceMethodDescriptor,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let methods = methods.iter().map(
|
||||
|Method {
|
||||
method_inner,
|
||||
method_proto,
|
||||
Input,
|
||||
Output,
|
||||
..
|
||||
}| {
|
||||
quote! {
|
||||
async fn #method_inner(handler: H, ctrl: H::Controller, input: #Input) -> #namespace::error::Result<#Output> {
|
||||
#namespace::__rt::call_method(handler, ctrl, #ServiceMethodDescriptor::#method_proto, input).await
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
impl<H> #ServiceClient<H> where H: #namespace::handler::Handler<Descriptor = #ServiceDescriptor> {
|
||||
#(#methods)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_Service_for_ServiceClient(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
Service,
|
||||
ServiceClient,
|
||||
ServiceDescriptor,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let methods = methods.iter().map(
|
||||
|Method {
|
||||
method,
|
||||
method_inner,
|
||||
Input,
|
||||
Output,
|
||||
..
|
||||
}| {
|
||||
quote! {
|
||||
async fn #method(&self, ctrl: H::Controller, input: #Input) -> #namespace::error::Result<#Output> {
|
||||
#ServiceClient::#method_inner(self.0.clone(), ctrl, input).await
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
#[async_trait::async_trait]
|
||||
impl<H> #Service for #ServiceClient<H> where H: #namespace::handler::Handler<Descriptor = #ServiceDescriptor> {
|
||||
type Controller = H::Controller;
|
||||
|
||||
#(#methods)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn struct_ServiceClientFactory(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
Service,
|
||||
ServiceClient,
|
||||
ServiceClientFactory,
|
||||
ServiceDescriptor,
|
||||
..
|
||||
} = self;
|
||||
|
||||
quote! {
|
||||
pub struct #ServiceClientFactory<C: #namespace::controller::Controller>(std::marker::PhantomData<C>);
|
||||
|
||||
impl<C: #namespace::controller::Controller> Clone for #ServiceClientFactory<C> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(std::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> #namespace::__rt::RpcClientFactory for #ServiceClientFactory<C> where C: #namespace::controller::Controller {
|
||||
type Descriptor = #ServiceDescriptor;
|
||||
type ClientImpl = Box<dyn #Service<Controller = C> + Send + Sync + 'static>;
|
||||
type Controller = C;
|
||||
|
||||
fn new(handler: impl #namespace::handler::Handler<Descriptor = Self::Descriptor, Controller = Self::Controller>) -> Self::ClientImpl {
|
||||
Box::new(#ServiceClient::new(handler))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn struct_ServiceServer(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
Service,
|
||||
ServiceDescriptor,
|
||||
ServiceServer,
|
||||
ServiceMethodDescriptor,
|
||||
Service_str,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let doc = formatdoc! {"
|
||||
A server for a `{Service_str}`.
|
||||
|
||||
This implements the `Server` trait by handling requests and dispatch them to methods on the
|
||||
supplied `{Service_str}`.
|
||||
"};
|
||||
|
||||
let arms = methods.iter().map(
|
||||
|Method {
|
||||
method_proto,
|
||||
method,
|
||||
Input,
|
||||
..
|
||||
}| {
|
||||
quote! {
|
||||
#ServiceMethodDescriptor::#method_proto => {
|
||||
let decoded: #Input = #namespace::__rt::decode(input)?;
|
||||
let ret = service.#method(ctrl, decoded).await?;
|
||||
#namespace::__rt::encode(ret)
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct #ServiceServer<A>(A) where A: #Service + Clone + Send + 'static;
|
||||
|
||||
impl<T> #ServiceServer<::std::sync::Weak<T>>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
::std::sync::Arc<T>: #Service,
|
||||
{
|
||||
pub fn new_arc(service: ::std::sync::Arc<T>) -> #ServiceServer<::std::sync::Weak<T>> {
|
||||
#ServiceServer(::std::sync::Arc::downgrade(&service))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> #ServiceServer<A> where A: #Service + Clone + Send + 'static {
|
||||
/// Creates a new server instance that dispatches all calls to the supplied service.
|
||||
pub fn new(service: A) -> #ServiceServer<A> {
|
||||
#ServiceServer(service)
|
||||
}
|
||||
|
||||
async fn call_inner(
|
||||
service: A,
|
||||
method: #ServiceMethodDescriptor,
|
||||
ctrl: A::Controller,
|
||||
input: ::bytes::Bytes)
|
||||
-> #namespace::error::Result<::bytes::Bytes> {
|
||||
match method {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<A> #namespace::handler::Handler for #ServiceServer<A>
|
||||
where
|
||||
A: #Service + Clone + Send + Sync + 'static {
|
||||
type Descriptor = #ServiceDescriptor;
|
||||
type Controller = A::Controller;
|
||||
|
||||
async fn call(
|
||||
&self,
|
||||
ctrl: A::Controller,
|
||||
method: #ServiceMethodDescriptor,
|
||||
input: ::bytes::Bytes)
|
||||
-> #namespace::error::Result<::bytes::Bytes> {
|
||||
#ServiceServer::call_inner(self.0.clone(), method, ctrl, input).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The service generator to be used with `prost-build` to generate RPC implementations for
|
||||
/// `prost-simple-rpc`.
|
||||
///
|
||||
/// See the crate-level documentation for more info.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ServiceGenerator;
|
||||
|
||||
impl prost_build::ServiceGenerator for ServiceGenerator {
|
||||
fn generate(&mut self, service: prost_build::Service, buf: &mut String) {
|
||||
let info = Service::new(service);
|
||||
|
||||
let trait_Service = info.trait_Service();
|
||||
let impl_Service_for_Weak = info.impl_Service_for_Weak();
|
||||
let struct_ServiceDescriptor = info.struct_ServiceDescriptor();
|
||||
let enum_ServiceMethodDescriptor = info.enum_ServiceMethodDescriptor();
|
||||
let struct_ServiceClient = info.struct_ServiceClient();
|
||||
let struct_ServiceClientFactory = info.struct_ServiceClientFactory();
|
||||
let struct_ServiceServer = info.struct_ServiceServer();
|
||||
|
||||
let tokens = quote! {
|
||||
#trait_Service
|
||||
|
||||
#impl_Service_for_Weak
|
||||
|
||||
#struct_ServiceDescriptor
|
||||
|
||||
#enum_ServiceMethodDescriptor
|
||||
|
||||
#struct_ServiceClient
|
||||
|
||||
#struct_ServiceClientFactory
|
||||
|
||||
#struct_ServiceServer
|
||||
};
|
||||
|
||||
buf.push('\n');
|
||||
buf.push_str(&tokens.to_string());
|
||||
buf.push('\n');
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,12 @@ core_clap:
|
||||
en: |+
|
||||
config server address, allow format:
|
||||
full url: --config-server udp://127.0.0.1:22020/admin, 'udp' can be replaced with tcp, ws, wss (when config server ws is proxied to wss)
|
||||
short link: --config-server https://example.com/easytier/admin, the HTTP(S) response should redirect to the full config server URL
|
||||
only user name: --config-server admin, will use official server
|
||||
zh-CN: |+
|
||||
配置服务器地址。允许格式:
|
||||
完整URL:--config-server udp://127.0.0.1:22020/admin,udp可以根据配置服务器替换为 tcp,ws,wss(配置服务器ws被代理为wss时)
|
||||
短链接:--config-server https://example.com/easytier/admin,HTTP(S) 响应应重定向到完整配置服务器 URL
|
||||
仅用户名:--config-server admin,将使用官方的服务器
|
||||
machine_id:
|
||||
en: |+
|
||||
|
||||
@@ -78,6 +78,22 @@ impl TunnelConnector for ConfigServerConnector {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_config_server_input(config_server_url_s: &str) -> Result<Url> {
|
||||
match Url::parse(config_server_url_s) {
|
||||
Ok(u) => Ok(u),
|
||||
Err(_) => format!(
|
||||
"udp://config-server.easytier.cn:22020/{}",
|
||||
config_server_url_s
|
||||
)
|
||||
.parse()
|
||||
.with_context(|| "failed to parse config server URL"),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_config_server_http_short_link(url: &Url) -> bool {
|
||||
matches!(url.scheme(), "http" | "https")
|
||||
}
|
||||
|
||||
impl WebClient {
|
||||
pub fn new<T: TunnelConnector + 'static, S: ToString, H: ToString>(
|
||||
connector: T,
|
||||
@@ -240,15 +256,7 @@ pub async fn run_web_client(
|
||||
) -> Result<WebClient> {
|
||||
let machine_id = resolve_machine_id(&machine_id_opts)
|
||||
.with_context(|| "failed to resolve machine id for web client")?;
|
||||
let config_server_url = match Url::parse(config_server_url_s) {
|
||||
Ok(u) => u,
|
||||
Err(_) => format!(
|
||||
"udp://config-server.easytier.cn:22020/{}",
|
||||
config_server_url_s
|
||||
)
|
||||
.parse()
|
||||
.with_context(|| "failed to parse config server URL")?,
|
||||
};
|
||||
let config_server_url = parse_config_server_input(config_server_url_s)?;
|
||||
|
||||
TunnelScheme::try_from(&config_server_url).map_err(|_| {
|
||||
anyhow::anyhow!(
|
||||
@@ -258,7 +266,8 @@ pub async fn run_web_client(
|
||||
})?;
|
||||
|
||||
let mut c_url = config_server_url.clone();
|
||||
if !matches!(c_url.scheme(), "ws" | "wss") {
|
||||
// Keep HTTP(S) paths so HttpTunnelConnector can request short links and handle their redirects.
|
||||
if !matches!(c_url.scheme(), "ws" | "wss") && !is_config_server_http_short_link(&c_url) {
|
||||
c_url.set_path("");
|
||||
}
|
||||
let token = config_server_url
|
||||
@@ -305,7 +314,15 @@ pub async fn run_web_client(
|
||||
mod tests {
|
||||
use std::sync::{Arc, atomic::AtomicBool};
|
||||
|
||||
use crate::{common::MachineIdOptions, instance_manager::NetworkInstanceManager};
|
||||
use crate::{
|
||||
common::{MachineIdOptions, config::TomlConfigLoader, global_ctx::GlobalCtx},
|
||||
instance_manager::NetworkInstanceManager,
|
||||
tunnel::TunnelConnector,
|
||||
};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt as _, AsyncWriteExt as _},
|
||||
net::TcpListener,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_manager_wait() {
|
||||
@@ -363,4 +380,47 @@ mod tests {
|
||||
assert!(!client.is_connected());
|
||||
drop(client);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn config_server_short_link_uses_existing_http_redirect_connector() {
|
||||
let target_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let target_addr = target_listener.local_addr().unwrap();
|
||||
let target_task = tokio::spawn(async move {
|
||||
let _ = target_listener.accept().await.unwrap();
|
||||
});
|
||||
|
||||
let http_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let http_addr = http_listener.local_addr().unwrap();
|
||||
let http_task = tokio::spawn(async move {
|
||||
let (mut stream, _) = http_listener.accept().await.unwrap();
|
||||
let mut buf = [0u8; 4096];
|
||||
let n = stream.read(&mut buf).await.unwrap();
|
||||
let req = String::from_utf8_lossy(&buf[..n]).to_string();
|
||||
let resp = format!(
|
||||
"HTTP/1.1 302 Found\r\nLocation: tcp://{}\r\nContent-Length: 0\r\n\r\n",
|
||||
target_addr
|
||||
);
|
||||
stream.write_all(resp.as_bytes()).await.unwrap();
|
||||
req
|
||||
});
|
||||
|
||||
let config = TomlConfigLoader::default();
|
||||
let global_ctx = Arc::new(GlobalCtx::new(config));
|
||||
let mut flags = global_ctx.get_flags();
|
||||
flags.bind_device = false;
|
||||
global_ctx.set_flags(flags);
|
||||
let url: url::Url = format!("http://{}/short-token", http_addr).parse().unwrap();
|
||||
let mut connector = super::ConfigServerConnector { url, global_ctx };
|
||||
|
||||
let tunnel = connector.connect().await.unwrap();
|
||||
|
||||
let req = http_task.await.unwrap();
|
||||
assert!(req.starts_with("GET /short-token "));
|
||||
let info = tunnel.info().unwrap();
|
||||
assert_eq!(
|
||||
info.resolved_remote_addr.unwrap().url,
|
||||
format!("tcp://{}", target_addr)
|
||||
);
|
||||
target_task.await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user