diff --git a/src/commands/mod.rs b/src/commands/mod.rs index d25f27a..c509ae6 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,3 +1,5 @@ +//! Build-in commands + mod help; use crate::plugins::Command; diff --git a/src/lib.rs b/src/lib.rs index 4d21673..8f7a463 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,24 @@ +//! # Servers - Simple TCP server +//! +//! [image]: https://socialify.git.ci/MedzikUser/servers/image?description=1&font=KoHo&language=1&owner=1&pattern=Circuit%20Board&theme=Light +//! +//! [![image]](https://github.com/MedzikUser/servers) +//! +//! ## 👨‍💻 Building +//! +//! First clone the repository: `git clone https://github.com/MedzikUser/servers.git` +//! +//! ### Requirements +//! - Rust +//! +//! To build run the command: `cargo build --release` +//! +//! The compiled binary can be found in `./target/release/servers` +//! +//! ## Writing plugins +//! +//! Go to [plugins](plugins) module + pub mod commands; pub mod plugins; pub mod tcp; diff --git a/src/main.rs b/src/main.rs index a556f54..40394ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,16 @@ +use std::net::TcpListener; + use clap::Parser; -use servers::tcp; +use servers::{ + plugins::loader, + tcp::{handle_connection, Client}, +}; use simple_logger::SimpleLogger; #[derive(Parser)] #[clap( name = "servers", - about = "Simple Tcp server that supports expansion via plugins" + about = "A simple TCP server for client which can be extended with plugins." )] struct Cli { #[clap( @@ -34,7 +39,35 @@ fn main() -> anyhow::Result<()> { let cli = Cli::parse(); // start tcp server - tcp::start_server(&cli.host, &cli.port)?; + start_server(&cli.host, &cli.port)?; Ok(()) } + +/// Start tcp server +#[tokio::main] +async fn start_server(host: &str, port: &str) -> anyhow::Result<()> { + // listen Tcp server + let listener = TcpListener::bind(format!("{host}:{port}"))?; + + println!("Tcp server started at: {}", listener.local_addr()?); + + // load plugins, commands and events + let (command_manager, _plugin_manager, event_manager) = loader()?; + + // Accepts a new incoming connection from this listener. + while let Ok((stream, _address)) = listener.accept() { + let client = Client::new(stream); + + // clone `CommandManager` + let command_manager = command_manager.clone(); + // clone `EventManager` + let event_manager = event_manager.clone(); + + // handle client connection in new thread + tokio::spawn(handle_connection(client, command_manager, event_manager)); + } + + // server for a unexpectedly reason be terminated + panic!("Server unexpectedly terminated!") +} diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 4ff6f92..51e90a6 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,3 +1,161 @@ +//! Plugins loader +//! +//! ## Writing plugins +//! +//! Create a new project `cargo new --lib plugin` +//! +//! Set a `crate-type` in Cargo.toml (to build a `.so` plugin) +//! +//! ```toml +//! [lib] +//! crate-type = ["dylib"] +//! ``` +//! +//! Add a `servers` and `async-trait` dependencies to Cargo.toml +//! +//! ```toml +//! [dependencies] +//! async-trait = "0.1.56" +//! servers = { path = ".." } +//! ``` +//! +//! ### Command plugin +//! +//! In file `src/lib.rs` +//! +//! ``` +//! use async_trait::async_trait; +//! use servers::{ +//! plugins::{ +//! Command, CommandManagerType, CommandRegistrar, EventRegistrar, Plugin, +//! PluginRegistrar, +//! }, +//! tcp::Client, +//! }; +//! +//! struct PluginTest; +//! +//! #[async_trait] +//! impl Plugin for PluginTest { +//! /// Name of the plugin. +//! fn name(&self) -> &'static str { +//! "test" +//! } +//! +//! /// Function will be executed when plugin loading. +//! async fn on_plugin_load(&self) { +//! println!("Loading plugin `test`...") +//! } +//! +//! /// Function will be executed when plugin unloading. +//! async fn on_plugin_unload(&self) { +//! println!("Unloading plugin `test`...") +//! } +//! } +//! +//! #[async_trait] +//! impl Command for PluginTest { +//! /// Command name +//! fn name(&self) -> &'static str { +//! "/test" +//! } +//! +//! /// Command help message +//! fn help(&self) -> &'static str { +//! "test command" +//! } +//! +//! /// Function will be executed when client send command `/test` +//! async fn execute(&self, client: &mut Client, _args: Vec<&str>, _commands: &CommandManagerType) { +//! client.send("Message sended by `test` plugin").expect("send message") +//! } +//! } +//! +//! /// Register plugin and command +//! #[no_mangle] +//! pub fn plugin_entry( +//! plugin: &mut dyn PluginRegistrar, +//! command: &mut dyn CommandRegistrar, +//! _event: &mut dyn EventRegistrar, +//! ) { +//! // register plugin +//! plugin.register(Box::new(PluginTest)); +//! // register command +//! command.register(Box::new(PluginTest)); +//! } +//! ``` +//! +//! ### Event plugin +//! +//! In file `src/lib.rs` +//! +//! ``` +//! use async_trait::async_trait; +//! use servers::{ +//! plugins::{ +//! CommandManagerType, CommandRegistrar, Event, EventRegistrar, Plugin, +//! PluginRegistrar, +//! }, +//! tcp::Client, +//! }; +//! +//! struct PluginTest; +//! +//! #[async_trait] +//! impl Plugin for PluginTest { +//! /// Name of the plugin. +//! fn name(&self) -> &'static str { +//! "test" +//! } +//! +//! /// Function will be executed when plugin loading. +//! async fn on_plugin_load(&self) { +//! println!("Loading plugin `test`...") +//! } +//! +//! /// Function will be executed when plugin unloading. +//! async fn on_plugin_unload(&self) { +//! println!("Unloading plugin `test`...") +//! } +//! } +//! +//! #[async_trait] +//! impl Event for PluginTest { +//! /// Event name (onConnect, onSend) +//! fn name(&self) -> &'static str { +//! "onConnect" +//! } +//! +//! /// Function will be executed when client connected +//! async fn execute(&self, client: &mut Client) { +//! client +//! .send(&format!("Welcome {}", client.stream.peer_addr().unwrap())) +//! .expect("send message") +//! } +//! } +//! +//! /// Register plugin and command +//! #[no_mangle] +//! pub fn plugin_entry( +//! plugin: &mut dyn PluginRegistrar, +//! _command: &mut dyn CommandRegistrar, +//! event: &mut dyn EventRegistrar, +//! ) { +//! // register plugin +//! plugin.register(Box::new(PluginTest)); +//! // register event +//! event.register(Box::new(PluginTest)); +//! } +//! ``` +//! +//! ## Build plugin +//! +//! To build plugin run command: `cargo build --release` +//! +//! The compiled plugin can be found in `./target/release/libplugin.so` +//! +//! Move compiled plugin to the `plugin` directory where servers is located + mod loader; pub use loader::*; diff --git a/src/tcp/client.rs b/src/tcp/client.rs index 84d1b9a..4e4b832 100644 --- a/src/tcp/client.rs +++ b/src/tcp/client.rs @@ -1,10 +1,13 @@ #![allow(clippy::unused_io_amount)] +pub const MAX_PACKET_LEN: usize = 65536; + use std::{ io::{self, Read, Write}, net::TcpStream, }; +/// TCP Client stream pub struct Client { pub stream: TcpStream, } @@ -17,8 +20,8 @@ impl Client { /// Read message/buffer from Client pub fn read(&mut self) -> anyhow::Result { - // allocate an empty buffer of length 1024 bytes - let mut buf = [0; 1024]; + // allocate an empty buffer + let mut buf = [0; MAX_PACKET_LEN]; // read buffer from stream self.stream.read(&mut buf)?; diff --git a/src/tcp/handle_connection.rs b/src/tcp/handle_connection.rs index 8d0ddb2..2af5454 100644 --- a/src/tcp/handle_connection.rs +++ b/src/tcp/handle_connection.rs @@ -56,6 +56,7 @@ pub async fn handle_connection( Ok(()) } +/// Search for a events and execute it async fn check_event(client: &mut Client, events: &EventManagerType, event_name: &str) { for event in events.events.iter() { // check if this event should be started diff --git a/src/tcp/mod.rs b/src/tcp/mod.rs index 0c8c698..5634b0b 100644 --- a/src/tcp/mod.rs +++ b/src/tcp/mod.rs @@ -1,7 +1,7 @@ +//! TCP connection utils + mod client; mod handle_connection; -mod start; pub use client::*; pub use handle_connection::*; -pub use start::*; diff --git a/src/tcp/start.rs b/src/tcp/start.rs deleted file mode 100644 index e9d781e..0000000 --- a/src/tcp/start.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::net::TcpListener; - -use crate::{ - plugins::loader, - tcp::{handle_connection, Client}, -}; - -#[tokio::main] -pub async fn start_server(host: &str, port: &str) -> anyhow::Result<()> { - // listen Tcp server - let listener = TcpListener::bind(format!("{host}:{port}"))?; - - println!("Tcp server started at: {}", listener.local_addr()?); - - // load plugins and commands - let (command_manager, _plugin_manager, event_manager) = loader()?; - - // Accepts a new incoming connection from this listener. - while let Ok((stream, _address)) = listener.accept() { - let client = Client::new(stream); - - // clone `CommandManager` - let command_manager = command_manager.clone(); - // clone `EventManager` - let event_manager = event_manager.clone(); - - // handle client connection in new thread - tokio::spawn(handle_connection(client, command_manager, event_manager)); - } - - Ok(()) -}