mirror of https://github.com/MedzikUser/servers
Compare commits
75 Commits
Author | SHA1 | Date |
---|---|---|
renovate[bot] | d5b238f153 | |
renovate[bot] | 91ab6cb607 | |
renovate[bot] | de5e26bbf2 | |
renovate[bot] | 09f7d14bcc | |
renovate[bot] | ce546a0c69 | |
renovate[bot] | 619ed0b41d | |
renovate[bot] | 5a103de793 | |
renovate[bot] | 0d4ff8041b | |
renovate[bot] | eb462622eb | |
MedzikUser | b56357c085 | |
renovate[bot] | fd4121b0d2 | |
MedzikUser | 032ec88ed6 | |
MedzikUser | 501cbc74b7 | |
MedzikUser | db6e068926 | |
MedzikUser | 212223c568 | |
MedzikUser | d15ec0c93e | |
MedzikUser | 9f66c2b9d2 | |
MedzikUser | 95d0e15786 | |
MedzikUser | d31e0fff2f | |
MedzikUser | 67fb1a0a3c | |
MedzikUser | 56e16145f6 | |
MedzikUser | 7da5daf522 | |
MedzikUser | bf1c3c4092 | |
MedzikUser | 5d2afe3719 | |
MedzikUser | 83f2961aee | |
MedzikUser | c51c0a2507 | |
MedzikUser | dd854ee6e5 | |
MedzikUser | 97b19ae413 | |
renovate[bot] | 45a89f365a | |
MedzikUser | f27df56c47 | |
MedzikUser | 6f7edf3d30 | |
MedzikUser | 84eed33e24 | |
MedzikUser | 25c2f0baa3 | |
MedzikUser | d0120a0703 | |
renovate[bot] | 868671848b | |
MedzikUser | 5fa16c6705 | |
MedzikUser | 45a9fe605d | |
renovate[bot] | 093671ff8f | |
MedzikUser | fc5afe56a1 | |
MedzikUser | 3fb0a1132a | |
MedzikUser | 6ad4afb146 | |
MedzikUser | e83a8360c2 | |
MedzikUser | 796084d13e | |
renovate[bot] | 2e3bb0cb43 | |
renovate[bot] | c48d61f22b | |
renovate[bot] | 183602a69e | |
renovate[bot] | 0f0a833dac | |
renovate[bot] | 6257dcfcfd | |
renovate[bot] | e303383607 | |
renovate[bot] | 40108a20fb | |
renovate[bot] | e6802c6db8 | |
renovate[bot] | 783a651e4f | |
MedzikUser | f08093ecc7 | |
renovate[bot] | cb1ccc6fc5 | |
renovate[bot] | 4b0b38755c | |
MedzikUser | 40fabbfecf | |
MedzikUser | 6430c2577c | |
MedzikUser | 1abc3290d9 | |
MedzikUser | ab60315350 | |
MedzikUser | 47d6bc47cf | |
MedzikUser | 1ea6336c8f | |
MedzikUser | 462524fc3e | |
MedzikUser | 65c828bb09 | |
MedzikUser | 172c00bde1 | |
MedzikUser | ec574f3268 | |
renovate[bot] | 9e06f31ff2 | |
MedzikUser | b9af11c34e | |
MedzikUser | d4f7fb650b | |
MedzikUser | 4f9baa4b76 | |
MedzikUser | 369dd14cf2 | |
MedzikUser | ebeec9adfa | |
renovate[bot] | 3e1d049c50 | |
renovate[bot] | 200f693b5f | |
MedzikUser | 2c144ae14a | |
MedzikUser | b0c019525e |
|
@ -2,8 +2,8 @@ name: Build release binaries (and publish them if this is a tag)
|
|||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
|
@ -17,8 +17,8 @@ jobs:
|
|||
|
||||
matrix:
|
||||
target:
|
||||
- x86_64-unknown-linux-musl
|
||||
- aarch64-unknown-linux-musl
|
||||
- x86_64-unknown-linux-gnu
|
||||
- aarch64-unknown-linux-gnu
|
||||
- x86_64-pc-windows-msvc
|
||||
- x86_64-apple-darwin
|
||||
- aarch64-apple-darwin
|
||||
|
@ -26,21 +26,17 @@ jobs:
|
|||
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-musl
|
||||
artifact_name: target/x86_64-unknown-linux-musl/release/servers
|
||||
release_name: x86_64-unknown-linux-musl
|
||||
target: x86_64-unknown-linux-gnu
|
||||
artifact_name: target/x86_64-unknown-linux-gnu/release/servers
|
||||
release_name: x86_64-unknown-linux-gnu
|
||||
cross: true
|
||||
strip: true
|
||||
compress: true
|
||||
cargo_flags: ""
|
||||
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-musl
|
||||
artifact_name: target/aarch64-unknown-linux-musl/release/servers
|
||||
release_name: aarch64-unknown-linux-musl
|
||||
target: aarch64-unknown-linux-gnu
|
||||
artifact_name: target/aarch64-unknown-linux-gnu/release/servers
|
||||
release_name: aarch64-unknown-linux-gnu
|
||||
cross: true
|
||||
strip: false
|
||||
compress: true
|
||||
cargo_flags: ""
|
||||
|
||||
- os: windows-latest
|
||||
|
@ -48,8 +44,6 @@ jobs:
|
|||
artifact_name: target/x86_64-pc-windows-msvc/release/servers.exe
|
||||
release_name: x86_64-pc-windows-msvc.exe
|
||||
cross: false
|
||||
strip: true
|
||||
compress: true
|
||||
cargo_flags: ""
|
||||
|
||||
- os: macos-latest
|
||||
|
@ -57,8 +51,6 @@ jobs:
|
|||
artifact_name: target/x86_64-apple-darwin/release/servers
|
||||
release_name: x86_64-apple-darwin
|
||||
cross: false
|
||||
strip: true
|
||||
compress: true
|
||||
cargo_flags: ""
|
||||
|
||||
- os: macos-latest
|
||||
|
@ -66,8 +58,6 @@ jobs:
|
|||
artifact_name: target/aarch64-apple-darwin/release/servers
|
||||
release_name: aarch64-apple-darwin
|
||||
cross: false
|
||||
strip: true
|
||||
compress: true
|
||||
cargo_flags: ""
|
||||
|
||||
- os: ubuntu-latest
|
||||
|
@ -75,8 +65,6 @@ jobs:
|
|||
artifact_name: target/x86_64-unknown-freebsd/release/servers
|
||||
release_name: x86_64-unknown-freebsd
|
||||
cross: true
|
||||
strip: false
|
||||
compress: false
|
||||
cargo_flags: ""
|
||||
|
||||
name: ${{ matrix.os }} for ${{ matrix.target }}
|
||||
|
@ -84,7 +72,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
|
@ -99,24 +87,12 @@ jobs:
|
|||
args: --release --target=${{ matrix.target }} ${{ matrix.cargo_flags }}
|
||||
use-cross: ${{ matrix.cross }}
|
||||
|
||||
- name: Compress binaries
|
||||
uses: svenstaro/upx-action@v2
|
||||
with:
|
||||
file: ${{ matrix.artifact_name }}
|
||||
args: --lzma
|
||||
strip: ${{ matrix.strip }}
|
||||
if: ${{ matrix.compress }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: ${{ matrix.artifact_name }}
|
||||
|
||||
###
|
||||
# Below this line, steps will only be ran if a tag was pushed.
|
||||
###
|
||||
|
||||
- name: Get tag name
|
||||
id: tag_name
|
||||
run: |
|
||||
|
@ -124,14 +100,6 @@ jobs:
|
|||
shell: bash
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
|
||||
- name: Get CHANGELOG.md entry
|
||||
id: changelog_reader
|
||||
uses: mindsers/changelog-reader-action@v1
|
||||
with:
|
||||
version: ${{ steps.tag_name.outputs.current_version }}
|
||||
path: ./CHANGELOG.md
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
|
||||
- name: Publish
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
|
|
|
@ -8,3 +8,6 @@
|
|||
# IDEs
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
|
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -1,26 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
<!-- next-header -->
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.1.0] - 2022-06-17
|
||||
### Default commands
|
||||
- help
|
||||
|
||||
### Dynamic plugins loader
|
||||
You can create custom commands and events (events executed if client connected or send message)
|
||||
|
||||
### Cli
|
||||
You set custom host and port `./servers --host 0.0.0.0 --port 9999`
|
||||
|
||||
Show cli help `./servers --help`
|
||||
|
||||
<!-- next-url -->
|
||||
[Unreleased]: https://github.com/MedzikUser/servers/compare/v0.1.0...HEAD
|
||||
[0.1.0]: https://github.com/MedzikUser/servers/commits/v0.1.0
|
File diff suppressed because it is too large
Load Diff
30
Cargo.toml
30
Cargo.toml
|
@ -1,22 +1,24 @@
|
|||
[workspace]
|
||||
members = ["plugin_test"]
|
||||
resolver = "2"
|
||||
|
||||
[package]
|
||||
name = "servers"
|
||||
version = "0.1.0"
|
||||
version = "0.6.0"
|
||||
description = "TCP and WebSocket server for Clients written in Rust"
|
||||
homepage = "https://github.com/MedzikUser/servers"
|
||||
repository = "https://github.com/MedzikUser/servers.git"
|
||||
license = "MIT"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
panic = 'abort'
|
||||
opt-level = 'z'
|
||||
codegen-units = 1
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.57"
|
||||
async-trait = "0.1.56"
|
||||
clap = { version = "3.2.5", features = ["derive"] }
|
||||
libloading = "0.7.3"
|
||||
log = { version = "0.4.17", features = ["release_max_level_info", "max_level_trace"] }
|
||||
simple_logger = { version = "2.1.0", default-features = false, features = ["colors"] }
|
||||
tokio = { version = "1.19.2", features = ["rt-multi-thread", "macros"] }
|
||||
anyhow = "1.0.68"
|
||||
async-std = { version = "1.12.0", features = ["attributes"] }
|
||||
async-trait = "0.1.63"
|
||||
clap = { version = "3.2.23", features = ["derive"] }
|
||||
libloading = "0.7.4"
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = "0.3.16"
|
||||
tungstenite = "0.18.0"
|
||||
futures = "0.3.25"
|
||||
lazy_static = "1.4.0"
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
# NOTE: Custom image specification for freebsd is required until new version of cross is released.
|
||||
[target.x86_64-unknown-freebsd]
|
||||
image = "svenstaro/cross-x86_64-unknown-freebsd:latest"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Servers - Simple TCP server
|
||||
# Servers - Simple TCP and WebSocket server
|
||||
|
||||
[docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
|
||||
[total-lines]: https://img.shields.io/tokei/lines/github/MedzikUser/servers?style=for-the-badge&logo=github&color=fede00
|
||||
|
@ -13,7 +13,7 @@
|
|||
|
||||
[![image]](https://github.com/MedzikUser/servers)
|
||||
|
||||
A simple TCP server for client written in Rust 🦀 which can be extended with plugins.
|
||||
A simple TCP server for clients and WebSocket server written in Rust 🦀 which can be extended with plugins.
|
||||
|
||||
## 👨💻 Building
|
||||
|
||||
|
@ -25,3 +25,7 @@ First clone the repository: `git clone https://github.com/MedzikUser/servers.git
|
|||
To build run the command: `cargo build --release`
|
||||
|
||||
The compiled binary can be found in `./target/release/servers`
|
||||
|
||||
## Writing plugins
|
||||
|
||||
Read the docs from [plugins](https://servers.medzik.xyz/servers/plugins) module.
|
||||
|
|
|
@ -4,8 +4,7 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["dylib"]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.56"
|
||||
servers = { path = ".." }
|
||||
|
|
|
@ -1,63 +1,55 @@
|
|||
use async_trait::async_trait;
|
||||
use servers::{
|
||||
plugins::{
|
||||
Command, CommandManagerType, CommandRegistrar, Event, EventRegistrar, Plugin,
|
||||
PluginRegistrar,
|
||||
},
|
||||
tcp::Client,
|
||||
};
|
||||
use servers::plugins::prelude::*;
|
||||
|
||||
struct PluginTest;
|
||||
|
||||
#[async_trait]
|
||||
impl Plugin for PluginTest {
|
||||
/// Name of the plugin.
|
||||
fn name(&self) -> &'static str {
|
||||
"test"
|
||||
"test_plugin"
|
||||
}
|
||||
|
||||
async fn on_plugin_load(&self) {}
|
||||
|
||||
async fn on_plugin_unload(&self) {}
|
||||
/// A function that will be executed when the plugin is loaded.
|
||||
async fn on_load(&self) {}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Command for PluginTest {
|
||||
/// Name of the command.
|
||||
fn name(&self) -> &'static str {
|
||||
"/test"
|
||||
}
|
||||
|
||||
fn help(&self) -> &'static str {
|
||||
"test command"
|
||||
/// Aliases for the command.
|
||||
fn aliases(&self) -> Vec<&'static str> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
async fn execute(&self, client: &mut Client, _args: Vec<&str>, _commands: &CommandManagerType) {
|
||||
client.send("content").expect("send message")
|
||||
/// Help message of the command.
|
||||
fn help(&self) -> &'static str {
|
||||
"Test commend loaded from dylib"
|
||||
}
|
||||
/// Usage message of the command.
|
||||
fn usage(&self) -> &'static str {
|
||||
"/test"
|
||||
}
|
||||
/// Command function.
|
||||
async fn execute(&self, client: &Client, _args: Vec<&str>) -> anyhow::Result<()> {
|
||||
client.send("successful executed command from dylib")
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Event for PluginTest {
|
||||
fn name(&self) -> &'static str {
|
||||
"onConnect"
|
||||
fn event(&self) -> EventType {
|
||||
EventType::OnConnect
|
||||
}
|
||||
|
||||
async fn execute(&self, client: &mut Client) {
|
||||
client
|
||||
.send(&format!("Welcome {}", client.stream.peer_addr().unwrap()))
|
||||
.expect("send message")
|
||||
async fn execute(&self, client: &Client, _data: EventData) -> anyhow::Result<()> {
|
||||
client.send("Hello!")
|
||||
}
|
||||
}
|
||||
|
||||
#[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));
|
||||
// register plugin
|
||||
event.register(Box::new(PluginTest));
|
||||
pub fn plugin_entry(registrar: &mut dyn Registrar) {
|
||||
registrar.register_plugins(Box::new(PluginTest));
|
||||
registrar.register_commands(Box::new(PluginTest));
|
||||
registrar.register_events(Box::new(PluginTest));
|
||||
}
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
"config:base",
|
||||
"schedule:weekly",
|
||||
"group:allNonMajor",
|
||||
":semanticCommits"
|
||||
],
|
||||
"labels": [
|
||||
"dependencies"
|
||||
],
|
||||
"prHourlyLimit": 0,
|
||||
"automergeType": "pr",
|
||||
"prCreation": "immediate",
|
||||
"packageRules": [
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# https://rust-lang.github.io/rustfmt
|
||||
|
||||
# stable
|
||||
edition = "2021"
|
||||
newline_style = "Unix"
|
||||
match_block_trailing_comma = true
|
||||
|
||||
# nightly
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Crate"
|
||||
format_code_in_doc_comments = true
|
|
@ -0,0 +1,91 @@
|
|||
use std::{
|
||||
io::{self, Read, Write},
|
||||
net::TcpStream,
|
||||
sync::Arc,
|
||||
thread,
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use servers::server::MAX_PACKET_LEN;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(
|
||||
name = "tcp-client",
|
||||
version = env!("CARGO_PKG_VERSION"),
|
||||
about = env!("CARGO_PKG_DESCRIPTION")
|
||||
)]
|
||||
struct Cli {
|
||||
#[clap(
|
||||
short = 'i',
|
||||
long = "host",
|
||||
help = "Server host",
|
||||
default_value = "0.0.0.0",
|
||||
display_order = 1
|
||||
)]
|
||||
host: String,
|
||||
|
||||
#[clap(
|
||||
short = 'p',
|
||||
long = "port",
|
||||
help = "Server port",
|
||||
default_value = "9999",
|
||||
display_order = 2
|
||||
)]
|
||||
port: String,
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = Cli::parse();
|
||||
|
||||
let addr = format!("{}:{}", args.host, args.port);
|
||||
|
||||
println!("Connecting to {}...", addr);
|
||||
|
||||
let stream = TcpStream::connect(addr)?;
|
||||
|
||||
println!("Connected!");
|
||||
|
||||
let stream = Arc::new(stream);
|
||||
|
||||
let reader = stream.clone();
|
||||
let writer = stream;
|
||||
|
||||
// read the output from the server to write to the stdout
|
||||
thread::spawn(move || {
|
||||
let mut buf = [0; MAX_PACKET_LEN];
|
||||
|
||||
// read buffer from the server
|
||||
while let Ok(buf_len) = reader.as_ref().read(&mut buf) {
|
||||
// ignore unused bytes
|
||||
let buf = &buf[0..buf_len];
|
||||
|
||||
// decode buffer from &[u8] to a String
|
||||
let mut buf_str = String::from_utf8(buf.to_vec()).unwrap();
|
||||
|
||||
// delete new line characters from the buffer
|
||||
buf_str = buf_str.replace('\n', "");
|
||||
buf_str = buf_str.replace('\r', "");
|
||||
|
||||
println!("{}", buf_str);
|
||||
}
|
||||
});
|
||||
|
||||
// create a new stdin handler
|
||||
let stdin = io::stdin();
|
||||
|
||||
// send command from stdin
|
||||
loop {
|
||||
let mut buf = String::new();
|
||||
|
||||
// read buffer from stdin
|
||||
stdin.read_line(&mut buf)?;
|
||||
|
||||
// remove new line characters
|
||||
while buf.ends_with('\n') || buf.ends_with('\r') {
|
||||
buf.pop();
|
||||
}
|
||||
|
||||
// send the buffer to the server
|
||||
writer.as_ref().write_all(buf.as_bytes())?;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
use async_std::task;
|
||||
|
||||
use crate::{plugins::prelude::*, CLIENTS};
|
||||
|
||||
pub struct Broadcast;
|
||||
|
||||
#[async_trait]
|
||||
impl Command for Broadcast {
|
||||
fn name(&self) -> &'static str {
|
||||
"/broadcast"
|
||||
}
|
||||
|
||||
fn aliases(&self) -> Vec<&'static str> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn help(&self) -> &'static str {
|
||||
"Send message to all connected clients"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &'static str {
|
||||
"/broadcast <message>"
|
||||
}
|
||||
|
||||
async fn execute(&self, client: &Client, args: Vec<&str>) -> anyhow::Result<()> {
|
||||
if args.is_empty() || args.join(" ").is_empty() {
|
||||
client.send("Missing message")?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let msg = args.join(" ");
|
||||
|
||||
let mut children = Vec::new();
|
||||
|
||||
// send message to all connected clients
|
||||
for (_i, client) in CLIENTS.lock().unwrap().clone() {
|
||||
let msg = msg.clone();
|
||||
let child = task::spawn(async move {
|
||||
client
|
||||
.send(msg)
|
||||
.expect("failed to send broadcast message to client")
|
||||
});
|
||||
|
||||
children.push(child);
|
||||
}
|
||||
|
||||
// wait for all task to complete
|
||||
for child in children {
|
||||
child.await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
use crate::plugins::prelude::*;
|
||||
|
||||
pub struct Disconnect;
|
||||
|
||||
#[async_trait]
|
||||
impl Command for Disconnect {
|
||||
fn name(&self) -> &'static str {
|
||||
"/disconnect"
|
||||
}
|
||||
|
||||
fn aliases(&self) -> Vec<&'static str> {
|
||||
vec!["/close", "/exit"]
|
||||
}
|
||||
|
||||
fn help(&self) -> &'static str {
|
||||
"Close the connection"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &'static str {
|
||||
"/disconnect"
|
||||
}
|
||||
|
||||
async fn execute(&self, client: &Client, _args: Vec<&str>) -> anyhow::Result<()> {
|
||||
client.close()
|
||||
}
|
||||
}
|
|
@ -1,32 +1,45 @@
|
|||
use async_trait::async_trait;
|
||||
use crate::plugins::prelude::*;
|
||||
|
||||
use crate::{
|
||||
plugins::{Command, CommandManagerType},
|
||||
tcp::Client,
|
||||
};
|
||||
|
||||
pub struct CommandHelp;
|
||||
pub struct Help;
|
||||
|
||||
#[async_trait]
|
||||
impl Command for CommandHelp {
|
||||
impl Command for Help {
|
||||
fn name(&self) -> &'static str {
|
||||
"/help"
|
||||
}
|
||||
|
||||
fn help(&self) -> &'static str {
|
||||
"show help"
|
||||
fn aliases(&self) -> Vec<&'static str> {
|
||||
vec!["/h", "/?", "?"]
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
client: &mut Client,
|
||||
_args: Vec<&str>,
|
||||
command_manager: &CommandManagerType,
|
||||
) {
|
||||
for command in command_manager.commands.iter() {
|
||||
client
|
||||
.send(&format!("{} - {}", command.name(), command.help()))
|
||||
.expect("send message");
|
||||
fn help(&self) -> &'static str {
|
||||
"Show commands help menu"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &'static str {
|
||||
"/help"
|
||||
}
|
||||
|
||||
async fn execute(&self, client: &Client, _args: Vec<&str>) -> anyhow::Result<()> {
|
||||
let mut msg = Vec::new();
|
||||
|
||||
for cmd in client.plugins_manager.commands.iter() {
|
||||
let aliases = cmd.aliases();
|
||||
|
||||
let aliases = if !aliases.is_empty() {
|
||||
cmd.aliases().join(", ")
|
||||
} else {
|
||||
"none".to_string()
|
||||
};
|
||||
|
||||
msg.push(format!(
|
||||
"{name} - {help} (Aliases: {aliases})",
|
||||
name = cmd.name(),
|
||||
help = cmd.help(),
|
||||
aliases = aliases,
|
||||
))
|
||||
}
|
||||
|
||||
client.send(msg.join("\n"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
use crate::plugins::prelude::*;
|
||||
|
||||
pub struct Id;
|
||||
|
||||
#[async_trait]
|
||||
impl Command for Id {
|
||||
fn name(&self) -> &'static str {
|
||||
"/id"
|
||||
}
|
||||
|
||||
fn aliases(&self) -> Vec<&'static str> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn help(&self) -> &'static str {
|
||||
"Get id of the client"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &'static str {
|
||||
"/id"
|
||||
}
|
||||
|
||||
async fn execute(&self, client: &Client, _args: Vec<&str>) -> anyhow::Result<()> {
|
||||
client.send(client.id)
|
||||
}
|
||||
}
|
|
@ -1,11 +1,25 @@
|
|||
//! Build-in commands
|
||||
//! Default servers commands.
|
||||
//!
|
||||
//! List of commands:
|
||||
//! - /broadcast
|
||||
//! - /disconnect
|
||||
//! - /help
|
||||
//! - /id
|
||||
|
||||
mod broadcast;
|
||||
mod disconnect;
|
||||
mod help;
|
||||
mod id;
|
||||
|
||||
use crate::plugins::Command;
|
||||
use self::{broadcast::Broadcast, disconnect::Disconnect, help::Help, id::Id};
|
||||
use crate::plugins::prelude::*;
|
||||
|
||||
/// Register default commands
|
||||
pub fn register_commands() -> Vec<Box<dyn Command>> {
|
||||
// create Vector with Commands
|
||||
vec![Box::new(help::CommandHelp)]
|
||||
vec![
|
||||
Box::new(Broadcast),
|
||||
Box::new(Disconnect),
|
||||
Box::new(Help),
|
||||
Box::new(Id),
|
||||
]
|
||||
}
|
||||
|
|
34
src/lib.rs
34
src/lib.rs
|
@ -1,24 +1,16 @@
|
|||
//! # 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
|
||||
use std::{collections::HashMap, sync::Mutex};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::server::Client;
|
||||
|
||||
pub mod commands;
|
||||
pub mod plugins;
|
||||
pub mod tcp;
|
||||
pub mod server;
|
||||
|
||||
lazy_static! {
|
||||
/// List with all connected clients
|
||||
pub static ref CLIENTS: Mutex<HashMap<usize, Client>> = Mutex::new(HashMap::new());
|
||||
/// Next ID of the client to be add to [CLIENTS]
|
||||
pub static ref CLIENT_NEXT: Mutex<usize> = Mutex::new(0);
|
||||
}
|
||||
|
|
77
src/main.rs
77
src/main.rs
|
@ -1,73 +1,46 @@
|
|||
use std::net::TcpListener;
|
||||
|
||||
use clap::Parser;
|
||||
use servers::{
|
||||
plugins::loader,
|
||||
tcp::{handle_connection, Client},
|
||||
};
|
||||
use simple_logger::SimpleLogger;
|
||||
use servers::server;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(
|
||||
name = "servers",
|
||||
about = "A simple TCP server for client which can be extended with plugins."
|
||||
name = env!("CARGO_PKG_NAME"),
|
||||
version = env!("CARGO_PKG_VERSION"),
|
||||
about = env!("CARGO_PKG_DESCRIPTION")
|
||||
)]
|
||||
struct Cli {
|
||||
#[clap(
|
||||
short = 'h',
|
||||
short = 'i',
|
||||
long = "host",
|
||||
help = "Server host",
|
||||
default_value = "0.0.0.0",
|
||||
help = "Tcp server host",
|
||||
display_order = 1
|
||||
)]
|
||||
host: String,
|
||||
#[clap(
|
||||
short = 'p',
|
||||
long = "port",
|
||||
short = 't',
|
||||
long = "tcp-port",
|
||||
help = "TCP server port",
|
||||
default_value = "9999",
|
||||
help = "Tcp server port [set 0 to random]",
|
||||
display_order = 2
|
||||
)]
|
||||
port: String,
|
||||
tcp_port: u16,
|
||||
#[clap(
|
||||
short = 'w',
|
||||
long = "websocket-port",
|
||||
help = "WebSocket server port",
|
||||
default_value = "9998",
|
||||
display_order = 3
|
||||
)]
|
||||
ws_port: u16,
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
// init logger
|
||||
SimpleLogger::new().init()?;
|
||||
fn main() {
|
||||
tracing_subscriber::fmt().init();
|
||||
|
||||
// parse cli args
|
||||
let cli = Cli::parse();
|
||||
let args = Cli::parse();
|
||||
|
||||
// start tcp server
|
||||
start_server(&cli.host, &cli.port)?;
|
||||
let tcp_host = format!("{host}:{port}", host = args.host, port = args.tcp_port);
|
||||
let ws_host = format!("{host}:{port}", host = args.host, port = args.ws_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!")
|
||||
server::run(tcp_host, ws_host).expect("failed to start tcp server");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
use std::{fs, path::Path};
|
||||
|
||||
use async_std::task;
|
||||
use libloading::{Library, Symbol};
|
||||
use tracing::{info, span, trace, Level};
|
||||
|
||||
use crate::{
|
||||
commands,
|
||||
plugins::{
|
||||
manager::{PluginsManager, PluginsManagerType},
|
||||
prelude::*,
|
||||
},
|
||||
};
|
||||
|
||||
/// Load all plugins, commands and events.
|
||||
pub fn loader(plugins_dir: &str) -> anyhow::Result<PluginsManagerType> {
|
||||
// if plugins directory doesn't exists, create it
|
||||
if !Path::new(plugins_dir).exists() {
|
||||
fs::create_dir_all(plugins_dir)?;
|
||||
}
|
||||
|
||||
// get all files from the plugins directory
|
||||
let plugins_files = fs::read_dir(plugins_dir)?;
|
||||
|
||||
// init a plugins manager
|
||||
let mut plugins_manager = PluginsManager::new();
|
||||
|
||||
// register default commands
|
||||
plugins_manager.commands = commands::register_commands();
|
||||
|
||||
for plugin_path in plugins_files {
|
||||
let path = plugin_path?.path();
|
||||
let path_str = path.to_str().unwrap();
|
||||
|
||||
// add span to logger
|
||||
let span = span!(Level::TRACE, "", plugin_path = path_str);
|
||||
let _enter = span.enter();
|
||||
|
||||
info!("Loading plugin {}", path_str);
|
||||
|
||||
// loading library from .so is unsafe
|
||||
unsafe {
|
||||
// Box::new and Box::leak must be there because
|
||||
// if it isn't there it throws an segmentation fault
|
||||
let lib = Box::leak(Box::new(Library::new(&path)?));
|
||||
|
||||
trace!("Finding symbol `plugin_entry` in {}", path_str);
|
||||
let func: Symbol<unsafe extern "C" fn(&mut dyn Registrar) -> ()> =
|
||||
lib.get(b"plugin_entry")?;
|
||||
|
||||
// execute the function `plugin_entry` to load the plugin (possible segmentation fault)
|
||||
trace!("Running function `plugin_entry` from plugin {}", path_str);
|
||||
func(&mut plugins_manager);
|
||||
}
|
||||
}
|
||||
|
||||
for plugin in plugins_manager.plugins.iter() {
|
||||
// execute the `on_load` function from the plugin
|
||||
task::block_on(async { plugin.on_load().await });
|
||||
info!("Loaded plugin {}.", plugin.name());
|
||||
}
|
||||
|
||||
Ok(plugins_manager.into())
|
||||
}
|
|
@ -1,204 +0,0 @@
|
|||
use std::{any::Any, fs, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use libloading::{Library, Symbol};
|
||||
use log::{debug, trace};
|
||||
|
||||
use crate::{commands, tcp::Client};
|
||||
|
||||
/// A plugin which allows you to add extra functionality.
|
||||
#[async_trait]
|
||||
pub trait Plugin: Any + Send + Sync {
|
||||
/// Get a name describing the `Plugin`.
|
||||
fn name(&self) -> &'static str;
|
||||
/// A function that runs immediately after plugin loading.
|
||||
/// Usually used for initialization.
|
||||
async fn on_plugin_load(&self);
|
||||
/// A function that runs immediately before the plugin is unloaded.
|
||||
/// Use this if you want to do any cleanup.
|
||||
async fn on_plugin_unload(&self);
|
||||
}
|
||||
|
||||
pub trait PluginRegistrar {
|
||||
/// Function to register the plugin
|
||||
fn register(&mut self, plugin: Box<dyn Plugin>);
|
||||
}
|
||||
|
||||
impl PluginRegistrar for PluginManager {
|
||||
fn register(&mut self, plugin: Box<dyn Plugin>) {
|
||||
self.plugins.push(plugin)
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin Manager
|
||||
pub struct PluginManager {
|
||||
pub plugins: Vec<Box<dyn Plugin>>,
|
||||
}
|
||||
|
||||
impl PluginManager {
|
||||
/// Create empty `PluginManager`
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
plugins: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PluginManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub type PluginManagerType = Arc<PluginManager>;
|
||||
|
||||
#[async_trait]
|
||||
pub trait Command: Any + Send + Sync {
|
||||
/// Command name
|
||||
fn name(&self) -> &'static str;
|
||||
/// Help message of this command
|
||||
fn help(&self) -> &'static str;
|
||||
/// Command function
|
||||
async fn execute(
|
||||
&self,
|
||||
client: &mut Client,
|
||||
args: Vec<&str>,
|
||||
command_manager: &CommandManagerType,
|
||||
);
|
||||
}
|
||||
|
||||
/// Command Manager
|
||||
pub struct CommandManager {
|
||||
pub commands: Vec<Box<dyn Command>>,
|
||||
}
|
||||
|
||||
impl CommandManager {
|
||||
/// Create empty `CommandManager`
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
commands: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CommandManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub type CommandManagerType = Arc<CommandManager>;
|
||||
|
||||
pub trait CommandRegistrar {
|
||||
/// Function to register the plugin and the commands in the plugin
|
||||
fn register(&mut self, command: Box<dyn Command>);
|
||||
}
|
||||
|
||||
impl CommandRegistrar for CommandManager {
|
||||
fn register(&mut self, command: Box<dyn Command>) {
|
||||
self.commands.push(command)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Event: Any + Send + Sync {
|
||||
/// Event name (onConnect, onSend)
|
||||
fn name(&self) -> &'static str;
|
||||
/// Event function
|
||||
async fn execute(&self, client: &mut Client);
|
||||
}
|
||||
|
||||
/// Event Manager
|
||||
pub struct EventManager {
|
||||
pub events: Vec<Box<dyn Event>>,
|
||||
}
|
||||
|
||||
impl EventManager {
|
||||
/// Create empty `EventManager`
|
||||
pub fn new() -> Self {
|
||||
Self { events: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EventManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub type EventManagerType = Arc<EventManager>;
|
||||
|
||||
pub trait EventRegistrar {
|
||||
/// Function to register the plugin and the commands in the plugin
|
||||
fn register(&mut self, command: Box<dyn Event>);
|
||||
}
|
||||
|
||||
impl EventRegistrar for EventManager {
|
||||
fn register(&mut self, command: Box<dyn Event>) {
|
||||
self.events.push(command)
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugins and Commands loader
|
||||
pub fn loader() -> anyhow::Result<(CommandManagerType, PluginManagerType, EventManagerType)> {
|
||||
// get path to .so lib from command argument
|
||||
let config_dir = "./plugins";
|
||||
let paths = fs::read_dir(config_dir)?;
|
||||
|
||||
// create a plugin manager where all loaded plugins will be located
|
||||
let mut plugin_manager = PluginManager::new();
|
||||
|
||||
// create a command manager where located all commands
|
||||
let mut command_manager = CommandManager::new();
|
||||
|
||||
// create a command manager where located all events from plugins
|
||||
let mut event_manager = EventManager::new();
|
||||
|
||||
// register default commands
|
||||
for command in commands::register_commands() {
|
||||
command_manager.commands.push(command)
|
||||
}
|
||||
|
||||
// for all plugin in directory
|
||||
for path in paths {
|
||||
// get library file path
|
||||
let path = path?.path();
|
||||
|
||||
let plugin_path = path.to_str().unwrap_or("unknown");
|
||||
|
||||
// log debug info
|
||||
debug!("Loading plugin `{}`", plugin_path);
|
||||
|
||||
// loading library with .so is unsafe
|
||||
unsafe {
|
||||
// load library
|
||||
// Box::new and Box::leak must be there because if it isn't there it throws a segmentation fault
|
||||
let lib = Box::leak(Box::new(Library::new(&path)?));
|
||||
|
||||
// get `plugin_entry` from library
|
||||
trace!("Finding symbol `plugin_entry` in `{}`", plugin_path);
|
||||
let func: Symbol<
|
||||
unsafe extern "C" fn(
|
||||
&mut dyn PluginRegistrar,
|
||||
&mut dyn CommandRegistrar,
|
||||
&mut dyn EventRegistrar,
|
||||
) -> (),
|
||||
> = lib.get(b"plugin_entry")?;
|
||||
|
||||
// execute initial plugin function
|
||||
trace!("Running `plugin_entry(...)` in plugin `{}`", plugin_path);
|
||||
func(
|
||||
&mut plugin_manager,
|
||||
&mut command_manager,
|
||||
&mut event_manager,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// return CommandManager, PluginManager and EventManager
|
||||
Ok((
|
||||
Arc::new(command_manager),
|
||||
Arc::new(plugin_manager),
|
||||
Arc::new(event_manager),
|
||||
))
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
use core::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::plugins::prelude::*;
|
||||
|
||||
/// Plugins manager struct with Clone derive added by Arc.
|
||||
pub type PluginsManagerType = Arc<PluginsManager>;
|
||||
|
||||
/// A plugins manager that stores all plugins, commands and events.
|
||||
#[derive(Default)]
|
||||
pub struct PluginsManager {
|
||||
/// Vector with all loaded plugins.
|
||||
pub plugins: Vec<Box<dyn Plugin>>,
|
||||
/// Vector with all loaded commands.
|
||||
pub commands: Vec<Box<dyn Command>>,
|
||||
/// Vector with all loaded events.
|
||||
pub events: Vec<Box<dyn Event>>,
|
||||
}
|
||||
|
||||
impl PluginsManager {
|
||||
/// Returns an empty instance of [PluginsManager]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
plugins: Vec::new(),
|
||||
commands: Vec::new(),
|
||||
events: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the instance in [PluginsManagerType].
|
||||
pub fn into(self) -> PluginsManagerType {
|
||||
Arc::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for PluginsManager {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("PluginsManager")
|
||||
.field("plugins", &self.plugins.len())
|
||||
.field("commands", &self.commands.len())
|
||||
.field("events", &self.events.len())
|
||||
.finish()
|
||||
}
|
||||
}
|
|
@ -1,161 +1,20 @@
|
|||
//! 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 = "0.1.0"
|
||||
//! ```
|
||||
//!
|
||||
//! ### 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
|
||||
//! Plugin infrastructure.
|
||||
|
||||
mod loader;
|
||||
mod load;
|
||||
mod manager;
|
||||
pub mod types;
|
||||
|
||||
pub use loader::*;
|
||||
pub use load::*;
|
||||
pub use manager::*;
|
||||
|
||||
/// Crates and types required in plugins.
|
||||
pub mod prelude {
|
||||
use super::*;
|
||||
|
||||
pub extern crate anyhow;
|
||||
pub extern crate async_std;
|
||||
pub use async_trait::async_trait;
|
||||
|
||||
pub use self::types::*;
|
||||
pub use crate::server::{Client, ClientMapValue};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
//! Types used for creating plugins.
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{plugins::manager::PluginsManager, server::Client};
|
||||
|
||||
/// A main plugin trait.
|
||||
#[async_trait]
|
||||
pub trait Plugin: Any + Send + Sync {
|
||||
/// Name of the plugin.
|
||||
fn name(&self) -> &'static str;
|
||||
/// A function that will be executed when the plugin is loaded.
|
||||
async fn on_load(&self);
|
||||
}
|
||||
|
||||
/// Add a command to the plugin.
|
||||
#[async_trait]
|
||||
pub trait Command: Any + Send + Sync {
|
||||
/// Name of the command.
|
||||
fn name(&self) -> &'static str;
|
||||
/// Aliases for the command.
|
||||
fn aliases(&self) -> Vec<&'static str>;
|
||||
/// Help message of the command.
|
||||
fn help(&self) -> &'static str;
|
||||
/// Usage message of the command.
|
||||
fn usage(&self) -> &'static str;
|
||||
/// Command function.
|
||||
async fn execute(&self, client: &Client, args: Vec<&str>) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
/// All possible to run events.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EventType {
|
||||
/// On client connected.
|
||||
OnConnect,
|
||||
/// On client sent message.
|
||||
OnSend,
|
||||
/// Event executed before command execute (e.g. for disable command).
|
||||
OnCommand,
|
||||
}
|
||||
|
||||
/// All possible to run events.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EventData {
|
||||
/// for `onCommand` event
|
||||
Command(String),
|
||||
/// No data
|
||||
None,
|
||||
}
|
||||
|
||||
/// Add a event to the plugin.
|
||||
#[async_trait]
|
||||
pub trait Event: Any + Send + Sync {
|
||||
/// Type of the event.
|
||||
fn event(&self) -> EventType;
|
||||
/// Event function.
|
||||
async fn execute(&self, client: &Client, data: EventData) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
/// A plugin registrar trait.
|
||||
pub trait Registrar {
|
||||
/// Function to register plugins.
|
||||
fn register_plugins(&mut self, plugin: Box<dyn Plugin>);
|
||||
/// Function to register commands.
|
||||
fn register_commands(&mut self, command: Box<dyn Command>);
|
||||
/// Function to register events.
|
||||
fn register_events(&mut self, event: Box<dyn Event>);
|
||||
}
|
||||
|
||||
impl Registrar for PluginsManager {
|
||||
fn register_plugins(&mut self, plugin: Box<dyn Plugin>) {
|
||||
self.plugins.push(plugin)
|
||||
}
|
||||
|
||||
fn register_commands(&mut self, command: Box<dyn Command>) {
|
||||
self.commands.push(command)
|
||||
}
|
||||
|
||||
fn register_events(&mut self, event: Box<dyn Event>) {
|
||||
self.events.push(event)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
net::{Shutdown, SocketAddr, TcpStream},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use tracing::info;
|
||||
use tungstenite::{accept, Message, WebSocket};
|
||||
|
||||
use super::run::PLUGINS_MANAGER;
|
||||
use crate::plugins::{
|
||||
prelude::{EventData, EventType},
|
||||
PluginsManagerType,
|
||||
};
|
||||
|
||||
/// Max length of a TCP and UDP packet
|
||||
pub const MAX_PACKET_LEN: usize = 65536;
|
||||
|
||||
/// Client struct
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Client {
|
||||
/// ID of the client
|
||||
pub id: usize,
|
||||
/// Connection stream of the client
|
||||
pub stream: ClientStream,
|
||||
/// Custom Client Map
|
||||
pub map: Arc<Mutex<HashMap<String, ClientMapValue>>>,
|
||||
/// Plugins Manager
|
||||
pub plugins_manager: PluginsManagerType,
|
||||
}
|
||||
|
||||
// impl Drop for Client {
|
||||
// fn drop(&mut self) {
|
||||
// if let Err(err) = self.close() {
|
||||
// error!("Failed to close client connection: {err}");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Value type of the client map entry
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ClientMapValue {
|
||||
String(String),
|
||||
Array(Vec<String>),
|
||||
Bool(bool),
|
||||
Int(isize),
|
||||
UInt(usize),
|
||||
}
|
||||
|
||||
/// Connection stream of the client
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ClientStream {
|
||||
/// TCP stream
|
||||
TCP(Arc<TcpStream>),
|
||||
/// WebSocket stream
|
||||
WebSocket(Arc<Mutex<WebSocket<TcpStream>>>),
|
||||
}
|
||||
|
||||
impl From<TcpStream> for Client {
|
||||
fn from(stream: TcpStream) -> Self {
|
||||
Self {
|
||||
id: 0,
|
||||
stream: ClientStream::TCP(Arc::new(stream)),
|
||||
map: Arc::new(Mutex::new(HashMap::new())),
|
||||
plugins_manager: PLUGINS_MANAGER.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WebSocket<TcpStream>> for Client {
|
||||
fn from(stream: WebSocket<TcpStream>) -> Self {
|
||||
Self {
|
||||
id: 0,
|
||||
stream: ClientStream::WebSocket(Arc::new(Mutex::new(stream))),
|
||||
map: Arc::new(Mutex::new(HashMap::new())),
|
||||
plugins_manager: PLUGINS_MANAGER.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new TCP Client instance
|
||||
pub fn new_tcp(stream: TcpStream, id: usize) -> Self {
|
||||
let mut client = Self::from(stream);
|
||||
|
||||
client.id = id;
|
||||
|
||||
client
|
||||
}
|
||||
|
||||
/// Create a new WebSocket Client instance
|
||||
pub fn new_websocket(stream: TcpStream, id: usize) -> anyhow::Result<Self> {
|
||||
let websocket = accept(stream)?;
|
||||
|
||||
let mut client = Self::from(websocket);
|
||||
|
||||
client.id = id;
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
/// Recieve a message from the client
|
||||
pub fn read(&self) -> anyhow::Result<String> {
|
||||
// read the message from the stream
|
||||
let mut msg = match &self.stream {
|
||||
ClientStream::TCP(stream) => {
|
||||
// allocate an empty buffer
|
||||
let mut buf = [0; MAX_PACKET_LEN];
|
||||
|
||||
// read the message and get length of it
|
||||
let len = stream.as_ref().read(&mut buf)?;
|
||||
|
||||
// select only used bytes in the buffer
|
||||
let buf = &buf[0..len];
|
||||
|
||||
// decode buffer (&[u8]) to a String
|
||||
String::from_utf8(buf.to_vec())?
|
||||
},
|
||||
ClientStream::WebSocket(stream) => {
|
||||
// read the message from the stream
|
||||
let msg = stream.lock().unwrap().read_message()?;
|
||||
|
||||
// decode message to a String
|
||||
msg.to_string()
|
||||
},
|
||||
};
|
||||
|
||||
// remove new line characters
|
||||
while msg.ends_with('\n') || msg.ends_with('\r') {
|
||||
msg.pop();
|
||||
}
|
||||
|
||||
info!("[Recieved]: {}", msg);
|
||||
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
/// Send a message to the client
|
||||
pub fn send<S>(&self, msg: S) -> anyhow::Result<()>
|
||||
where
|
||||
S: ToString,
|
||||
S: fmt::Display,
|
||||
{
|
||||
// convert the message into a string
|
||||
let msg = msg.to_string();
|
||||
|
||||
// convert the message into bytes to send it
|
||||
let buf = msg.as_bytes();
|
||||
|
||||
// send the message
|
||||
match &self.stream {
|
||||
ClientStream::TCP(stream) => stream.as_ref().write_all(buf)?,
|
||||
ClientStream::WebSocket(stream) => {
|
||||
stream.lock().unwrap().write_message(Message::from(buf))?
|
||||
},
|
||||
}
|
||||
|
||||
info!("[Sent]: {}", msg);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the socket address of the remote peer of this connection.
|
||||
pub fn peer_addr(&self) -> anyhow::Result<SocketAddr> {
|
||||
let addr = match &self.stream {
|
||||
ClientStream::TCP(stream) => stream.peer_addr()?,
|
||||
ClientStream::WebSocket(stream) => stream.lock().unwrap().get_ref().peer_addr()?,
|
||||
};
|
||||
|
||||
Ok(addr)
|
||||
}
|
||||
|
||||
/// Flush this output stream, ensuring that all intermediately buffered contents reach their destination.
|
||||
pub fn flush(&self) -> anyhow::Result<()> {
|
||||
match &self.stream {
|
||||
ClientStream::TCP(stream) => stream.as_ref().flush()?,
|
||||
ClientStream::WebSocket(_stream) => {},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Close the client connection
|
||||
pub fn close(&self) -> anyhow::Result<()> {
|
||||
match &self.stream {
|
||||
ClientStream::TCP(stream) => stream.shutdown(Shutdown::Both)?,
|
||||
ClientStream::WebSocket(stream) => stream.lock().unwrap().close(None)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Inserts a key-value pair into the map.
|
||||
pub fn insert_key<S>(&self, key: S, value: ClientMapValue) -> Option<ClientMapValue>
|
||||
where
|
||||
S: ToString,
|
||||
{
|
||||
self.map.lock().unwrap().insert(key.to_string(), value)
|
||||
}
|
||||
|
||||
/// Returns the value from the key.
|
||||
pub fn get_value<S>(&self, key: S) -> Option<ClientMapValue>
|
||||
where
|
||||
S: ToString,
|
||||
{
|
||||
self.map.lock().unwrap().get(&key.to_string()).cloned()
|
||||
}
|
||||
|
||||
/// Delete key from the map.
|
||||
pub fn delete_key<S>(&self, key: S) -> Option<ClientMapValue>
|
||||
where
|
||||
S: ToString,
|
||||
{
|
||||
self.map.lock().unwrap().remove(&key.to_string())
|
||||
}
|
||||
|
||||
pub async fn run_events(
|
||||
&self,
|
||||
event_type: EventType,
|
||||
event_data: EventData,
|
||||
) -> anyhow::Result<()> {
|
||||
for event in self.plugins_manager.events.iter() {
|
||||
if event.event() == event_type {
|
||||
event.execute(self, event_data.clone()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
//! Server infrastructure.
|
||||
|
||||
mod client;
|
||||
mod run;
|
||||
|
||||
pub use client::*;
|
||||
pub use run::*;
|
|
@ -0,0 +1,217 @@
|
|||
use std::{net::TcpListener, thread};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use async_std::task;
|
||||
use futures::join;
|
||||
use lazy_static::lazy_static;
|
||||
use tracing::{error, info, span, Level};
|
||||
|
||||
use crate::{
|
||||
plugins::{
|
||||
self,
|
||||
prelude::{EventData, EventType},
|
||||
PluginsManagerType,
|
||||
},
|
||||
server::Client,
|
||||
CLIENTS, CLIENT_NEXT,
|
||||
};
|
||||
|
||||
/// Plugins directory.
|
||||
pub const PLUGINS_DIR: &str = "plugins";
|
||||
|
||||
lazy_static! {
|
||||
/// Plugin manager, where you can find loaded plugins, commands and events
|
||||
pub static ref PLUGINS_MANAGER: PluginsManagerType =
|
||||
plugins::loader(PLUGINS_DIR).expect("failed to load plugins");
|
||||
}
|
||||
|
||||
/// Start servers
|
||||
pub fn run(tcp_host: String, ws_host: String) -> anyhow::Result<()> {
|
||||
info!("Loaded {} plugins", PLUGINS_MANAGER.plugins.len());
|
||||
info!("Loaded {} commands", PLUGINS_MANAGER.commands.len());
|
||||
info!("Loaded {} events", PLUGINS_MANAGER.events.len());
|
||||
|
||||
let tcp_child = task::spawn(async move {
|
||||
start_tcp(tcp_host).await.unwrap();
|
||||
});
|
||||
|
||||
let ws_child = task::spawn(async move {
|
||||
start_websocket(ws_host).await.unwrap();
|
||||
});
|
||||
|
||||
task::block_on(async {
|
||||
join!(tcp_child, ws_child);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Process client connection
|
||||
async fn process(client: Client) -> anyhow::Result<()> {
|
||||
let client_addr = client.peer_addr()?;
|
||||
|
||||
info!("Processing client connection: {}", client_addr);
|
||||
|
||||
// run `onConnect` events
|
||||
client
|
||||
.run_events(EventType::OnConnect, EventData::None)
|
||||
.await?;
|
||||
|
||||
loop {
|
||||
let buf = client.read()?;
|
||||
|
||||
// functions for error handling see `if` below function
|
||||
async fn handle(client: &Client, buf: String) -> anyhow::Result<()> {
|
||||
// run `onSend` events
|
||||
client
|
||||
.run_events(EventType::OnSend, EventData::None)
|
||||
.await?;
|
||||
|
||||
let mut args: Vec<&str> = buf.split_ascii_whitespace().collect();
|
||||
|
||||
// if client sent an empty buffer
|
||||
if args.is_empty() {
|
||||
client.send("empty buffer")?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let cmd = args[0];
|
||||
|
||||
// remove command name from args
|
||||
args = args[1..args.len()].to_vec();
|
||||
|
||||
// find command
|
||||
let command = client
|
||||
.plugins_manager
|
||||
.commands
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|&(_i, command)| command.name() == cmd || command.aliases().contains(&cmd));
|
||||
|
||||
// execute command, if command isn't blocked
|
||||
// to block a command return error in the `onCommand` event
|
||||
if let Some((_i, cmd)) = command {
|
||||
// run `onCommand` events
|
||||
if client
|
||||
.run_events(
|
||||
EventType::OnCommand,
|
||||
EventData::Command(cmd.name().to_string()),
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
// execute command
|
||||
cmd.execute(client, args).await?;
|
||||
}
|
||||
} else {
|
||||
client.send("unknown command")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// handle errors from message processing
|
||||
if let Err(err) = handle(&client, buf).await {
|
||||
let err = err.to_string();
|
||||
|
||||
// client disconnect e.g. using ctrl + c
|
||||
if err.contains("Broken pipe") {
|
||||
return Err(anyhow!("disconnected"));
|
||||
} else {
|
||||
error!("Unexpected error in message handler: {}", err);
|
||||
client.send("Unexpected error")?;
|
||||
}
|
||||
}
|
||||
|
||||
client.flush()?;
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_tcp(host: String) -> anyhow::Result<()> {
|
||||
let listener = TcpListener::bind(host)?;
|
||||
|
||||
let incoming = listener.incoming();
|
||||
|
||||
for stream in incoming {
|
||||
let stream = stream?;
|
||||
|
||||
// get id for the client
|
||||
let id = *CLIENT_NEXT.lock().unwrap();
|
||||
|
||||
// add one to next id
|
||||
*CLIENT_NEXT.lock().unwrap() += 1;
|
||||
|
||||
thread::spawn(move || {
|
||||
// get id for the client and add one to next id
|
||||
let client = Client::new_tcp(stream, id);
|
||||
|
||||
// insert the cloned client to CLIENTS
|
||||
CLIENTS.lock().unwrap().insert(id, client.clone());
|
||||
|
||||
// add span to logger
|
||||
let span = span!(Level::ERROR, "TCP", id = client.id);
|
||||
let _enter = span.enter();
|
||||
|
||||
if let Err(err) = task::block_on(process(client)) {
|
||||
let err = err.to_string();
|
||||
|
||||
// client disconnect e.g. using ctrl + c
|
||||
if err == "disconnected" {
|
||||
info!("Client disconnected")
|
||||
} else {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
// delete the client from CLIENTS map
|
||||
CLIENTS.lock().unwrap().remove(&id);
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn start_websocket(host: String) -> anyhow::Result<()> {
|
||||
let listener = TcpListener::bind(host)?;
|
||||
|
||||
let incoming = listener.incoming();
|
||||
|
||||
for stream in incoming {
|
||||
let stream = stream?;
|
||||
|
||||
// get id for the client
|
||||
let id = *CLIENT_NEXT.lock().unwrap();
|
||||
|
||||
// add one to next id
|
||||
*CLIENT_NEXT.lock().unwrap() += 1;
|
||||
|
||||
thread::spawn(move || {
|
||||
let client = Client::new_websocket(stream, id).unwrap();
|
||||
|
||||
// insert the cloned client to CLIENTS
|
||||
CLIENTS.lock().unwrap().insert(id, client.clone());
|
||||
|
||||
// add span to logger
|
||||
let span = span!(Level::ERROR, "WS", id = client.id);
|
||||
let _enter = span.enter();
|
||||
|
||||
if let Err(err) = task::block_on(process(client)) {
|
||||
let err = err.to_string();
|
||||
|
||||
// client disconnect e.g. using ctrl + c
|
||||
if err == "disconnected"
|
||||
|| err.contains("Connection reset without closing handshake")
|
||||
{
|
||||
info!("Client disconnected")
|
||||
} else {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
// delete the client from CLIENTS map
|
||||
CLIENTS.lock().unwrap().remove(&id);
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
#![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,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create new Client
|
||||
pub fn new(stream: TcpStream) -> Self {
|
||||
Self { stream }
|
||||
}
|
||||
|
||||
/// Read message/buffer from Client
|
||||
pub fn read(&mut self) -> anyhow::Result<String> {
|
||||
// allocate an empty buffer
|
||||
let mut buf = [0; MAX_PACKET_LEN];
|
||||
|
||||
// read buffer from stream
|
||||
self.stream.read(&mut buf)?;
|
||||
|
||||
// encode &[u8] to a String and replace null spaces (empty `\0` bytes)
|
||||
let decoded = String::from_utf8(buf.to_vec())?.replace('\0', "");
|
||||
|
||||
Ok(decoded)
|
||||
}
|
||||
|
||||
/// Send message to Client
|
||||
pub fn send(&mut self, content: &str) -> io::Result<()> {
|
||||
// add a new line at the end of the content
|
||||
let content = format!("{content}\n\r");
|
||||
|
||||
// send message
|
||||
self.stream.write_all(content.as_bytes())
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
use std::io::Write;
|
||||
|
||||
use log::trace;
|
||||
|
||||
use crate::plugins::{CommandManagerType, EventManagerType};
|
||||
|
||||
use super::Client;
|
||||
|
||||
/// Handle Client connection
|
||||
pub async fn handle_connection(
|
||||
mut client: Client,
|
||||
commands: CommandManagerType,
|
||||
events: EventManagerType,
|
||||
) -> anyhow::Result<()> {
|
||||
println!("New Client: {:?}", client.stream.peer_addr()?);
|
||||
|
||||
// run `onConnect` events from plugins
|
||||
check_event(&mut client, &events, "onConnect").await;
|
||||
|
||||
loop {
|
||||
// read client message/buffer
|
||||
let buf = client.read()?;
|
||||
|
||||
// run `onSend` events from plugins
|
||||
check_event(&mut client, &events, "onSend").await;
|
||||
|
||||
// split message by whitespace
|
||||
let args: &Vec<&str> = &buf.split_ascii_whitespace().collect();
|
||||
|
||||
// get command from args
|
||||
let cmd = args[0];
|
||||
|
||||
// search if a command exists
|
||||
for command in commands.commands.iter() {
|
||||
// if this is the entered command
|
||||
if cmd == command.name() {
|
||||
trace!("Executing a command `{}`", command.name());
|
||||
|
||||
// execute command
|
||||
command
|
||||
.execute(&mut client, args[1..args.len()].to_vec(), &commands)
|
||||
.await;
|
||||
|
||||
// don't search for more commands
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if an I/O or EOF error, abort the connection
|
||||
if client.stream.flush().is_err() {
|
||||
// terminate connection
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if event.name() == event_name {
|
||||
trace!("Executing a event `{}`", event.name());
|
||||
|
||||
// execute event
|
||||
event.execute(client).await;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
//! TCP connection utils
|
||||
|
||||
mod client;
|
||||
mod handle_connection;
|
||||
|
||||
pub use client::*;
|
||||
pub use handle_connection::*;
|
Loading…
Reference in New Issue