diff --git a/server/kafka/kafka-script-proxy/src/main.rs b/server/kafka/kafka-script-proxy/src/main.rs index c0ddf71..c3c933c 100644 --- a/server/kafka/kafka-script-proxy/src/main.rs +++ b/server/kafka/kafka-script-proxy/src/main.rs @@ -8,7 +8,7 @@ use tokio::fs::OpenOptions; use tokio::io::AsyncWriteExt; use tokio::process::Command; -const TOPICS: [&str; 3] = ["move", "status", "input"]; +const TOPICS: [&str; 4] = ["move", "status", "input", "session"]; #[tokio::main] pub async fn main() { diff --git a/server/src/http.rs b/server/src/http.rs index 2e82415..d1649b3 100644 --- a/server/src/http.rs +++ b/server/src/http.rs @@ -2,6 +2,7 @@ use std::convert::Infallible; use std::fs::read; use std::io::ErrorKind::NotFound; use std::net::SocketAddr; +use std::str::FromStr; use std::sync::Arc; use hyper::{Body, body, Method, Request, Response, Server, StatusCode}; use hyper::server::conn::AddrStream; @@ -16,17 +17,17 @@ use pong::event::event::{Event, EventReader, EventWriter}; use futures::{sink::SinkExt, stream::StreamExt}; use crate::kafka::{KafkaEventReaderImpl, KafkaSessionEventWriterImpl}; use crate::player::Player; -use crate::session::{CachingSessionManager, SessionManager}; +use crate::session::{SessionManager}; use crate::utils::http_utils::{get_query_params, read_json_body}; pub struct HttpServer { addr: [u8; 4], port: u16, - session_manager: Arc> + session_manager: Arc> } impl HttpServer { pub fn new(addr: [u8; 4], port: u16, kafka_host: &str) -> HttpServer { - let session_manager = Arc::new(Mutex::new(CachingSessionManager::new(kafka_host))); + let session_manager = Arc::new(Mutex::new(SessionManager::new(kafka_host))); HttpServer {addr, port, session_manager} } @@ -39,12 +40,40 @@ impl HttpServer { let mut session_manager = Arc::clone(&session_manager); async move { if hyper_tungstenite::is_upgrade_request(&req) { + println!("Received request to upgrade to websocket connection: {:?}", req); + let params = get_query_params(&req); + println!("Ws request params: {:?}", params); + if !params.contains_key("session_id") { + eprintln!("Missing session id request param for websocket connection, don't upgrade connection to ws."); + return build_error_res("Missing request param: session_id", StatusCode::BAD_REQUEST); + } + if !params.contains_key("connection_type") { + eprintln!("Missing connection type request param for websocket connection, don't upgrade connection to ws."); + let res = build_error_res("Missing request param: connection_type", StatusCode::BAD_REQUEST); + return res; + } + let session_id = params.get("session_id").unwrap(); + let connection_type_raw = params.get("connection_type").unwrap(); + let connection_type = WebSocketConnectionType::from_str(connection_type_raw); + if let Err(_) = connection_type { + let error = format!("Invalid connection type: {}", connection_type_raw); + eprintln!("{}", error); + return build_error_res(error.as_str(), StatusCode::BAD_REQUEST); + } + if let None = session_manager.lock().await.get_session(session_id) { + let error = format!("Session does not exist: {}", session_id); + eprintln!("{}", error); + return build_error_res(error.as_str(), StatusCode::NOT_FOUND); + } + let websocket_session = WebSocketSession {session_id: session_id.to_string(), connection_type: connection_type.unwrap()}; + println!("Websocket upgrade request is valid, will now upgrade to websocket: {:?}", req); + let (response, websocket) = hyper_tungstenite::upgrade(req, None).unwrap(); // Spawn a task to handle the websocket connection. tokio::spawn(async move { - if let Err(e) = serve_websocket(websocket, session_manager).await { - eprintln!("Error in websocket connection: {}", e); + if let Err(e) = serve_websocket(websocket_session, websocket, session_manager).await { + eprintln!("Error in websocket connection: {:?}", e); } }); @@ -68,10 +97,21 @@ impl HttpServer { } /// Handle a websocket connection. -async fn serve_websocket(websocket: HyperWebsocket, session_manager: Arc>) -> Result<(), Error> { +async fn serve_websocket(websocket_session: WebSocketSession, websocket: HyperWebsocket, session_manager: Arc>) -> Result<(), Error> { let mut websocket = websocket.await?; + let (mut write, mut read) = websocket.split(); + + let session_manager_reader = Arc::clone(&session_manager); + let websocket_session_read_copy = websocket_session.clone(); tokio::spawn(async move { - while let Some(message) = websocket.next().await { + let mut locked = session_manager_reader.lock().await; + let writer = locked.get_session_writer(&websocket_session_read_copy.session_id); + if let Err(e) = writer { + eprintln!("Failed to retrieve session writer for session {}: {}", websocket_session_read_copy.session_id, e); + return + } + let mut writer = writer.unwrap(); + while let Some(message) = read.next().await { match message.unwrap() { Message::Text(msg) => { let events = serde_json::from_str::(&msg); @@ -80,15 +120,12 @@ async fn serve_websocket(websocket: HyperWebsocket, session_manager: Arc { @@ -112,16 +151,28 @@ async fn serve_websocket(websocket: HyperWebsocket, session_manager: Arc>, req: Request, addr: SocketAddr) -> Result, Infallible> { +async fn handle_request(session_manager: &Arc>, req: Request, addr: SocketAddr) -> Result, Infallible> { println!("req to {} with method {}", req.uri().path(), req.method()); match (req.method(), req.uri().path()) { (&Method::GET, "/session") => handle_get_session(session_manager, req).await, @@ -133,7 +184,7 @@ async fn handle_request(session_manager: &Arc>, req } } -async fn handle_get_session(session_manager: &Arc>, req: Request) -> Result, Infallible> { +async fn handle_get_session(session_manager: &Arc>, req: Request) -> Result, Infallible> { let mut locked = session_manager.lock().await; let query_params = get_query_params(&req); let session_id = query_params.get("session_id"); @@ -148,7 +199,7 @@ async fn handle_get_session(session_manager: &Arc>, return build_success_res(&serde_json::to_string(&session.unwrap()).unwrap()); } -async fn handle_session_create(session_manager: &Arc>, req: Request, addr: SocketAddr) -> Result, Infallible> { +async fn handle_session_create(session_manager: &Arc>, req: Request, addr: SocketAddr) -> Result, Infallible> { let mut locked = session_manager.lock().await; let session_create_res = locked.create_session(Player {id: addr.to_string()}).await; if let Err(e) = session_create_res { @@ -158,7 +209,8 @@ async fn handle_session_create(session_manager: &Arc>, mut req: Request, addr: SocketAddr) -> Result, Infallible> { +async fn handle_session_join(session_manager: &Arc>, mut req: Request, addr: SocketAddr) -> Result, Infallible> { + println!("Received request to join session: {:?}", req); let mut locked = session_manager.lock().await; let body = read_json_body::(&mut req).await; let session_join_res = locked.join_session(body.session_id, Player {id: addr.to_string()}).await; @@ -166,11 +218,13 @@ async fn handle_session_join(session_manager: &Arc> eprintln!("Failed to join session: {:?}", e); return Ok(Response::builder().status(StatusCode::INTERNAL_SERVER_ERROR).body(Body::from(e)).unwrap()); } - let serialized = json!(session_join_res.unwrap()); + let session = session_join_res.unwrap(); + println!("Successfully joined session: {:?}", session); + let serialized = json!(session); return build_success_res(&serialized.to_string()); } -async fn handle_event_write(session_manager: &Arc>, mut req: Request) -> Result, Infallible> { +async fn handle_event_write(session_manager: &Arc>, mut req: Request) -> Result, Infallible> { let mut locked = session_manager.lock().await; let event = read_json_body::(&mut req).await; let writer = locked.get_session_writer(&event.session_id); @@ -182,9 +236,8 @@ async fn handle_event_write(session_manager: &Arc>, return Ok(res); } let mut writer = writer.unwrap(); - let mut writer_locked = writer.lock().await; println!("Writing session event to kafka: {:?}", event); - let write_res = writer_locked.write_to_session(&event.topic, &event.msg); + let write_res = writer.write_to_session(&event.topic, &event.msg); if let Err(e) = write_res { let err = format!("Failed to write event: {}", e); println!("{}", err); @@ -196,10 +249,10 @@ async fn handle_event_write(session_manager: &Arc>, build_success_res(&serde_json::to_string(&event).unwrap()) } -async fn handle_event_read(session_manager: &Arc>, mut req: Request) -> Result, Infallible> { +async fn handle_event_read(session_manager: &Arc>, mut req: Request) -> Result, Infallible> { let mut locked = session_manager.lock().await; let read_payload = read_json_body::(&mut req).await; - let reader = locked.get_session_reader(&read_payload.session_id); + let reader = locked.get_session_reader(&read_payload.session_id, &["move", "status", "input", "session"]); if let Err(e) = reader { let err = format!("Failed to read events: {}", e); println!("{}", err); @@ -208,9 +261,8 @@ async fn handle_event_read(session_manager: &Arc>, return Ok(res); } let mut reader = reader.unwrap(); - let mut reader_locked = reader.lock().await; println!("Reading session events from kafka for session: {}", read_payload.session_id); - let events = reader_locked.read_from_session(); + let events = reader.read_from_session(); if let Err(e) = events { let err = format!("Failed to read events: {}", e); println!("{}", err); @@ -268,3 +320,36 @@ struct SessionReadDTO { struct SessionJoinDto { session_id: String } + +#[derive(Debug, Clone)] +enum WebSocketConnectionType { + HOST, PEER, OBSERVER +} + +impl FromStr for WebSocketConnectionType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "host" => Ok(WebSocketConnectionType::HOST), + "peer" => Ok(WebSocketConnectionType::PEER), + "observer" => Ok(WebSocketConnectionType::OBSERVER), + _ => Err(()) + } + } +} + +#[derive(Debug, Clone)] +struct WebSocketSession { + pub connection_type: WebSocketConnectionType, + pub session_id: String +} + +impl WebSocketConnectionType { + pub fn get_topics(&self) -> &[&str] { + match self { + WebSocketConnectionType::HOST => &["input", "session"], + WebSocketConnectionType::PEER | WebSocketConnectionType::OBSERVER => &["move", "input", "status", "session"], + } + } +} diff --git a/server/src/kafka.rs b/server/src/kafka.rs index a7dfa73..41b553a 100644 --- a/server/src/kafka.rs +++ b/server/src/kafka.rs @@ -119,8 +119,8 @@ impl KafkaEventReaderImpl { } } - pub fn for_partitions(host: &str, partitions: &[i32], topics: &[&str]) -> KafkaEventReaderImpl { - println!("Connecting partition specific consumer to kafka host: {}", host); + pub fn for_partitions(host: &str, partitions: &[i32], topics: &[&str]) -> Result { + println!("Connecting partition specific consumer to kafka host {} with topics {:?} / partitions {:?}", host, topics, partitions); let mut builder = Consumer::from_hosts(vec!(host.to_owned())); for topic in topics.iter() { builder = builder.with_topic_partitions(topic.parse().unwrap(), partitions); @@ -131,11 +131,15 @@ impl KafkaEventReaderImpl { .with_offset_storage(GroupOffsetStorage::Kafka); let consumer = builder - .create() - .unwrap(); - KafkaEventReaderImpl { - consumer + .create(); + if let Err(e) = consumer { + eprintln!("Failed to connect consumer: {:?}", e); + return Err("Failed to connect consumer".to_string()); } + let consumer = consumer.unwrap(); + Ok(KafkaEventReaderImpl { + consumer + }) } } impl EventReaderImpl for KafkaEventReaderImpl { @@ -172,11 +176,16 @@ pub struct KafkaSessionEventReaderImpl { } impl KafkaSessionEventReaderImpl { - pub fn new(host: &str, session: &Session, topics: &[&str]) -> KafkaSessionEventReaderImpl { + pub fn new(host: &str, session: &Session, topics: &[&str]) -> Result { let partitions = [session.id as i32]; - KafkaSessionEventReaderImpl { - inner: KafkaEventReaderImpl::for_partitions(host, &partitions, topics) + let reader = KafkaEventReaderImpl::for_partitions(host, &partitions, topics); + if let Err(e) = reader { + return Err("Failed to create kafka session event reader".to_string()); } + let reader = reader.unwrap(); + Ok(KafkaSessionEventReaderImpl { + inner: reader + }) } } @@ -254,7 +263,7 @@ impl Partitioner for SessionPartitioner { Some(key) => { let key = std::str::from_utf8(key).unwrap(); msg.partition = key.parse::().unwrap(); - println!("Overriding message partition with key: {}", msg.partition); + // println!("Overriding message partition with key: {}", msg.partition); }, None => panic!("Producing message without key not allowed!") } diff --git a/server/src/session.rs b/server/src/session.rs index f283e4d..2f6d247 100644 --- a/server/src/session.rs +++ b/server/src/session.rs @@ -87,13 +87,18 @@ impl SessionManager { return Ok(()); } - pub fn get_session_reader(&self, session_id: &str) -> Result { + pub fn get_session_reader(&self, session_id: &str, topics: &[&str]) -> Result { let session = self.find_session(&session_id); if let None = session { return Err(format!("Unable to find session with hash {}", session_id)) } let session = session.unwrap(); - let event_reader = EventReader::new(Box::new(KafkaSessionEventReaderImpl::new(&self.kafka_host, &session, &["move", "status", "input"]))); + let kafka_reader = KafkaSessionEventReaderImpl::new(&self.kafka_host, &session, topics); + if let Err(_) = kafka_reader { + return Err("Unable to create kafka reader.".to_string()) + } + let kafka_reader = kafka_reader.unwrap(); + let event_reader = EventReader::new(Box::new(kafka_reader)); Ok(SessionReader {reader: event_reader, session}) } @@ -145,7 +150,7 @@ impl CachingSessionManager { println!("Reusing existing reader for session: {:?}", session_id); return Ok(Arc::clone(reader)); } - let reader = self.inner.get_session_reader(session_id); + let reader = self.inner.get_session_reader(session_id, &["move", "input", "status", "session"]); if let Err(e) = reader { return Err(e); } diff --git a/www/index.js b/www/index.js index f9b5858..fa7faf7 100644 --- a/www/index.js +++ b/www/index.js @@ -88,7 +88,7 @@ window.WASM_PONG.createOnlineSession = () => { session_display_tag.style.display = 'block'; session_display_tag.innerHTML = JSON.stringify(session) - websocket = new WebSocket("ws://localhost:4000") + websocket = new WebSocket(`ws://localhost:4000/ws?session_id=${session.hash}&connection_type=host`) websocket.onmessage = (event) => { console.log(event) } @@ -109,7 +109,7 @@ window.WASM_PONG.joinOnlineSession = () => { session_display_tag.style.display = 'block'; session_display_tag.innerHTML = JSON.stringify(session) - websocket = new WebSocket("ws://localhost:4000") + websocket = new WebSocket(`ws://localhost:4000/ws?session_id=${session.hash}&connection_type=peer`) websocket.onmessage = (event) => { console.log(event) }