From d0120a0703c51340cd39957b779ee4ba583ddffb Mon Sep 17 00:00:00 2001 From: MedzikUser Date: Fri, 12 Aug 2022 22:32:22 +0200 Subject: [PATCH] refactor: rewrite - Better Client struct (TCP and WebSocket in one type) - WebSocket is now not a proxy to tcp - Use async-std instead a tokio - Use Arc and Client type now have a Clone derive - Add global CLIENTS list --- CHANGELOG.md | 47 --- Cargo.lock | 685 +++++++++++++++++++++++------------ Cargo.toml | 22 +- Cross.toml | 3 - README.md | 31 -- plugin_test/Cargo.toml | 2 + plugin_test/src/lib.rs | 67 +--- renovate.json | 13 - rustfmt.toml | 10 + src/cli.rs | 43 --- src/commands/disconnect.rs | 33 +- src/commands/help.rs | 54 +-- src/commands/mod.rs | 12 +- src/lib.rs | 31 +- src/logger.rs | 9 - src/main.rs | 118 ++---- src/plugins/load.rs | 52 +++ src/plugins/loader.rs | 60 --- src/plugins/manager.rs | 37 ++ src/plugins/mod.rs | 173 +-------- src/plugins/types.rs | 96 ++--- src/tcp/client.rs | 175 +++++++-- src/tcp/handle_connection.rs | 97 ----- src/tcp/handle_websocket.rs | 85 ----- src/tcp/mod.rs | 7 +- src/tcp/server.rs | 139 +++++++ 26 files changed, 1005 insertions(+), 1096 deletions(-) delete mode 100644 CHANGELOG.md delete mode 100644 Cross.toml delete mode 100644 README.md delete mode 100644 renovate.json create mode 100644 rustfmt.toml delete mode 100644 src/cli.rs delete mode 100644 src/logger.rs create mode 100644 src/plugins/load.rs delete mode 100644 src/plugins/loader.rs create mode 100644 src/plugins/manager.rs delete mode 100644 src/tcp/handle_connection.rs delete mode 100644 src/tcp/handle_websocket.rs create mode 100644 src/tcp/server.rs diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 9893b55..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,47 +0,0 @@ -# Changelog - - - -## [Unreleased] - -## [0.3.0] - 2022-08-04 -## **Breaking** -- **tcp**: use tcp from tokio instead of std - -## Features -- **client**: added `peer_addr` function -- **server**: added `/help` command -- **api**: re-export `async_trait` so that it doesn't have to be added to dependencies in plugins - -## Changed -- **server**: the `/help` command has been accelerated -- **cli**: moved to the `cli.rs` file -- **logger**: changed `log` to `tracing` -- **dependencies**: updated -- **cli**: deleted option `--disable-websocket` and added `--enable-websocket` - -## [0.2.0] - 2022-06-26 -### Features -- **plugins**: add `Result<()>` in `fn execute()` (Event and Command traits) -- **websocket**: WS Client <-> TCP Proxy (default port 9998) <-> TCP (default port 9999) - -### Chore -- **deps**: upgrade - -## [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` - - -[Unreleased]: https://github.com/MedzikUser/servers/compare/v0.3.0...HEAD -[0.3.0]: https://github.com/MedzikUser/servers/commits/v0.3.0 -[0.2.0]: https://github.com/MedzikUser/servers/commits/v0.2.0 -[0.1.0]: https://github.com/MedzikUser/servers/commits/v0.1.0 diff --git a/Cargo.lock b/Cargo.lock index 3c7adac..d9a9500 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "ansi_term" version = "0.12.1" @@ -28,9 +13,121 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c794e162a5eff65c72ef524dfe393eb923c354e350bb78b9c7383df13f3bc142" +checksum = "508b352bb5c066aac251f6daf6b36eccd03e8a88e8081cd44959ea277a3af9a8" + +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "async-channel" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "num_cpus", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" +dependencies = [ + "concurrent-queue", + "futures-lite", + "libc", + "log", + "once_cell", + "parking", + "polling", + "slab", + "socket2", + "waker-fn", + "winapi", +] + +[[package]] +name = "async-lock" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-attributes", + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" [[package]] name = "async-trait" @@ -43,6 +140,12 @@ dependencies = [ "syn", ] +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + [[package]] name = "atty" version = "0.2.14" @@ -60,37 +163,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "backtrace" -version = "0.3.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base64" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" -[[package]] -name = "better-panic" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa9e1d11a268684cbd90ed36370d7577afb6c62d912ddff5c15fc34343e5036" -dependencies = [ - "backtrace", - "console", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -106,6 +184,26 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "once_cell", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + [[package]] name = "byteorder" version = "1.4.3" @@ -114,9 +212,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" [[package]] name = "cc" @@ -132,9 +236,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "3.2.16" +version = "3.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" +checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b" dependencies = [ "atty", "bitflags", @@ -149,9 +253,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.15" +version = "3.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4" +checksum = "13547f7012c01ab4a0e8f8967730ada8f9fdf419e8b6c792788f39cf4e46eefa" dependencies = [ "heck", "proc-macro-error", @@ -162,24 +266,20 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87eba3c8c7f42ef17f6c659fc7416d0f4758cd3e58861ee63c5fa4a4dde649e4" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] [[package]] -name = "console" -version = "0.15.0" +name = "concurrent-queue" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" dependencies = [ - "encode_unicode", - "libc", - "once_cell", - "terminal_size", - "winapi", + "cache-padded", ] [[package]] @@ -192,15 +292,35 @@ dependencies = [ ] [[package]] -name = "crypto-common" -version = "0.1.3" +name = "crossbeam-utils" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] +[[package]] +name = "ctor" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.3" @@ -212,10 +332,19 @@ dependencies = [ ] [[package]] -name = "encode_unicode" -version = "0.3.6" +name = "event-listener" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] [[package]] name = "fnv" @@ -233,12 +362,80 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.21" @@ -257,9 +454,13 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -267,9 +468,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -287,16 +488,22 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.26.1" +name = "gloo-timers" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] [[package]] name = "hashbrown" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" @@ -352,10 +559,37 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.2" +name = "instant" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" + +[[package]] +name = "js-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] [[package]] name = "lazy_static" @@ -365,9 +599,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.131" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40" [[package]] name = "libloading" @@ -386,6 +620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", + "value-bag", ] [[package]] @@ -400,27 +635,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "miniz_oxide" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys", -] - [[package]] name = "num_cpus" version = "1.13.1" @@ -431,15 +645,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.28.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.13.0" @@ -448,9 +653,15 @@ checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "os_str_bytes" -version = "6.1.0" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" +checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] name = "percent-encoding" @@ -477,6 +688,19 @@ dependencies = [ "servers", ] +[[package]] +name = "polling" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +dependencies = [ + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "winapi", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -509,18 +733,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -555,25 +779,17 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rustc-demangle" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" - [[package]] name = "servers" -version = "0.3.0" +version = "0.1.0" dependencies = [ "anyhow", + "async-std", "async-trait", - "better-panic", "clap", - "futures-util", + "futures", "lazy_static", "libloading", - "tokio", - "tokio-tungstenite", "tracing", "tracing-subscriber", "tungstenite", @@ -601,9 +817,12 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" @@ -629,9 +848,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", @@ -647,16 +866,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "terminal_size" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "textwrap" version = "0.15.0" @@ -665,18 +874,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" dependencies = [ "proc-macro2", "quote", @@ -707,48 +916,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" -[[package]] -name = "tokio" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" -dependencies = [ - "autocfg", - "bytes", - "libc", - "memchr", - "mio", - "num_cpus", - "once_cell", - "pin-project-lite", - "socket2", - "tokio-macros", - "winapi", -] - -[[package]] -name = "tokio-macros" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite", -] - [[package]] name = "tracing" version = "0.1.36" @@ -840,15 +1007,15 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" [[package]] name = "unicode-normalization" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dee68f85cab8cf68dec42158baf3a79a1cdc065a8b103025965d6ccb7f6cbd" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" dependencies = [ "tinyvec", ] @@ -877,18 +1044,119 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "value-bag" +version = "1.0.0-alpha.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" +dependencies = [ + "ctor", + "version_check", +] + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" + +[[package]] +name = "web-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + [[package]] name = "winapi" version = "0.3.9" @@ -919,46 +1187,3 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/Cargo.toml b/Cargo.toml index 367ce18..1832cf1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,29 +4,19 @@ resolver = "2" [package] name = "servers" -description = "Simple TCP server for clients written in Rust with plugins support." -version = "0.3.0" -license = "MIT" -authors = ["MedzikUser "] -homepage = "https://github.com/MedzikUser/servers" -repository = "https://github.com/MedzikUser/servers.git" +version = "0.1.0" edition = "2021" -[profile.release] -lto = true -opt-level = 'z' -codegen-units = 1 +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] anyhow = "1.0.60" +async-std = { version = "1.12.0", features = ["attributes"] } async-trait = "0.1.57" -better-panic = "0.3.0" -lazy_static = "1.4.0" +clap = { version = "3.2.17", features = ["derive"] } libloading = "0.7.3" -tokio-tungstenite = "0.17.2" tracing = "0.1.36" tracing-subscriber = "0.3.15" tungstenite = "0.17.3" -clap = { version = "3.2.16", features = ["derive"] } -futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] } -tokio = { version = "1.20.1", features = ["rt-multi-thread", "macros", "net"] } +futures = "0.3.21" +lazy_static = "1.4.0" diff --git a/Cross.toml b/Cross.toml deleted file mode 100644 index ccb61cf..0000000 --- a/Cross.toml +++ /dev/null @@ -1,3 +0,0 @@ -# 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" diff --git a/README.md b/README.md deleted file mode 100644 index 67d693e..0000000 --- a/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# 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 -[code-size]: https://img.shields.io/github/languages/code-size/MedzikUser/servers?style=for-the-badge&color=c8df52&logo=github -[ci]: https://img.shields.io/github/workflow/status/MedzikUser/servers/Rust/main?style=for-the-badge -[image]:https://socialify.git.ci/MedzikUser/servers/image?description=1&font=KoHo&language=1&owner=1&pattern=Circuit%20Board&theme=Light - -[![docs-rs]](https://servers.medzik.xyz) -[![total-lines]](https://github.com/MedzikUser/servers) -[![code-size]](https://github.com/MedzikUser/servers) -[![ci]](https://github.com/MedzikUser/servers/actions/workflows/build.yml) - -[![image]](https://github.com/MedzikUser/servers) - -A simple TCP server for clients and WebSocket server written in Rust 🦀 which can be extended with plugins. - -## 👨‍💻 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 - -Read the docs from [plugins](https://servers.medzik.xyz/servers/plugins) module. diff --git a/plugin_test/Cargo.toml b/plugin_test/Cargo.toml index 1bebcf2..22a876b 100644 --- a/plugin_test/Cargo.toml +++ b/plugin_test/Cargo.toml @@ -3,6 +3,8 @@ name = "plugin_test" version = "0.1.0" edition = "2021" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + [lib] crate-type = ["dylib"] diff --git a/plugin_test/src/lib.rs b/plugin_test/src/lib.rs index 95a9ca9..01d78f7 100644 --- a/plugin_test/src/lib.rs +++ b/plugin_test/src/lib.rs @@ -1,72 +1,45 @@ -use servers::{ - async_trait, - plugins::{Command, Event, Plugin, PluginManagerType, Registrar, Result}, - tcp::Client, -}; +use servers::plugins::prelude::*; struct PluginTest; -/// Create a new plugin #[async_trait] impl Plugin for PluginTest { /// Name of the plugin. fn name(&self) -> &'static str { - "test" + "test_plugin" } - - /// A function will be executed when plugin loading. - /// Usally used for initialization. - async fn on_plugin_load(&self) {} + /// A function that will be executed when the plugin is loaded. + async fn on_load(&self) {} } -/// Create a new command #[async_trait] impl Command for PluginTest { - /// Command name + /// Name of the command. fn name(&self) -> &'static str { "/test" } - - /// Help message of the command + /// Aliases for the command. + fn aliases(&self) -> Vec<&'static str> { + Vec::new() + } + /// Help message of the command. fn help(&self) -> &'static str { - "Test command from plugin" + "Test commend loaded from dylib" } - - /// Command function - async fn execute( - &self, - client: &mut Client, - _args: Vec<&str>, - _commands: &PluginManagerType, - ) -> Result<()> { - client.send("content").await?; + /// 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")?; Ok(()) } } -/// Create a new event -#[async_trait] -impl Event for PluginTest { - /// Event name (onConnect or onSend) - fn name(&self) -> &'static str { - "onConnect" - } - - /// Event function - async fn execute(&self, client: &mut Client) -> Result<()> { - client - .send(format!("Welcome {}", client.peer_addr().unwrap())) - .await?; - - Ok(()) - } -} - -/// Register plugin #[no_mangle] pub fn plugin_entry(registrar: &mut dyn Registrar) { - registrar.register_plugin(Box::new(PluginTest)); - registrar.register_command(Box::new(PluginTest)); - registrar.register_event(Box::new(PluginTest)); + registrar.register_plugins(Box::new(PluginTest)); + registrar.register_commands(Box::new(PluginTest)); } diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 48fd849..0000000 --- a/renovate.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:base", "schedule:weekly", "group:allNonMajor", ":semanticCommits"], - "labels": ["dependencies"], - "automergeType": "pr", - "prCreation": "immediate", - "packageRules": [ - { - "matchUpdateTypes": ["minor", "patch", "pin", "digest"], - "automerge": true - } - ] -} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..3bf4091 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,10 @@ +# https://rust-lang.github.io/rustfmt + +# stable +edition = "2021" +newline_style = "Unix" + +# nightly +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +format_code_in_doc_comments = true diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index ef589ac..0000000 --- a/src/cli.rs +++ /dev/null @@ -1,43 +0,0 @@ -use clap::Parser; - -#[derive(Parser)] -#[clap( - name = env!("CARGO_PKG_NAME"), - version = env!("CARGO_PKG_VERSION"), - about = env!("CARGO_PKG_DESCRIPTION"), -)] -pub struct Cli { - #[clap( - short = 'h', - long = "host", - default_value = "0.0.0.0", - help = "Tcp server host", - display_order = 1 - )] - pub host: String, - - #[clap( - short = 'p', - long = "port", - default_value = "9999", - help = "Tcp server port [set 0 to random]", - display_order = 2 - )] - pub port: String, - - #[clap( - short = 'w', - long = "ws-port", - default_value = "9998", - help = "WebSocket server port [set 0 to random]", - display_order = 3 - )] - pub ws_port: String, - - #[clap( - long = "enable-websocket", - help = "Enable WebSocket proxy to Tcp [default disabled]", - display_order = 4 - )] - pub ws_enable: bool, -} diff --git a/src/commands/disconnect.rs b/src/commands/disconnect.rs index 767de20..1ce1f04 100644 --- a/src/commands/disconnect.rs +++ b/src/commands/disconnect.rs @@ -1,31 +1,28 @@ -use async_trait::async_trait; -use tokio::io::AsyncWriteExt; +use crate::plugins::prelude::*; -use crate::{ - plugins::{Command, PluginManagerType, Result}, - tcp::Client, -}; - -pub struct CommandDisconnect; +pub struct Disconnect; #[async_trait] -impl Command for CommandDisconnect { +impl Command for Disconnect { fn name(&self) -> &'static str { "/disconnect" } - fn help(&self) -> &'static str { - "Disconnect from the server" + fn aliases(&self) -> Vec<&'static str> { + vec!["/close", "/exit"] } - async fn execute( - &self, - client: &mut Client, - _args: Vec<&str>, - _plugin_manager: &PluginManagerType, - ) -> Result<()> { + 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<()> { // close the connection - client.stream.shutdown().await?; + client.close()?; Ok(()) } diff --git a/src/commands/help.rs b/src/commands/help.rs index d06ad76..8eaa858 100644 --- a/src/commands/help.rs +++ b/src/commands/help.rs @@ -1,38 +1,46 @@ -use async_trait::async_trait; +use crate::plugins::prelude::*; -use crate::{ - plugins::{Command, PluginManagerType, Result}, - 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 { - "Display all available commands" + fn aliases(&self) -> Vec<&'static str> { + vec!["/h", "/?", "?"] } - async fn execute( - &self, - client: &mut Client, - _args: Vec<&str>, - plugin_manager: &PluginManagerType, - ) -> Result<()> { - // Vector which will contain help messages of the commands - let mut help = Vec::new(); + fn help(&self) -> &'static str { + "Show commands help menu" + } - for command in plugin_manager.commands.iter() { - // add a help message for the command - help.push(format!("{} - {}", command.name(), command.help())); + 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, + )) } - // send help message to the client - client.send(help.join("\n\r")).await?; + client.send(msg.join("\n"))?; Ok(()) } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 28d73b7..27b6d18 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,15 +1,9 @@ -//! Build-in commands - mod disconnect; mod help; -use crate::plugins::Command; +use self::{disconnect::Disconnect, help::Help}; +use crate::plugins::prelude::*; -/// Register build-in commands pub fn register_commands() -> Vec> { - // create array with build-in commands - vec![ - Box::new(help::CommandHelp), - Box::new(disconnect::CommandDisconnect), - ] + vec![Box::new(Help), Box::new(Disconnect)] } diff --git a/src/lib.rs b/src/lib.rs index 00c27e9..5af8247 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,29 +1,14 @@ -//! # Servers - Simple TCP and WebSocket 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}; -#![doc(html_root_url = "https://servers.medzik.xyz")] +use lazy_static::lazy_static; + +use crate::tcp::Client; pub mod commands; -pub mod logger; pub mod plugins; pub mod tcp; -pub use async_trait::async_trait; +lazy_static! { + pub static ref CLIENTS: Mutex> = Mutex::new(HashMap::new()); + pub static ref CLIENT_NEXT: Mutex = Mutex::new(0); +} diff --git a/src/logger.rs b/src/logger.rs deleted file mode 100644 index 2760ced..0000000 --- a/src/logger.rs +++ /dev/null @@ -1,9 +0,0 @@ -use tracing::metadata::LevelFilter; - -pub fn init() { - better_panic::install(); - - tracing_subscriber::fmt() - .with_max_level(LevelFilter::TRACE) - .init(); -} diff --git a/src/main.rs b/src/main.rs index fc25c9c..dac552e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,88 +1,46 @@ -mod cli; - use clap::Parser; -use servers::{ - logger, - plugins::loader, - tcp::{handle_connection, handle_websocket, Client}, -}; -use tokio::net::TcpListener; -use tracing::{error, info}; +use servers::tcp::server; -use crate::cli::Cli; +#[derive(Debug, Parser)] +#[clap( + name = env!("CARGO_PKG_NAME"), + 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 = 't', + long = "tcp-port", + help = "TCP server port", + default_value = "9999", + display_order = 2 + )] + tcp_port: u16, + #[clap( + short = 'w', + long = "websocket-port", + help = "WebSocket server port", + default_value = "9998", + display_order = 3 + )] + ws_port: u16, +} -#[tokio::main] -async fn main() -> anyhow::Result<()> { - logger::init(); +fn main() { + tracing_subscriber::fmt().init(); - // parse cli args let args = Cli::parse(); - // if enabled start WebSocket server - if args.ws_enable { - tokio::spawn(start_ws_server( - args.host.clone(), - args.ws_port, - args.port.clone(), - )); - } + 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); - // start tcp server - start_tcp_server(args.host, args.port).await?; - - Ok(()) -} - -/// Start tcp server -async fn start_tcp_server(host: String, port: String) -> anyhow::Result<()> { - // listen TCP server - let listener = TcpListener::bind(format!("{host}:{port}")).await?; - - info!("TCP server started at: {}", listener.local_addr()?); - - // load plugins, commands and events - let plugin_manager = loader()?; - - // Accepts a new incoming connection from this listener. - while let Ok((stream, _address)) = listener.accept().await { - let client = Client::new(stream); - let plugin_manager = plugin_manager.clone(); - - // handle client connection in new thread - tokio::spawn(async move { - // get ip address of the client - let ip = client.peer_addr().expect("failed to get peer address"); - - if let Err(e) = handle_connection(client, plugin_manager).await { - error!("Client {ip}: {e}") - } - }); - } - - // server for a unexpectedly reason be terminated - panic!("TCP server unexpectedly terminated!") -} - -/// Start WebSocket server -async fn start_ws_server(host: String, port: String, tcp_port: String) -> anyhow::Result<()> { - // listen Tcp server - let listener = tokio::net::TcpListener::bind(format!("{host}:{port}")).await?; - - info!("WebSocket server started at: {}", listener.local_addr()?); - - // Accepts a new incoming connection from this listener. - while let Ok((stream, _address)) = listener.accept().await { - let tcp_port = tcp_port.clone(); - tokio::spawn(async { - // get ip address of the client - let ip = stream.peer_addr().expect("failed to get peer address"); - - if let Err(e) = handle_websocket(stream, tcp_port).await { - error!("Client {ip}: {e}") - } - }); - } - - // server for a unexpectedly reason be terminated - panic!("WebSocket server unexpectedly terminated!") + server::run(tcp_host, ws_host).expect("failed to start tcp server"); } diff --git a/src/plugins/load.rs b/src/plugins/load.rs new file mode 100644 index 0000000..560f6c6 --- /dev/null +++ b/src/plugins/load.rs @@ -0,0 +1,52 @@ +use std::{fs, path::Path, sync::Arc}; + +use libloading::{Library, Symbol}; +use tracing::{info, trace}; + +use crate::{ + commands, + plugins::{ + manager::{PluginsManager, PluginsManagerType}, + prelude::*, + }, +}; + +pub fn loader(plugins_dir: &str) -> anyhow::Result { + // 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(); + + // add 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(); + + 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 ()> = + 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); + } + } + + Ok(Arc::new(plugins_manager)) +} diff --git a/src/plugins/loader.rs b/src/plugins/loader.rs deleted file mode 100644 index 7c20603..0000000 --- a/src/plugins/loader.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::{fs, path::Path, sync::Arc}; - -use libloading::{Library, Symbol}; -use tracing::{info, trace}; - -use crate::{commands, plugins::Registrar}; - -use super::{PluginManager, PluginManagerType}; - -/// Plugins and Commands loader -pub fn loader() -> anyhow::Result { - let config_dir = "./plugins"; - - // if config directory doesn't exists, create it - if !Path::new(config_dir).exists() { - fs::create_dir_all(config_dir)?; - } - - // get path to .so lib from command argument - let paths = fs::read_dir(config_dir)?; - - // create a plugin manager - let mut plugin_manager = PluginManager::new(); - - // register default commands - for command in commands::register_commands() { - plugin_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(); - - info!("Loading plugin `{}`", plugin_path); - - // loading library from .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 function `plugin_entry` from library - trace!("Finding symbol `plugin_entry` in `{}`", plugin_path); - let func: Symbol ()> = - lib.get(b"plugin_entry")?; - - // execute initial plugin function - trace!( - "Running function `plugin_entry` from plugin `{}`", - plugin_path - ); - func(&mut plugin_manager); - } - } - - // return a `PluginManager` - Ok(Arc::new(plugin_manager)) -} diff --git a/src/plugins/manager.rs b/src/plugins/manager.rs new file mode 100644 index 0000000..5aa1296 --- /dev/null +++ b/src/plugins/manager.rs @@ -0,0 +1,37 @@ +use core::fmt; +use std::sync::Arc; + +use crate::plugins::prelude::*; + +#[derive(Default)] +pub struct PluginsManager { + /// Vector with all loaded plugins. + pub plugins: Vec>, + /// Vector with all loaded commands. + pub commands: Vec>, + /// Vector with all loaded events. + pub events: Vec>, +} + +impl PluginsManager { + /// Returns an empty PluginsManager + pub fn new() -> PluginsManager { + Self { + plugins: Vec::new(), + commands: Vec::new(), + events: Vec::new(), + } + } +} + +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() + } +} + +pub type PluginsManagerType = Arc; diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 19fbd7a..e19b0c1 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,161 +1,16 @@ -//! 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] -//! servers = { git = "https://github.com/MedzikUser/servers" } -//! ``` -//! -//! In file `src/lib.rs` -//! -//! ```no_run -//! use servers::{plugins::{Plugin, Registrar}, tcp::Client, async_trait}; -//! -//! struct PluginTest; -//! -//! /// Create a new plugin -//! #[async_trait] -//! impl Plugin for PluginTest { -//! /// Name of the plugin. -//! fn name(&self) -> &'static str { -//! "test" -//! } -//! -//! /// A function will be executed when plugin loading. -//! /// Usally used for initialization. -//! async fn on_plugin_load(&self) {} -//! } -//! -//! /// Register plugin -//! #[no_mangle] -//! pub fn plugin_entry(registrar: &mut dyn Registrar) { -//! registrar.register_plugin(Box::new(PluginTest)); -//! } -//! ``` -//! -//! ## Add command -//! -//! ```no_run -//! use servers::{ -//! plugins::{Command, PluginManagerType, Registrar, Result, async_trait}, -//! tcp::Client, -//! }; -//! # -//! # struct PluginTest; -//! # -//! # #[async_trait] -//! # impl servers::plugins::Plugin for PluginTest { -//! # /// Name of the plugin. -//! # fn name(&self) -> &'static str { -//! # "test" -//! # } -//! # -//! # /// A function will be executed when plugin loading. -//! # /// Usally used for initialization. -//! # async fn on_plugin_load(&self) {} -//! # } -//! -//! /// Create a new command -//! #[async_trait] -//! impl Command for PluginTest { -//! /// Command name -//! fn name(&self) -> &'static str { -//! "/test" -//! } -//! -//! /// Help message of the command -//! fn help(&self) -> &'static str { -//! "test command" -//! } -//! -//! /// Command function -//! async fn execute(&self, client: &mut Client, _args: Vec<&str>, _commands: &PluginManagerType) -> Result<()> { -//! client.send("Command executed!").await?; -//! -//! Ok(()) -//! } -//! } -//! -//! /// Register plugin -//! #[no_mangle] -//! pub fn plugin_entry(registrar: &mut dyn Registrar) { -//! # registrar.register_plugin(Box::new(PluginTest)); -//! registrar.register_command(Box::new(PluginTest)); -//! } -//! ``` -//! -//! ## Add event -//! -//! In file `src/lib.rs` -//! -//! ```no_run -//! use servers::{ -//! plugins::{Event, Registrar, Result, async_trait}, -//! tcp::Client, -//! }; -//! # -//! # struct PluginTest; -//! # -//! # #[async_trait] -//! # impl servers::plugins::Plugin for PluginTest { -//! # /// Name of the plugin. -//! # fn name(&self) -> &'static str { -//! # "test" -//! # } -//! # -//! # /// A function will be executed when plugin loading. -//! # /// Usally used for initialization. -//! # async fn on_plugin_load(&self) {} -//! # } -//! -//! /// Create a new event -//! #[async_trait] -//! impl Event for PluginTest { -//! /// Event name (onConnect or onSend) -//! fn name(&self) -> &'static str { -//! "onConnect" -//! } -//! -//! /// Event function -//! async fn execute(&self, client: &mut Client) -> Result<()> { -//! client -//! .send(format!("Welcome {}", client.peer_addr()?)) -//! .await?; -//! -//! Ok(()) -//! } -//! } -//! -//! /// Register plugin -//! #[no_mangle] -//! pub fn plugin_entry(registrar: &mut dyn Registrar) { -//! # registrar.register_plugin(Box::new(PluginTest)); -//! registrar.register_event(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 (or create a symlink) the built plugin to the `plugin/` directory in the server root directory. +mod load; +pub mod manager; +pub mod types; -mod loader; -mod types; +pub use load::*; -pub use loader::*; -pub use types::*; +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::tcp::Client; +} diff --git a/src/plugins/types.rs b/src/plugins/types.rs index 0908b77..783a68e 100644 --- a/src/plugins/types.rs +++ b/src/plugins/types.rs @@ -1,20 +1,16 @@ -use std::{any::Any, sync::Arc}; +use std::any::Any; use async_trait::async_trait; -use crate::tcp::Client; +use crate::{plugins::manager::PluginsManager, tcp::Client}; -/// Custom Result alias, imported from [anyhow::Result]. -pub type Result = anyhow::Result; - -/// A plugin wich allows you to add extra functionality. +// A main plugin trait. #[async_trait] pub trait Plugin: Any + Send + Sync { /// Name of the plugin. fn name(&self) -> &'static str; - /// A function will be executed when plugin loading. - /// Usally used for initialization. - async fn on_plugin_load(&self); + /// A function that will be executed when the plugin is loaded. + async fn on_load(&self); } /// Add a command to the plugin. @@ -22,76 +18,52 @@ pub trait Plugin: Any + Send + Sync { 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; - /// Command function - async fn execute( - &self, - client: &mut Client, - args: Vec<&str>, - plugin_manager: &PluginManagerType, - ) -> Result<()>; + /// Usage message of the command. + fn usage(&self) -> &'static str; + /// Command function. + async fn execute(&self, client: &Client, args: Vec<&str>) -> anyhow::Result<()>; } -/// Add a new function that will be executed when the event occurs. +/// All possible to run events. +pub enum EventType { + /// On client connected. + OnConnect, + /// On client sent message. + OnSend, +} + +/// Add a event to the plugin. #[async_trait] pub trait Event: Any + Send + Sync { - /// Event name (onConnect or onSend) - fn name(&self) -> &'static str; - /// Event function - async fn execute(&self, client: &mut Client) -> Result<()>; + /// Type of the event. + fn event(&self) -> EventType; + /// Event function. + async fn execute(&self) -> anyhow::Result<()>; } -/// Plugin Manager with all plugins features. -pub struct PluginManager { - /// Array with loaded plugins. - pub plugins: Vec>, - /// Array with all commands. - pub commands: Vec>, - /// Array with all events. - pub events: Vec>, -} - -impl PluginManager { - /// Create an empty [PluginManager] - pub fn new() -> Self { - Self { - plugins: Vec::new(), - commands: Vec::new(), - events: Vec::new(), - } - } -} - -impl Default for PluginManager { - fn default() -> Self { - Self::new() - } -} - -/// Arc type of the [PluginManager]. -pub type PluginManagerType = Arc; - -/// Plugin Registrar pub trait Registrar { - /// Function to register the plugin - fn register_plugin(&mut self, plugin: Box); - /// Function to register the command - fn register_command(&mut self, command: Box); - /// Function to register the event - fn register_event(&mut self, event: Box); + /// Function to register plugins. + fn register_plugins(&mut self, plugin: Box); + /// Function to register commands. + fn register_commands(&mut self, command: Box); + /// Function to register events. + fn register_events(&mut self, event: Box); } -impl Registrar for PluginManager { - fn register_plugin(&mut self, plugin: Box) { +impl Registrar for PluginsManager { + fn register_plugins(&mut self, plugin: Box) { self.plugins.push(plugin) } - fn register_command(&mut self, command: Box) { + fn register_commands(&mut self, command: Box) { self.commands.push(command) } - fn register_event(&mut self, event: Box) { + fn register_events(&mut self, event: Box) { self.events.push(event) } } diff --git a/src/tcp/client.rs b/src/tcp/client.rs index 93d7ebc..8afc426 100644 --- a/src/tcp/client.rs +++ b/src/tcp/client.rs @@ -1,61 +1,166 @@ -use core::fmt; -use std::net::SocketAddr; - -use tokio::{ - io::{self, AsyncReadExt, AsyncWriteExt}, - net::TcpStream, +use std::{ + collections::HashMap, + fmt, + io::{Read, Write}, + net::{Shutdown, SocketAddr, TcpStream}, + sync::{Arc, Mutex}, }; -/// Max size of a TCP packet +use tungstenite::{accept, Message, WebSocket}; + +use super::server::PLUGINS_MANAGER; +use crate::plugins::manager::PluginsManagerType; + +/// Max length of a TCP and UDP packet pub const MAX_PACKET_LEN: usize = 65536; -/// TCP Client +#[derive(Debug, Clone)] pub struct Client { - /// TCP stream of this client - pub stream: TcpStream, + pub stream: ClientStream, + pub map: HashMap, + pub plugins_manager: PluginsManagerType, +} + +#[derive(Debug, Clone)] +pub enum ClientMapValue { + /// String type + String(String), + /// Vector with String type + Array(Vec), + /// bool type + Bool(bool), + /// isize type + Int(isize), + /// usize type + UInt(usize), +} + +/// Connection stream of the client +#[derive(Debug, Clone)] +pub enum ClientStream { + /// TCP stream + TCP(Arc), + /// WebSocket stream + WebSocket(Arc>>), +} + +impl From for Client { + fn from(stream: TcpStream) -> Self { + Self { + stream: ClientStream::TCP(Arc::new(stream)), + map: HashMap::new(), + plugins_manager: PLUGINS_MANAGER.clone(), + } + } +} + +impl From> for Client { + fn from(stream: WebSocket) -> Self { + Self { + stream: ClientStream::WebSocket(Arc::new(Mutex::new(stream))), + map: HashMap::new(), + plugins_manager: PLUGINS_MANAGER.clone(), + } + } } impl Client { - /// Create new Client - pub fn new(stream: TcpStream) -> Self { - Self { stream } + /// Create a new TCP Client instance + pub fn new_tcp(stream: TcpStream) -> Self { + Self::from(stream) } - /// Read message/buffer from the client - pub async fn read(&mut self) -> anyhow::Result { - // allocate an empty buffer - let mut buf = [0; MAX_PACKET_LEN]; + /// Create a new WebSocket Client instance + pub fn new_websocket(stream: TcpStream) -> anyhow::Result { + let websocket = accept(stream)?; - // read buffer from stream - let len = self.stream.read(&mut buf).await?; + Ok(Self::from(websocket)) + } - // select only used bytes from the buffer - let buf = &buf[0..len]; + /// Recieve a message from the client + pub fn read(&self) -> anyhow::Result { + // 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]; - // encode buffer (&[u8]) to a String - let mut decoded = String::from_utf8(buf.to_vec())?; + // 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 decoded.ends_with('\n') || decoded.ends_with('\r') { - decoded.pop(); + while msg.ends_with('\n') || msg.ends_with('\r') { + msg.pop(); } - Ok(decoded) + Ok(msg) } - /// Send message to the client - pub async fn send(&mut self, content: S) -> io::Result<()> + /// Send a message to the client + pub fn send(&self, msg: S) -> anyhow::Result<()> where - S: ToString + fmt::Display, + S: ToString, + S: fmt::Display, { - // add a new line at the end of the content - let content = format!("{content}\n\r"); + // convert the message into a string + let msg = msg.to_string(); - // send message - self.stream.write_all(content.as_bytes()).await + // 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(msg))? + } + } + + Ok(()) } - pub fn peer_addr(&self) -> io::Result { - self.stream.peer_addr() + /// Returns the socket address of the remote peer of this connection. + pub fn peer_addr(&self) -> anyhow::Result { + 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 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(()) } } diff --git a/src/tcp/handle_connection.rs b/src/tcp/handle_connection.rs deleted file mode 100644 index 70c4185..0000000 --- a/src/tcp/handle_connection.rs +++ /dev/null @@ -1,97 +0,0 @@ -use tokio::io::AsyncWriteExt; -use tracing::{error, info, trace}; - -use crate::plugins::PluginManagerType; - -use super::Client; - -/// Handle Client connection -pub async fn handle_connection( - mut client: Client, - plugin_manager: PluginManagerType, -) -> anyhow::Result<()> { - info!("New Client: {}", client.peer_addr()?); - - // run `onConnect` events from plugins - check_event(&mut client, &plugin_manager, "onConnect").await?; - - loop { - // read client message/buffer - let buf = client.read().await?; - - // run `onSend` events from plugins - check_event(&mut client, &plugin_manager, "onSend").await?; - - // split the message by whitespaces and collect it into Vec<&str> - let mut args = buf.split_ascii_whitespace().collect::>(); - - // client sent an empty buffer - if args.is_empty() { - client.send("empty buffer").await?; - - // don't execute the following commands because it causes panic - continue; - } - - // get command from args - let cmd = args[0]; - - // remove command name from args - args = args[1..args.len()].to_vec(); - - // search if a command exists - for command in plugin_manager.commands.iter() { - // if this is the entered command - if cmd == command.name() { - trace!("Executing a command `{}`", command.name()); - - // execute command - match command.execute(&mut client, args, &plugin_manager).await { - Ok(_) => (), - Err(err) => { - error!("failed to execute command `{cmd}`, error message = `{err}`"); - - client.send(format!("error: {err}")).await?; - } - } - - // don't search for more commands - break; - } - } - - // if an I/O or EOF error, abort the connection - if client.stream.flush().await.is_err() { - // terminate connection - break; - } - } - - Ok(()) -} - -/// Search for a events and execute it -async fn check_event( - client: &mut Client, - events: &PluginManagerType, - event_name: &str, -) -> anyhow::Result<()> { - 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 - match event.execute(client).await { - Ok(_) => (), - Err(err) => { - error!("failed to execute event `{event_name}`, error message = `{err}`"); - - client.send(format!("error: {err}")).await?; - } - } - } - } - - Ok(()) -} diff --git a/src/tcp/handle_websocket.rs b/src/tcp/handle_websocket.rs deleted file mode 100644 index a97d982..0000000 --- a/src/tcp/handle_websocket.rs +++ /dev/null @@ -1,85 +0,0 @@ -#![allow(clippy::unused_io_amount)] - -use futures_util::{ - stream::{SplitSink, SplitStream}, - SinkExt, StreamExt, -}; -use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - net::{ - tcp::{OwnedReadHalf, OwnedWriteHalf}, - TcpStream, - }, -}; -use tokio_tungstenite::WebSocketStream; -use tracing::info; -use tungstenite::Message; - -use super::MAX_PACKET_LEN; - -/// Handle WebSocket connection -pub async fn handle_websocket(stream: TcpStream, tcp_port: String) -> anyhow::Result<()> { - info!("New WebSocket Client: {}", stream.peer_addr()?); - - // accept connection as WebSocket - let ws_stream = tokio_tungstenite::accept_async(stream).await?; - - // connect to Tcp server - let tcp_stream = TcpStream::connect(format!("0.0.0.0:{}", tcp_port)).await?; - - // split streams - let (tcp_read, tcp_write) = tcp_stream.into_split(); - let (ws_write, ws_read) = ws_stream.split(); - - // tcp read -> ws write - tokio::spawn(tcp_to_ws(tcp_read, ws_write)); - - // ws read -> tcp write - ws_to_tcp(tcp_write, ws_read).await?; - - Ok(()) -} - -/// Tcp read -> WebSocket write -async fn tcp_to_ws( - mut tcp_read: OwnedReadHalf, - mut ws_write: SplitSink, Message>, -) -> anyhow::Result<()> { - // allocate an empty buffer - let mut buf = [0; MAX_PACKET_LEN]; - - loop { - // read buffer from tcp - let len = tcp_read.read(&mut buf).await?; - - if len > 0 { - // select only used bytes from the buffer - let recv_buf = &buf[0..len]; - // covert &[u8] buffer to a vector - let recv_vec = recv_buf.to_vec(); - // create a `Message` type from buffer Vec - let msg = Message::Binary(recv_vec); - - // write buffer to websocket - ws_write.send(msg).await?; - } - } -} - -/// WebSocket read -> Tcp write -async fn ws_to_tcp( - mut tcp_write: OwnedWriteHalf, - mut ws_read: SplitStream>, -) -> anyhow::Result<()> { - while let Some(msg) = ws_read.next().await { - // handle error in the message - let msg = msg?; - // create a buffer from a message - let buf = msg.into_data(); - - // write buffer to tcp - tcp_write.write(&buf).await?; - } - - Ok(()) -} diff --git a/src/tcp/mod.rs b/src/tcp/mod.rs index 8324753..6db2673 100644 --- a/src/tcp/mod.rs +++ b/src/tcp/mod.rs @@ -1,9 +1,4 @@ -//! TCP connection utils - mod client; -mod handle_connection; -mod handle_websocket; +pub mod server; pub use client::*; -pub use handle_connection::*; -pub use handle_websocket::*; diff --git a/src/tcp/server.rs b/src/tcp/server.rs new file mode 100644 index 0000000..48ade3f --- /dev/null +++ b/src/tcp/server.rs @@ -0,0 +1,139 @@ +use std::net::TcpListener; + +use async_std::task; +use futures::join; +use lazy_static::lazy_static; +use tracing::{error, info}; + +use crate::{ + plugins::{self, manager::PluginsManagerType}, + tcp::Client, + CLIENTS, CLIENT_NEXT, +}; + +pub const PLUGINS_DIR: &str = "plugins"; + +lazy_static! { + pub static ref PLUGINS_MANAGER: PluginsManagerType = + plugins::loader(PLUGINS_DIR).expect("failed to load plugins"); +} + +/// Start server +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); + + loop { + let buf = client.read()?; + + let mut args: Vec<&str> = buf.split_ascii_whitespace().collect(); + + // if client sent an empty buffer + if buf.is_empty() { + client.send("empty buffer")?; + continue; + } + + 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 let Some((_i, cmd)) = command { + cmd.execute(&client, args).await?; + } else { + client.send("unknown command")?; + } + + 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?; + + task::spawn(async { + let client = Client::new_tcp(stream); + + // get id for the client and add one to next id + let id = (*CLIENT_NEXT.lock().unwrap() + 1).clone(); + + // insert the cloned client to CLIENTS + CLIENTS.lock().unwrap().insert(id, client.clone()); + + if let Err(err) = process(client).await { + error!("TCP client 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?; + + task::spawn(async { + let client = Client::new_websocket(stream).unwrap(); + + // get id for the client and add one to next id + let id = (*CLIENT_NEXT.lock().unwrap() + 1).clone(); + + // insert the cloned client to CLIENTS + CLIENTS.lock().unwrap().insert(id, client.clone()); + + if let Err(err) = process(client).await { + error!("TCP client error: {}", err); + } + + // delete the client from CLIENTS map + CLIENTS.lock().unwrap().remove(&id); + }); + } + + Ok(()) +}