Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
a1e10f93ce
|
|||
|
2cf0b9abe4
|
|||
|
8b0f01e606
|
|||
|
beda8c151d
|
|||
|
84f7009ad2
|
|||
|
267b741ac4
|
|||
|
bffb41e8a1
|
|||
|
b5870e62fe
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -153,7 +153,7 @@ checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
||||
|
||||
[[package]]
|
||||
name = "eagle"
|
||||
version = "0.2.4"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"log",
|
||||
|
||||
20
Cargo.toml
20
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "eagle"
|
||||
version = "0.2.4"
|
||||
version = "0.3.0"
|
||||
description = "A simple library for creating RPC protocols."
|
||||
repository = "https://git.colon-three.com/kodi/eagle"
|
||||
authors = ["KodiCraft <kodi@kdcf.me>"]
|
||||
@@ -10,26 +10,24 @@ resolver = "2"
|
||||
|
||||
[features]
|
||||
default = ["tcp", "log"]
|
||||
tcp = ["tokio/net"]
|
||||
unix = ["tokio/net"]
|
||||
log = ["dep:log", "dep:env_logger"]
|
||||
tcp = []
|
||||
unix = []
|
||||
log = []
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.85"
|
||||
quote = "1.0.36"
|
||||
ron = "0.8.1"
|
||||
serde = { version = "1.0.203", features = ["serde_derive"] }
|
||||
syn = "2.0.66"
|
||||
tokio = { version = "1.38.0", features = ["sync", "io-util"] }
|
||||
env_logger = { version = "0.11.3", optional = true }
|
||||
log = { version = "0.4.21", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# tokio = { version = "1.38.0", features = ["sync", "io-util"] }
|
||||
ron = "0.8.1"
|
||||
serde = { version = "1.0.203", features = ["serde_derive"] }
|
||||
rand = "0.8.5"
|
||||
tokio = { version = "1.38.0", features = ["sync", "rt-multi-thread", "macros", "time", "io-util", "net"] }
|
||||
env_logger = "0.11.3"
|
||||
log = "0.4.21"
|
||||
tokio-test = "0.4.4"
|
||||
env_logger = { version = "0.11.3" }
|
||||
log = { version = "0.4.21" }
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
@@ -9,6 +9,8 @@ Eagle is still in early development. Performance is not ideal, the interface is
|
||||
Eagle is a library which allows you to easily build an [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) protocol.
|
||||
It uses a macro to generate the required communication code and makes adding new functions easy and quick. Eagle is designed to work specifically with [`tokio`](https://crates.io/crates/tokio) and uses [`serde`](https://crates.io/crates/serde) for formatting data.
|
||||
|
||||
Please note that since `eagle` is a pure proc-macro library, you must manually add compatible versions of `tokio`, `serde`, `ron` and optionally `log` to your dependencies.
|
||||
|
||||
## Using Eagle
|
||||
|
||||
The way that `eagle` is designed to be used is inside a shared dependency between your "server" and your "client". Both of these should be in a workspace. Create a `shared` crate which both components should depend on, this crate should have `eagle` as a dependency. By default `eagle` uses TCP for communication, but you may disable default features and enable the `unix` feature on `eagle` to use unix sockets instead.
|
||||
@@ -67,9 +69,9 @@ Your handler can now be used by the server. You can easily bind your server to a
|
||||
use shared::ExampleServer;
|
||||
|
||||
let handler = ExampleHandler { state: 0 };
|
||||
let server_task = tokio::spawn(ExampleServer::bind(handler, "127.0.0.1:1234"));
|
||||
let server_task = ExampleServer::bind(handler, "127.0.0.1:1234").await;
|
||||
// Or, if you're using the 'unix' feature...
|
||||
let server_task = tokio::spawn(ExampleServer::bind(handler, "/tmp/sock"));
|
||||
let server_task = ExampleServer::bind(handler, "/tmp/sock").await;
|
||||
|
||||
```
|
||||
|
||||
|
||||
63
src/lib.rs
63
src/lib.rs
@@ -102,13 +102,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//! # tokio_test::block_on(async {
|
||||
//! let handler = Handler;
|
||||
//! let address = "127.0.0.1:12345"; // Or, if using the 'unix' feature, "/tmp/eagle.sock"
|
||||
//! let server_task = tokio::spawn(ExampleServer::bind(handler, address));
|
||||
//! let server = ExampleServer::bind(handler, address).await;
|
||||
//! server.close().await;
|
||||
//! # });
|
||||
//! ```
|
||||
//!
|
||||
//! Please note the usage of `tokio::spawn`. This is because the `bind` function
|
||||
//! will not return until the server is closed. You can use the `abort` method
|
||||
//! on the task to close the server.
|
||||
//! Once bound, the server will begin listening for incoming connections and
|
||||
//! queries. **You must remember to use the `close` method to shut down the server.**
|
||||
//!
|
||||
//! On the client side, you can simply use the generated client struct to connect
|
||||
//! to the server and begin sending queries.
|
||||
@@ -136,11 +135,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//! # tokio_test::block_on(async {
|
||||
//! # let handler = Handler;
|
||||
//! let address = "127.0.0.1:12345"; // Or, if using the 'unix' feature, "/tmp/eagle.sock"
|
||||
//! # let server_task = tokio::spawn(ExampleServer::bind(handler, address));
|
||||
//! # let server = ExampleServer::bind(handler, address).await;
|
||||
//! # tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; // Wait for the server to start
|
||||
//! let client = ExampleClient::connect(address).await.unwrap();
|
||||
//! # // Wait for the server to start, the developer is responsible for this in production
|
||||
//! # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
//! assert_eq!(client.add(2, 5).await.unwrap(), 7);
|
||||
//! # server.close().await;
|
||||
//! # });
|
||||
//! ```
|
||||
//!
|
||||
@@ -335,11 +334,39 @@ fn derive_protocol(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream
|
||||
|
||||
// Create an error and result type for sending messages
|
||||
let error_enum = quote! {
|
||||
#[derive(Debug)]
|
||||
#[derive(::std::fmt::Debug)]
|
||||
#vis enum #error_enum_name {
|
||||
SendError(::tokio::sync::mpsc::error::SendError<(u64, #question_enum_name)>),
|
||||
Closed,
|
||||
}
|
||||
impl ::std::fmt::Display for #error_enum_name {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
match self {
|
||||
#error_enum_name::SendError(e) => write!(f, "Failed to send query: {}", e),
|
||||
#error_enum_name::Closed => write!(f, "Connection closed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ::std::error::Error for #error_enum_name {
|
||||
fn source(&self) -> ::std::option::Option<&(dyn ::std::error::Error + 'static)> {
|
||||
match self {
|
||||
#error_enum_name::SendError(e) => ::std::option::Option::Some(e),
|
||||
#error_enum_name::Closed => ::std::option::Option::None,
|
||||
}
|
||||
}
|
||||
fn description(&self) -> &str {
|
||||
match self {
|
||||
#error_enum_name::SendError(_) => "Failed to send query",
|
||||
#error_enum_name::Closed => "Connection closed",
|
||||
}
|
||||
}
|
||||
fn cause(&self) -> ::std::option::Option<&dyn ::std::error::Error> {
|
||||
match self {
|
||||
#error_enum_name::SendError(e) => ::std::option::Option::Some(e),
|
||||
#error_enum_name::Closed => ::std::option::Option::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// Create enums for the types of messages the server and client will use
|
||||
|
||||
@@ -435,6 +462,13 @@ fn derive_protocol(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream
|
||||
sc
|
||||
}
|
||||
|
||||
pub async fn close(self) {
|
||||
#info("Closing server");
|
||||
for task in self.tasks.lock().await.drain(..) {
|
||||
task.abort();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn accept_connections<A: #stream_addr_trait>(
|
||||
&self,
|
||||
addr: A,
|
||||
@@ -610,7 +644,7 @@ fn derive_protocol(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream
|
||||
// Create a struct which the client will use to communicate
|
||||
let client_recv_queue_wrapper = format_ident!("__{}RecvQueueWrapper", name);
|
||||
let client_struct = quote! {
|
||||
#[derive(Clone)]
|
||||
#[derive(::std::clone::Clone)]
|
||||
struct #client_recv_queue_wrapper {
|
||||
recv_queue: ::std::sync::Arc<::tokio::sync::Mutex<::tokio::sync::mpsc::Receiver<(u64, #answer_enum_name)>>>,
|
||||
}
|
||||
@@ -657,8 +691,8 @@ fn derive_protocol(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream
|
||||
let connection_task = ::tokio::spawn(connection.run());
|
||||
Ok(Self::new(send_queue, recv_queue, ::std::option::Option::Some(::std::sync::Arc::new(connection_task)), ready_notify))
|
||||
}
|
||||
pub fn close(self) {
|
||||
if let ::std::option::Option::Some(task) = self.connection_task {
|
||||
pub fn close(&mut self) {
|
||||
if let ::std::option::Option::Some(task) = self.connection_task.take() {
|
||||
task.abort();
|
||||
}
|
||||
}
|
||||
@@ -699,6 +733,11 @@ fn derive_protocol(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream
|
||||
}
|
||||
#(#client_impl)*
|
||||
}
|
||||
impl ::std::ops::Drop for #client_struct_name {
|
||||
fn drop(&mut self) {
|
||||
self.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let expanded = quote! {
|
||||
|
||||
@@ -73,9 +73,8 @@ async fn e2e() {
|
||||
};
|
||||
#[cfg(feature = "tcp")]
|
||||
let address = format!("127.0.0.1:{}", 10000 + rand::random::<u64>() % 1000);
|
||||
let server_task = tokio::spawn(TestProtocolServer::bind(TrivialServer, address.clone()));
|
||||
// Wait for the server to start, the developer is responsible for this in production
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
let server = TestProtocolServer::bind(TrivialServer, address.clone()).await;
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; // Wait for the server to start
|
||||
let client = TestProtocolClient::connect(address).await.unwrap();
|
||||
assert_eq!(client.addition(2, 5).await.unwrap(), 7);
|
||||
assert_eq!(
|
||||
@@ -90,5 +89,5 @@ async fn e2e() {
|
||||
"The number is 42"
|
||||
);
|
||||
client.void().await.unwrap();
|
||||
server_task.abort();
|
||||
server.close().await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user