Implemented rudimentary RPC
This commit is contained in:
parent
69c126d860
commit
9c42b20fa9
|
@ -1144,8 +1144,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1769,6 +1771,7 @@ dependencies = [
|
||||||
"async-stream",
|
"async-stream",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-extra",
|
"axum-extra",
|
||||||
|
"bytes",
|
||||||
"cached",
|
"cached",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -1779,7 +1782,6 @@ dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"headers",
|
"headers",
|
||||||
"hyper",
|
"hyper",
|
||||||
"idna 1.0.2",
|
|
||||||
"itertools",
|
"itertools",
|
||||||
"lru",
|
"lru",
|
||||||
"magnetar_common",
|
"magnetar_common",
|
||||||
|
@ -1788,12 +1790,13 @@ dependencies = [
|
||||||
"magnetar_host_meta",
|
"magnetar_host_meta",
|
||||||
"magnetar_model",
|
"magnetar_model",
|
||||||
"magnetar_nodeinfo",
|
"magnetar_nodeinfo",
|
||||||
|
"magnetar_runtime",
|
||||||
"magnetar_sdk",
|
"magnetar_sdk",
|
||||||
"magnetar_webfinger",
|
"magnetar_webfinger",
|
||||||
"miette",
|
"miette",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"regex",
|
"rmp-serde",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
|
@ -1806,6 +1809,7 @@ dependencies = [
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"ulid",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
@ -1929,6 +1933,7 @@ dependencies = [
|
||||||
"nom_locate",
|
"nom_locate",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"serde",
|
"serde",
|
||||||
|
"smallvec",
|
||||||
"strum",
|
"strum",
|
||||||
"tracing",
|
"tracing",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
@ -1966,6 +1971,22 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "magnetar_runtime"
|
||||||
|
version = "0.3.0-alpha"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"itertools",
|
||||||
|
"magnetar_core",
|
||||||
|
"magnetar_sdk",
|
||||||
|
"miette",
|
||||||
|
"thiserror",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "magnetar_sdk"
|
name = "magnetar_sdk"
|
||||||
version = "0.3.0-alpha"
|
version = "0.3.0-alpha"
|
||||||
|
@ -2822,6 +2843,28 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rmp"
|
||||||
|
version = "0.8.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"num-traits",
|
||||||
|
"paste",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rmp-serde"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"rmp",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsa"
|
name = "rsa"
|
||||||
version = "0.9.6"
|
version = "0.9.6"
|
||||||
|
@ -4215,6 +4258,17 @@ version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
|
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ulid"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04f903f293d11f31c0c29e4148f6dc0d033a7f80cebc0282bea147611667d289"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"rand",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unic-char-property"
|
name = "unic-char-property"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
|
@ -15,6 +15,7 @@ members = [
|
||||||
"ext_model",
|
"ext_model",
|
||||||
"fe_calckey",
|
"fe_calckey",
|
||||||
"magnetar_common",
|
"magnetar_common",
|
||||||
|
"magnetar_runtime",
|
||||||
"magnetar_sdk",
|
"magnetar_sdk",
|
||||||
"magnetar_mmm_parser",
|
"magnetar_mmm_parser",
|
||||||
"core",
|
"core",
|
||||||
|
@ -30,6 +31,7 @@ async-stream = "0.3"
|
||||||
axum = "0.7"
|
axum = "0.7"
|
||||||
axum-extra = "0.9"
|
axum-extra = "0.9"
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
|
bytes = "1.7"
|
||||||
cached = "0.53"
|
cached = "0.53"
|
||||||
cfg-if = "1"
|
cfg-if = "1"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
@ -58,6 +60,7 @@ priority-queue = "2.0"
|
||||||
quick-xml = "0.36"
|
quick-xml = "0.36"
|
||||||
redis = "0.26"
|
redis = "0.26"
|
||||||
regex = "1.9"
|
regex = "1.9"
|
||||||
|
rmp-serde = "1.3"
|
||||||
rsa = "0.9"
|
rsa = "0.9"
|
||||||
reqwest = "0.12"
|
reqwest = "0.12"
|
||||||
sea-orm = "1"
|
sea-orm = "1"
|
||||||
|
@ -92,6 +95,7 @@ magnetar_host_meta = { path = "./ext_host_meta" }
|
||||||
magnetar_webfinger = { path = "./ext_webfinger" }
|
magnetar_webfinger = { path = "./ext_webfinger" }
|
||||||
magnetar_nodeinfo = { path = "./ext_nodeinfo" }
|
magnetar_nodeinfo = { path = "./ext_nodeinfo" }
|
||||||
magnetar_model = { path = "./ext_model" }
|
magnetar_model = { path = "./ext_model" }
|
||||||
|
magnetar_runtime = { path = "./magnetar_runtime" }
|
||||||
magnetar_sdk = { path = "./magnetar_sdk" }
|
magnetar_sdk = { path = "./magnetar_sdk" }
|
||||||
|
|
||||||
cached = { workspace = true }
|
cached = { workspace = true }
|
||||||
|
@ -111,9 +115,6 @@ tower = { workspace = true }
|
||||||
tower-http = { workspace = true, features = ["cors", "trace", "fs"] }
|
tower-http = { workspace = true, features = ["cors", "trace", "fs"] }
|
||||||
ulid = { workspace = true }
|
ulid = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
idna = { workspace = true }
|
|
||||||
|
|
||||||
regex = { workspace = true }
|
|
||||||
|
|
||||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
@ -132,6 +133,7 @@ thiserror = { workspace = true }
|
||||||
|
|
||||||
percent-encoding = { workspace = true }
|
percent-encoding = { workspace = true }
|
||||||
|
|
||||||
|
rmp-serde = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
serde_urlencoded = { workspace = true }
|
serde_urlencoded = { workspace = true }
|
||||||
|
|
|
@ -59,6 +59,20 @@
|
||||||
# Environment variable: MAG_C_PROXY_REMOTE_FILES
|
# Environment variable: MAG_C_PROXY_REMOTE_FILES
|
||||||
# networking.proxy_remote_files = false
|
# networking.proxy_remote_files = false
|
||||||
|
|
||||||
|
# ------------------------------[ RPC CONNECTION ]-----------------------------
|
||||||
|
|
||||||
|
# [Optional]
|
||||||
|
# A type of connection to use for the application's internal RPC
|
||||||
|
# Possible values: "none", "tcp", "unix"
|
||||||
|
# Default: "none"
|
||||||
|
# Environment variable: MAG_C_RPC_CONNECTION_TYPE
|
||||||
|
# rpc.connection_type = "none"
|
||||||
|
|
||||||
|
# [Optional]
|
||||||
|
# The corresponding bind address (or path for Unix-domain sockets) for the internal RPC
|
||||||
|
# Default: ""
|
||||||
|
# Environment variable: MAG_C_RPC_BIND_ADDR
|
||||||
|
# rpc.bind_addr = ""
|
||||||
|
|
||||||
# -----------------------------[ CALCKEY FRONTEND ]----------------------------
|
# -----------------------------[ CALCKEY FRONTEND ]----------------------------
|
||||||
|
|
||||||
|
@ -83,7 +97,6 @@
|
||||||
# -------------------------------[ FEDERATION ]--------------------------------
|
# -------------------------------[ FEDERATION ]--------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------[ BRANDING ]---------------------------------
|
# --------------------------------[ BRANDING ]---------------------------------
|
||||||
|
|
||||||
# [Optional]
|
# [Optional]
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::net::IpAddr;
|
use std::net::{IpAddr, SocketAddr};
|
||||||
|
use std::path::PathBuf;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
|
@ -94,6 +95,56 @@ impl Default for MagnetarNetworking {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Default, Clone)]
|
||||||
|
#[serde(
|
||||||
|
rename_all = "snake_case",
|
||||||
|
tag = "connection_type",
|
||||||
|
content = "bind_addr"
|
||||||
|
)]
|
||||||
|
pub enum MagnetarRpcSocketKind {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
Unix(PathBuf),
|
||||||
|
Tcp(SocketAddr),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct MagnetarRpcConfig {
|
||||||
|
pub connection_settings: MagnetarRpcSocketKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn env_rpc_connection() -> MagnetarRpcSocketKind {
|
||||||
|
match std::env::var("MAG_C_RPC_CONNECTION_TYPE")
|
||||||
|
.unwrap_or_else(|_| "none".to_owned())
|
||||||
|
.to_lowercase()
|
||||||
|
.as_str()
|
||||||
|
{
|
||||||
|
"none" => MagnetarRpcSocketKind::None,
|
||||||
|
"unix" => MagnetarRpcSocketKind::Unix(
|
||||||
|
std::env::var("MAG_C_RPC_BIND_ADDR")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.parse()
|
||||||
|
.expect("MAG_C_RPC_BIND_ADDR must be a valid path"),
|
||||||
|
),
|
||||||
|
"tcp" => MagnetarRpcSocketKind::Tcp(
|
||||||
|
std::env::var("MAG_C_RPC_BIND_ADDR")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.parse()
|
||||||
|
.expect("MAG_C_RPC_BIND_ADDR must be a valid socket address"),
|
||||||
|
),
|
||||||
|
_ => panic!("MAG_C_RPC_CONNECTION_TYPE must be a valid protocol or 'none'"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MagnetarRpcConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
MagnetarRpcConfig {
|
||||||
|
connection_settings: env_rpc_connection(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct MagnetarCalckeyFrontendConfig {
|
pub struct MagnetarCalckeyFrontendConfig {
|
||||||
|
@ -196,6 +247,8 @@ pub struct MagnetarConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub networking: MagnetarNetworking,
|
pub networking: MagnetarNetworking,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub rpc: MagnetarRpcConfig,
|
||||||
|
#[serde(default)]
|
||||||
pub branding: MagnetarBranding,
|
pub branding: MagnetarBranding,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub calckey_frontend: MagnetarCalckeyFrontendConfig,
|
pub calckey_frontend: MagnetarCalckeyFrontendConfig,
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "magnetar_runtime"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
magnetar_core = { path = "../core" }
|
||||||
|
magnetar_sdk = { path = "../magnetar_sdk" }
|
||||||
|
|
||||||
|
either = { workspace = true }
|
||||||
|
futures-channel = { workspace = true }
|
||||||
|
futures-util = { workspace = true }
|
||||||
|
futures-core = { workspace = true }
|
||||||
|
itertools = { workspace = true }
|
||||||
|
miette = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
|
tracing = { workspace = true }
|
|
@ -0,0 +1 @@
|
||||||
|
|
52
src/main.rs
52
src/main.rs
|
@ -2,6 +2,7 @@ mod api_v1;
|
||||||
pub mod host_meta;
|
pub mod host_meta;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod nodeinfo;
|
pub mod nodeinfo;
|
||||||
|
mod rpc_v1;
|
||||||
pub mod service;
|
pub mod service;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod web;
|
pub mod web;
|
||||||
|
@ -14,16 +15,21 @@ use crate::service::MagnetarService;
|
||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use dotenvy::dotenv;
|
use dotenvy::dotenv;
|
||||||
|
use futures::{select, FutureExt};
|
||||||
|
use magnetar_common::config::{MagnetarConfig, MagnetarRpcSocketKind};
|
||||||
use magnetar_model::{CacheConnectorConfig, CalckeyCache, CalckeyModel, ConnectorConfig};
|
use magnetar_model::{CacheConnectorConfig, CalckeyCache, CalckeyModel, ConnectorConfig};
|
||||||
use miette::{miette, IntoDiagnostic};
|
use miette::{miette, IntoDiagnostic};
|
||||||
|
use rpc_v1::proto::{MagRpc, RpcMessage, RpcSockAddr};
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use std::future::Future;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
use tower_http::cors::{Any, CorsLayer};
|
use tower_http::cors::{Any, CorsLayer};
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
use tracing::info;
|
|
||||||
use tracing::log::error;
|
use tracing::log::error;
|
||||||
|
use tracing::{debug, info};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -63,10 +69,48 @@ async fn main() -> miette::Result<()> {
|
||||||
.into_diagnostic()?,
|
.into_diagnostic()?,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let shutdown_signal = shutdown_signal().shared();
|
||||||
|
|
||||||
|
select! {
|
||||||
|
rpc_res = run_rpc(service.clone(), config, shutdown_signal.clone()).fuse() => rpc_res,
|
||||||
|
web_res = run_web(service, config, shutdown_signal).fuse() => web_res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_rpc(
|
||||||
|
service: Arc<MagnetarService>,
|
||||||
|
config: &'static MagnetarConfig,
|
||||||
|
shutdown_signal: impl Future<Output=()> + Send + 'static,
|
||||||
|
) -> miette::Result<()> {
|
||||||
|
let rpc_bind_addr = match &config.rpc.connection_settings {
|
||||||
|
MagnetarRpcSocketKind::None => {
|
||||||
|
std::future::pending::<Infallible>().await;
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
MagnetarRpcSocketKind::Unix(path) => RpcSockAddr::Unix(path.clone()),
|
||||||
|
MagnetarRpcSocketKind::Tcp(ip) => RpcSockAddr::Ip(*ip),
|
||||||
|
};
|
||||||
|
|
||||||
|
let rpc = MagRpc::new().handle(
|
||||||
|
"/ping",
|
||||||
|
|_, RpcMessage(message): RpcMessage<String>| async move {
|
||||||
|
debug!("Received RPC ping: {}", message);
|
||||||
|
RpcMessage("pong".to_owned())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
rpc.run(service, rpc_bind_addr, Some(shutdown_signal)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_web(
|
||||||
|
service: Arc<MagnetarService>,
|
||||||
|
config: &'static MagnetarConfig,
|
||||||
|
shutdown_signal: impl Future<Output=()> + Send + 'static,
|
||||||
|
) -> miette::Result<()> {
|
||||||
let well_known_router = Router::new()
|
let well_known_router = Router::new()
|
||||||
.route(
|
.route(
|
||||||
"/webfinger",
|
"/webfinger",
|
||||||
get(webfinger::handle_webfinger).with_state((config, db)),
|
get(webfinger::handle_webfinger).with_state((config, service.db.clone())),
|
||||||
)
|
)
|
||||||
.route("/host-meta", get(handle_host_meta))
|
.route("/host-meta", get(handle_host_meta))
|
||||||
.route("/nodeinfo", get(handle_nodeinfo));
|
.route("/nodeinfo", get(handle_nodeinfo));
|
||||||
|
@ -93,7 +137,7 @@ async fn main() -> miette::Result<()> {
|
||||||
let listener = TcpListener::bind(addr).await.into_diagnostic()?;
|
let listener = TcpListener::bind(addr).await.into_diagnostic()?;
|
||||||
info!("Serving...");
|
info!("Serving...");
|
||||||
axum::serve(listener, app.into_make_service())
|
axum::serve(listener, app.into_make_service())
|
||||||
.with_graceful_shutdown(shutdown_signal())
|
.with_graceful_shutdown(shutdown_signal)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| miette!("Error running server: {}", e))
|
.map_err(|e| miette!("Error running server: {}", e))
|
||||||
}
|
}
|
||||||
|
@ -121,5 +165,5 @@ async fn shutdown_signal() {
|
||||||
_ = terminate => {},
|
_ = terminate => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Shutting down...");
|
info!("Received a signal to shut down...");
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod proto;
|
|
@ -0,0 +1,399 @@
|
||||||
|
use crate::service::MagnetarService;
|
||||||
|
use bytes::BufMut;
|
||||||
|
use futures::{FutureExt, Stream, StreamExt};
|
||||||
|
use miette::{miette, IntoDiagnostic};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::any::Any;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::io::{AsyncRead, AsyncReadExt, BufReader, ReadBuf};
|
||||||
|
use tokio::net::{TcpListener, UnixSocket};
|
||||||
|
use tokio::select;
|
||||||
|
use tokio::task::JoinSet;
|
||||||
|
use tracing::{debug, error, Instrument};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum RpcSockAddr {
|
||||||
|
Ip(SocketAddr),
|
||||||
|
Unix(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct RpcMessage<T>(pub T);
|
||||||
|
|
||||||
|
pub trait IntoRpcResponse: Send {
|
||||||
|
fn into_rpc_response(self) -> Option<RpcResponse>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RpcResponse(Vec<u8>);
|
||||||
|
|
||||||
|
impl<T: Serialize + Send + 'static> IntoRpcResponse for RpcMessage<T> {
|
||||||
|
fn into_rpc_response(self) -> Option<RpcResponse> {
|
||||||
|
rmp_serde::to_vec(&self)
|
||||||
|
.inspect_err(|e| {
|
||||||
|
error!(
|
||||||
|
"Failed to serialize value of type {}: {}",
|
||||||
|
std::any::type_name::<T>(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
.map(RpcResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct RpcResult<T> {
|
||||||
|
success: bool,
|
||||||
|
data: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Serialize + Send + 'static, E: Serialize + Send + 'static> IntoRpcResponse
|
||||||
|
for Result<T, E>
|
||||||
|
{
|
||||||
|
fn into_rpc_response(self) -> Option<RpcResponse> {
|
||||||
|
match self {
|
||||||
|
Ok(data) => RpcMessage(RpcResult {
|
||||||
|
success: true,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
.into_rpc_response(),
|
||||||
|
Err(data) => RpcMessage(RpcResult {
|
||||||
|
success: false,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
.into_rpc_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RpcHandler<T>: Send + Sync + 'static
|
||||||
|
where
|
||||||
|
T: Send + 'static,
|
||||||
|
{
|
||||||
|
fn process(
|
||||||
|
&self,
|
||||||
|
context: Arc<MagnetarService>,
|
||||||
|
message: RpcMessage<T>,
|
||||||
|
) -> impl Future<Output=Option<RpcResponse>> + Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, F, Fut, RR> RpcHandler<T> for F
|
||||||
|
where
|
||||||
|
T: Send + 'static,
|
||||||
|
F: Fn(Arc<MagnetarService>, RpcMessage<T>) -> Fut + Send + Sync + 'static,
|
||||||
|
Fut: Future<Output=RR> + Send,
|
||||||
|
RR: IntoRpcResponse,
|
||||||
|
{
|
||||||
|
async fn process(
|
||||||
|
&self,
|
||||||
|
context: Arc<MagnetarService>,
|
||||||
|
message: RpcMessage<T>,
|
||||||
|
) -> Option<RpcResponse> {
|
||||||
|
self(context, message).await.into_rpc_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MagRpcHandlerMapped = dyn Fn(
|
||||||
|
Arc<MagnetarService>,
|
||||||
|
Box<dyn Any + Send + 'static>,
|
||||||
|
) -> Pin<Box<dyn Future<Output=Option<RpcResponse>> + Send + 'static>>
|
||||||
|
+ Send
|
||||||
|
+ Sync;
|
||||||
|
|
||||||
|
type MagRpcDecoderMapped =
|
||||||
|
dyn Fn(&[u8]) -> Result<Box<dyn Any + Send + 'static>, rmp_serde::decode::Error> + Send + Sync;
|
||||||
|
|
||||||
|
pub struct MagRpc {
|
||||||
|
listeners: HashMap<String, Box<MagRpcHandlerMapped>>,
|
||||||
|
payload_decoders: HashMap<String, Box<MagRpcDecoderMapped>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MagRpc {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
MagRpc {
|
||||||
|
listeners: HashMap::new(),
|
||||||
|
payload_decoders: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle<H, T>(mut self, method: impl Into<String>, handler: H) -> Self
|
||||||
|
where
|
||||||
|
T: Send + 'static,
|
||||||
|
H: RpcHandler<T> + Sync + 'static,
|
||||||
|
{
|
||||||
|
let handler_ref = Arc::new(handler);
|
||||||
|
self.listeners.insert(
|
||||||
|
method.into(),
|
||||||
|
Box::new(move |ctx, data| {
|
||||||
|
let handler = handler_ref.clone();
|
||||||
|
async move {
|
||||||
|
handler
|
||||||
|
.process(ctx, RpcMessage(*data.downcast().unwrap()))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(
|
||||||
|
self,
|
||||||
|
context: Arc<MagnetarService>,
|
||||||
|
addr: RpcSockAddr,
|
||||||
|
graceful_shutdown: Option<impl Future<Output=()>>,
|
||||||
|
) -> miette::Result<()> {
|
||||||
|
match addr {
|
||||||
|
RpcSockAddr::Ip(sock_addr) => {
|
||||||
|
self.run_tcp(context, &sock_addr, graceful_shutdown).await
|
||||||
|
}
|
||||||
|
RpcSockAddr::Unix(path) => self.run_unix(context, &path, graceful_shutdown).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_tcp(
|
||||||
|
self,
|
||||||
|
context: Arc<MagnetarService>,
|
||||||
|
sock_addr: &SocketAddr,
|
||||||
|
graceful_shutdown: Option<impl Future<Output=()>>,
|
||||||
|
) -> miette::Result<()> {
|
||||||
|
debug!("Binding RPC socket to {}", sock_addr);
|
||||||
|
let listener = TcpListener::bind(sock_addr).await.into_diagnostic()?;
|
||||||
|
|
||||||
|
let (sender, mut cancel) = tokio::sync::oneshot::channel::<()>();
|
||||||
|
let mut cancellation_tokens = Vec::new();
|
||||||
|
|
||||||
|
let rx_dec = RpcCallDecoder {
|
||||||
|
listeners: Arc::new(self.listeners),
|
||||||
|
payload_decoders: Arc::new(self.payload_decoders),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut connections = JoinSet::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (stream, sock_addr) = select!(
|
||||||
|
_ = connections.join_next() => continue,
|
||||||
|
conn = listener.accept() => {
|
||||||
|
if let Err(e) = conn {
|
||||||
|
error!("Connection error: {}", e);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.unwrap()
|
||||||
|
},
|
||||||
|
_ = &mut cancel => break
|
||||||
|
);
|
||||||
|
|
||||||
|
debug!("RPC TCP connection accepted: {:?}", sock_addr);
|
||||||
|
|
||||||
|
let (cancel_send, cancel_recv) = tokio::sync::oneshot::channel::<()>();
|
||||||
|
let buf_read = BufReader::new(stream);
|
||||||
|
let context = context.clone();
|
||||||
|
let rx_dec = rx_dec.clone();
|
||||||
|
let fut = async move {
|
||||||
|
rx_dec
|
||||||
|
.stream_decode(buf_read, cancel_recv)
|
||||||
|
.filter_map(|r| async move {
|
||||||
|
if let Err(e) = &r {
|
||||||
|
error!("Stream decoding error: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
r.ok()
|
||||||
|
})
|
||||||
|
.for_each_concurrent(Some(32), |(payload, listener)| async {
|
||||||
|
if let Some(response) = listener(context.clone(), payload).await {
|
||||||
|
// TODO: Respond
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
miette::Result::<()>::Ok(())
|
||||||
|
}
|
||||||
|
.instrument(tracing::info_span!("RPC", sock_addr = ?sock_addr));
|
||||||
|
|
||||||
|
connections.spawn_local(fut);
|
||||||
|
|
||||||
|
cancellation_tokens.push(cancel_send);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(graceful_shutdown) = graceful_shutdown {
|
||||||
|
graceful_shutdown.await;
|
||||||
|
sender.send(()).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
connections.join_all().await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_unix(
|
||||||
|
self,
|
||||||
|
context: Arc<MagnetarService>,
|
||||||
|
addr: &Path,
|
||||||
|
graceful_shutdown: Option<impl Future<Output=()>>,
|
||||||
|
) -> miette::Result<()> {
|
||||||
|
let sock = UnixSocket::new_stream().into_diagnostic()?;
|
||||||
|
sock.bind(addr).into_diagnostic()?;
|
||||||
|
let listener = sock.listen(16).into_diagnostic()?;
|
||||||
|
|
||||||
|
let (sender, mut cancel) = tokio::sync::oneshot::channel::<()>();
|
||||||
|
let mut cancellation_tokens = Vec::new();
|
||||||
|
|
||||||
|
let rx_dec = RpcCallDecoder {
|
||||||
|
listeners: Arc::new(self.listeners),
|
||||||
|
payload_decoders: Arc::new(self.payload_decoders),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut connections = JoinSet::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (stream, sock_addr) = select!(
|
||||||
|
_ = connections.join_next() => continue,
|
||||||
|
conn = listener.accept() => {
|
||||||
|
if let Err(e) = conn {
|
||||||
|
error!("Connection error: {}", e);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.unwrap()
|
||||||
|
},
|
||||||
|
_ = &mut cancel => break
|
||||||
|
);
|
||||||
|
|
||||||
|
debug!("RPC Unix connection accepted: {:?}", sock_addr);
|
||||||
|
|
||||||
|
let (cancel_send, cancel_recv) = tokio::sync::oneshot::channel::<()>();
|
||||||
|
let buf_read = BufReader::new(stream);
|
||||||
|
let context = context.clone();
|
||||||
|
let rx_dec = rx_dec.clone();
|
||||||
|
let fut = async move {
|
||||||
|
rx_dec
|
||||||
|
.stream_decode(buf_read, cancel_recv)
|
||||||
|
.filter_map(|r| async move {
|
||||||
|
if let Err(e) = &r {
|
||||||
|
error!("Stream decoding error: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
r.ok()
|
||||||
|
})
|
||||||
|
.for_each_concurrent(Some(32), |(payload, listener)| async {
|
||||||
|
if let Some(response) = listener(context.clone(), payload).await {
|
||||||
|
// TODO: Respond
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
miette::Result::<()>::Ok(())
|
||||||
|
}
|
||||||
|
.instrument(tracing::info_span!("RPC", sock_addr = ?sock_addr));
|
||||||
|
|
||||||
|
connections.spawn_local(fut);
|
||||||
|
|
||||||
|
cancellation_tokens.push(cancel_send);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(graceful_shutdown) = graceful_shutdown {
|
||||||
|
graceful_shutdown.await;
|
||||||
|
sender.send(()).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
connections.join_all().await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct RpcCallDecoder {
|
||||||
|
listeners: Arc<HashMap<String, Box<MagRpcHandlerMapped>>>,
|
||||||
|
payload_decoders: Arc<HashMap<String, Box<MagRpcDecoderMapped>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpcCallDecoder {
|
||||||
|
fn stream_decode<R: AsyncRead + AsyncReadExt + Unpin + Send>(
|
||||||
|
&self,
|
||||||
|
mut buf_read: BufReader<R>,
|
||||||
|
mut cancel: tokio::sync::oneshot::Receiver<()>,
|
||||||
|
) -> impl Stream<Item=miette::Result<(Box<dyn Any + Send + 'static>, &MagRpcHandlerMapped)>> + Send
|
||||||
|
{
|
||||||
|
async_stream::try_stream! {
|
||||||
|
let mut name_buf = Vec::new();
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let read_fut = async {
|
||||||
|
let name_len = buf_read.read_u32().await.into_diagnostic()? as usize;
|
||||||
|
|
||||||
|
if name_len > name_buf.capacity() {
|
||||||
|
name_buf.reserve(name_len - name_buf.capacity());
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: We use ReadBuf which expects uninit anyway
|
||||||
|
unsafe {
|
||||||
|
name_buf.set_len(name_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut name_buf_write = ReadBuf::uninit(&mut name_buf);
|
||||||
|
while name_buf_write.has_remaining_mut() {
|
||||||
|
buf_read
|
||||||
|
.read_buf(&mut name_buf_write)
|
||||||
|
.await
|
||||||
|
.into_diagnostic()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload_len = buf_read.read_u32().await.into_diagnostic()? as usize;
|
||||||
|
if payload_len > buf.capacity() {
|
||||||
|
buf.reserve(payload_len - buf.capacity());
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: We use ReadBuf which expects uninit anyway
|
||||||
|
unsafe {
|
||||||
|
buf.set_len(payload_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf_write = ReadBuf::uninit(&mut buf);
|
||||||
|
while buf_write.has_remaining_mut() {
|
||||||
|
buf_read.read_buf(&mut buf_write).await.into_diagnostic()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
miette::Result::<_>::Ok((name_buf_write, buf_write))
|
||||||
|
};
|
||||||
|
|
||||||
|
let (name_buf_write, payload) = select! {
|
||||||
|
read_result = read_fut => read_result,
|
||||||
|
_ = &mut cancel => { break; }
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let name = std::str::from_utf8(name_buf_write.filled()).into_diagnostic()?;
|
||||||
|
|
||||||
|
let decoder = self
|
||||||
|
.payload_decoders
|
||||||
|
.get(name)
|
||||||
|
.ok_or_else(|| miette!("No such RPC call name: {}", name))?
|
||||||
|
.as_ref();
|
||||||
|
let listener = self
|
||||||
|
.listeners
|
||||||
|
.get(name)
|
||||||
|
.ok_or_else(|| miette!("No such RPC call name: {}", name))?
|
||||||
|
.as_ref();
|
||||||
|
|
||||||
|
let packet = match decoder(payload.filled()) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to parse packet: {e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
yield (packet, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue