actors, messages & ws commands created

This commit is contained in:
Víctor Martínez 2021-03-07 02:56:24 +01:00
parent 392adc8c56
commit b4c707db0d
15 changed files with 268 additions and 46 deletions

35
Cargo.lock generated
View file

@ -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"

View file

@ -12,4 +12,6 @@ path = "src/lib.rs"
[dependencies]
actix = "0.10.0"
actix-web = "3"
actix-web-actors = "3.0.0"
actix-web-actors = "3.0.0"
uuid = { version = "0.8", features = ["serde", "v4"] }
derive_more = "0.99.11"

View file

@ -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<SessionId, Recipient<Message>>,
rooms: HashMap<RoomId, HashSet<SessionId>>,
}
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<Self>;
}
impl Handler<Connect> for ChatServer {
type Result = MessageResult<Connect>;
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<Disconnect> 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);
}
}

View file

@ -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<Uuid>,
pub room: Option<Uuid>,
pub addr: Addr<ChatServer>,
}
impl WsChatSession {
pub fn new(addr: Addr<ChatServer>) -> Self {
WsChatSession {
id: None,
room: None,
addr: addr,
}
}
}
impl Actor for WsChatSession {
type Context = WebsocketContext<Self>;
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<Message> for WsChatSession {
type Result = ();
fn handle(&mut self, msg: Message, ctx: &mut Self::Context) -> Self::Result {
ctx.text(msg.0);
}
}
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WsChatSession {
fn handle(&mut self, item: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
//TODO: Implement receiving message
}
}

View file

@ -0,0 +1,2 @@
pub mod chat_server;
pub mod chat_session;

View file

@ -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
}

View file

@ -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<Self, Self::Err> {
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)
}
}
}

View file

@ -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<Message>,
}
#[derive(ActixMessage)]
#[rtype(result = "()")]
pub struct Disconnect {
pub session: Uuid,
}

View file

@ -0,0 +1 @@
pub mod chat_server;

View file

@ -1 +1,15 @@
pub mod user;
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<ChatServer>,
}

View file

View file

@ -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<AppState>) -> 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!")
}

View file

View file

@ -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<AppState>,
) -> impl Responder {
let chat = state.chat.clone();
ws::start(WsChatSession::new(chat), &req, stream)
}

View file

@ -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));
}