From b4c707db0db3b2d57725fb08106e75381bbdee2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= Date: Sun, 7 Mar 2021 02:56:24 +0100 Subject: [PATCH] actors, messages & ws commands created --- Cargo.lock | 35 +++++++++++++-- Cargo.toml | 4 +- src/actors/chat_server.rs | 63 +++++++++++++++++++++++++++ src/actors/chat_session.rs | 68 ++++++++++++++++++++++++++++++ src/actors/mod.rs | 2 + src/bin/chat_app.rs | 12 +++--- src/models/commands/mod.rs | 31 ++++++++++++++ src/models/messages/chat_server.rs | 29 +++++++++++++ src/models/messages/mod.rs | 1 + src/models/mod.rs | 16 ++++++- src/models/user.rs | 0 src/routes/mod.rs | 20 --------- src/routes/user.rs | 0 src/routes/ws.rs | 13 ++++++ src/server.rs | 20 +++------ 15 files changed, 268 insertions(+), 46 deletions(-) create mode 100644 src/actors/chat_session.rs create mode 100644 src/models/commands/mod.rs create mode 100644 src/models/messages/chat_server.rs create mode 100644 src/models/messages/mod.rs delete mode 100644 src/models/user.rs delete mode 100644 src/routes/user.rs diff --git a/Cargo.lock b/Cargo.lock index f08e07a..1ee2ace 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,6 +124,8 @@ dependencies = [ "actix", "actix-web", "actix-web-actors", + "derive_more", + "uuid", ] [[package]] @@ -762,7 +764,18 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", ] [[package]] @@ -1219,7 +1232,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.16", "libc", "rand_chacha", "rand_core", @@ -1242,7 +1255,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.16", ] [[package]] @@ -1742,6 +1755,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.2", + "serde", +] + [[package]] name = "version_check" version = "0.9.2" @@ -1754,6 +1777,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + [[package]] name = "wasm-bindgen" version = "0.2.71" diff --git a/Cargo.toml b/Cargo.toml index ac21fe1..3a5707b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,6 @@ path = "src/lib.rs" [dependencies] actix = "0.10.0" actix-web = "3" -actix-web-actors = "3.0.0" \ No newline at end of file +actix-web-actors = "3.0.0" +uuid = { version = "0.8", features = ["serde", "v4"] } +derive_more = "0.99.11" \ No newline at end of file diff --git a/src/actors/chat_server.rs b/src/actors/chat_server.rs index e69de29..a3d912d 100644 --- a/src/actors/chat_server.rs +++ b/src/actors/chat_server.rs @@ -0,0 +1,63 @@ +use actix::{Actor, Context, Handler, MessageResult, Recipient}; +use std::collections::{HashMap, HashSet}; +use uuid::Uuid; + +use crate::models::{ + messages::chat_server::{Connect, Disconnect, Message}, + RoomId, SessionId, +}; + +pub struct ChatServer { + sessions: HashMap>, + rooms: HashMap>, +} + +impl ChatServer { + pub fn new() -> Self { + ChatServer { + sessions: HashMap::new(), + rooms: HashMap::new(), + } + } + + pub fn send_message(&self, room: &Uuid, message: &'static str, skip_id: &Uuid) { + self.rooms.get(room).map(|sessions| { + sessions.iter().for_each(|id| { + if id != skip_id { + self.sessions + .get(id) + .map(|addr| addr.do_send(Message(message))); + } + }); + }); + } +} + +impl Actor for ChatServer { + type Context = Context; +} + +impl Handler for ChatServer { + type Result = MessageResult; + + fn handle(&mut self, msg: Connect, _ctx: &mut Self::Context) -> Self::Result { + let session_id = Uuid::new_v4(); + self.sessions.insert(session_id, msg.addr); + MessageResult(session_id) + } +} + +impl Handler for ChatServer { + type Result = (); + + fn handle( + &mut self, + Disconnect { session }: Disconnect, + _ctx: &mut Self::Context, + ) -> Self::Result { + for (_id, sessions) in self.rooms.iter_mut() { + sessions.remove(&session); + } + let _ = self.sessions.remove(&session); + } +} diff --git a/src/actors/chat_session.rs b/src/actors/chat_session.rs new file mode 100644 index 0000000..e4754ba --- /dev/null +++ b/src/actors/chat_session.rs @@ -0,0 +1,68 @@ +use crate::{ + actors::chat_server::ChatServer, + models::messages::chat_server::{Connect, Disconnect, Message}, +}; +use actix::*; +use actix::{Actor, Addr, AsyncContext}; +use actix_web_actors::ws::{self, WebsocketContext}; +use uuid::Uuid; + +pub struct WsChatSession { + pub id: Option, + pub room: Option, + pub addr: Addr, +} + +impl WsChatSession { + pub fn new(addr: Addr) -> Self { + WsChatSession { + id: None, + room: None, + addr: addr, + } + } +} + +impl Actor for WsChatSession { + type Context = WebsocketContext; + + fn started(&mut self, ctx: &mut Self::Context) { + let addr = ctx.address(); + self.addr + .send(Connect { + addr: addr.recipient(), + }) + .into_actor(self) + .then(|res, act, ctx| { + match res { + Ok(res) => act.id = Some(res), + // something is wrong with chat server + _ => ctx.stop(), + } + fut::ready(()) + }) + .wait(ctx); + } + + fn stopping(&mut self, _: &mut Self::Context) -> Running { + // notify chat server + if let Some(id) = self.id { + self.addr.do_send(Disconnect { session: id }); + } + Running::Stop + } +} + +impl Handler for WsChatSession { + type Result = (); + + fn handle(&mut self, msg: Message, ctx: &mut Self::Context) -> Self::Result { + ctx.text(msg.0); + } +} + +impl StreamHandler> for WsChatSession { + fn handle(&mut self, item: Result, ctx: &mut Self::Context) { + //TODO: Implement receiving message + } +} diff --git a/src/actors/mod.rs b/src/actors/mod.rs index e69de29..37abea7 100644 --- a/src/actors/mod.rs +++ b/src/actors/mod.rs @@ -0,0 +1,2 @@ +pub mod chat_server; +pub mod chat_session; \ No newline at end of file diff --git a/src/bin/chat_app.rs b/src/bin/chat_app.rs index a289d05..69db70d 100644 --- a/src/bin/chat_app.rs +++ b/src/bin/chat_app.rs @@ -1,12 +1,10 @@ use actix_web::{App, HttpServer}; -use lib::server::{app_data, routes}; +use lib::server::init; #[actix_web::main] async fn main() -> std::io::Result<()> { - HttpServer::new(|| { - return App::new().configure(app_data).configure(routes); - }) - .bind("127.0.0.1:8080")? - .run() - .await + HttpServer::new(|| App::new().configure(init)) + .bind("127.0.0.1:8080")? + .run() + .await } diff --git a/src/models/commands/mod.rs b/src/models/commands/mod.rs new file mode 100644 index 0000000..93e1d47 --- /dev/null +++ b/src/models/commands/mod.rs @@ -0,0 +1,31 @@ +use derive_more::{Display, Error}; +use std::str::FromStr; +use uuid::Uuid; + +use super::RoomId; + +#[derive(Debug, Display, Error)] +#[display(fmt = "Invalid command")] +pub struct CommandError; + +pub enum Command { + Create, + Join(RoomId), +} + +impl FromStr for Command { + type Err = CommandError; + + fn from_str(s: &str) -> Result { + if s.starts_with("/create") { + Ok(Command::Create) + } else if s.starts_with("/join") { + let words: Vec<&str> = s.split_whitespace().into_iter().collect(); + let uuid = words.last().map(|&v| v).unwrap_or(""); + let uuid = Uuid::from_str(uuid).map_err(|_| CommandError)?; + Ok(Command::Join(uuid)) + } else { + Err(CommandError) + } + } +} diff --git a/src/models/messages/chat_server.rs b/src/models/messages/chat_server.rs new file mode 100644 index 0000000..4e44810 --- /dev/null +++ b/src/models/messages/chat_server.rs @@ -0,0 +1,29 @@ +use actix::{Message as ActixMessage, Recipient}; +use uuid::Uuid; +#[derive(ActixMessage)] +#[rtype(result = "()")] +pub struct Message(pub &'static str); + +#[derive(ActixMessage)] +#[rtype(result = "Uuid")] +pub struct CreateRoom { + pub session: Uuid, +} +#[derive(ActixMessage)] +#[rtype(result = "()")] +pub struct JoinRoom { + pub session: Uuid, + pub room: Uuid, +} + +#[derive(ActixMessage)] +#[rtype(result = "Uuid")] +pub struct Connect { + pub addr: Recipient, +} + +#[derive(ActixMessage)] +#[rtype(result = "()")] +pub struct Disconnect { + pub session: Uuid, +} diff --git a/src/models/messages/mod.rs b/src/models/messages/mod.rs new file mode 100644 index 0000000..9c625f7 --- /dev/null +++ b/src/models/messages/mod.rs @@ -0,0 +1 @@ +pub mod chat_server; \ No newline at end of file diff --git a/src/models/mod.rs b/src/models/mod.rs index 018ff2e..85598e6 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1 +1,15 @@ -pub mod user; \ No newline at end of file +use actix::Addr; +use uuid::Uuid; + +use crate::actors::chat_server::ChatServer; + +pub mod messages; +pub mod commands; + +pub type SessionId = Uuid; +pub type RoomId = Uuid; + +pub struct AppState { + pub chat: Addr, +} + diff --git a/src/models/user.rs b/src/models/user.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/routes/mod.rs b/src/routes/mod.rs index e10cf83..e9b7289 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,23 +1,3 @@ -pub mod user; pub mod ws; -use crate::server::AppState; -use actix_web::{get, post, web, web::Data, HttpResponse, Responder}; - -#[get("/")] -pub async fn hello(app: Data) -> impl Responder { - let app_name = &app.app_name; - println!("App name: {}", app_name); - HttpResponse::Ok().body("Hello world!") -} - -#[post("/echo")] -pub async fn echo(req_body: String) -> impl Responder { - HttpResponse::Ok().body(req_body) -} - -pub async fn manual_hello() -> impl Responder { - HttpResponse::Ok().body("Hey there!") -} - diff --git a/src/routes/user.rs b/src/routes/user.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/routes/ws.rs b/src/routes/ws.rs index e69de29..3f60649 100644 --- a/src/routes/ws.rs +++ b/src/routes/ws.rs @@ -0,0 +1,13 @@ +use actix_web::{web, HttpRequest, Responder}; +use actix_web_actors::ws; + +use crate::{actors::chat_session::WsChatSession, models::AppState}; + +pub async fn connect( + req: HttpRequest, + stream: web::Payload, + state: web::Data, +) -> impl Responder { + let chat = state.chat.clone(); + ws::start(WsChatSession::new(chat), &req, stream) +} diff --git a/src/server.rs b/src/server.rs index 4b71667..f474fe0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,18 +1,10 @@ -use crate::routes::{echo, hello, manual_hello}; +use crate::{actors::chat_server::ChatServer, models::AppState, routes::ws::connect}; +use actix::Actor; use actix_web::web; -pub struct AppState { - pub app_name: String, -} +pub fn init(app: &mut web::ServiceConfig) { + let chat = ChatServer::new().start(); -pub fn app_data(cfg: &mut web::ServiceConfig) { - cfg.data(AppState { - app_name: String::from("Testing!"), - }); -} - -pub fn routes(cfg: &mut web::ServiceConfig) { - cfg.service(hello) - .service(echo) - .route("/hey", web::get().to(manual_hello)); + app.data(AppState { chat }) + .service(web::resource("/ws/").to(connect)); }