From fae0fca7aabb81325a296a1d6202239c3db44b60 Mon Sep 17 00:00:00 2001 From: stale Date: Fri, 20 May 2022 06:39:58 -0300 Subject: wip room support --- src/main.rs | 160 +++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 122 insertions(+), 38 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index e972025..6c2f375 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use std::{ error::Error, net::SocketAddr, sync::Arc, + collections::HashMap, }; mod types; @@ -17,8 +18,8 @@ fn main() -> Result<(), Box> { let conf = Config { cert: "./cert.pem".to_owned(), pkey: "./cert.rsa".to_owned(), + index_pg: "./assets/index.html".to_owned(), room_pg: "./assets/room.html".to_owned(), - form_pg: "./assets/form.html".to_owned(), client_code: "./assets/client.js".to_owned(), stylesheet: "./assets/style.css".to_owned(), socket_addr: ([0,0,0,0],31235).into(), @@ -29,50 +30,117 @@ fn main() -> Result<(), Box> { #[tokio::main] async fn tokio_main(conf: Config) -> Result<(), Box> { - // Start the temporary single room - let room = Arc::new(RwLock::new({ - let name = "Testing room".to_string(); - let players = PlayerMap::default(); - let bconf = BoardConf { w: 75, h: 35, mine_ratio: (1, 8) }; - let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel(); - let handle = tokio::spawn(gameloop(cmd_rx, players.clone(), bconf)); - Room { - name, - players, - peer_limit: 32, - board_conf: bconf, - cmd_stream: cmd_tx, - driver: handle, - } - })); - + let rooms: RoomMap = Arc::new(RwLock::new(HashMap::new())); + let public_rooms = Arc::new(RwLock::new(Vec::new())); use warp::*; - let style = path("s.css").and(fs::file(conf.stylesheet.clone())); - let code = path("c.js").and(fs::file(conf.client_code.clone())); - let font = path("f.ttf").map(|| FONT_FILE); - let listing = path("rlist").map(|| "placeholder'em"); - let room_form = path("r").map(|| "yeah placeholder mate"); - let index = path::end().and(fs::file(conf.room_pg.clone())); - - let websocket_route = { - let room = room.clone(); - use warp::*; - path("ws") + let index = path::end().and(fs::file(conf.index_pg.clone())); + let style = path!("s.css").and(fs::file(conf.stylesheet.clone())); + let code = path!("c.js").and(fs::file(conf.client_code.clone())); + let font = path!("f.ttf").map(|| FONT_FILE); + let listing = path!("rlist").map(|| "placeholder'em"); + let rform_recv = { + let rooms = rooms.clone(); + let pubs = public_rooms.clone(); + post().and(path("r")).and(body::content_length_limit(4096)).and(body::form()) + .and_then(move |rinfo: HashMap| { + println!("{:?}", rinfo); + let rooms = rooms.clone(); + let pubs = pubs.clone(); + async move { + if let (Some(w),Some(h),Some(num),Some(denom),access,limit) = ( + rinfo.get("rwidth").and_then(|wt| wt.parse::().ok()), + rinfo.get("rheight").and_then(|ht| ht.parse::().ok()), + rinfo.get("rration").and_then(|nt| nt.parse::().ok()), + rinfo.get("rratiod").and_then(|dt| dt.parse::().ok()), + rinfo.get("raccess"), + rinfo.get("rlimit").and_then(|l| l.parse::().ok()), + ) { + let board_conf = BoardConf { w, h, mine_ratio: (num,denom) }; + let name = rinfo.get("rname").map(|r| r.to_owned()).unwrap_or(format!("{w}x{h} room")); + + let mut rooms = rooms.write().await; + let uid = types::RoomId::new_in(&rooms); + let players = PlayerMap::default(); + let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel(); + let handle = tokio::spawn(gameloop(cmd_rx, players.clone(), board_conf)); + rooms.insert(uid.clone(), Arc::new(RwLock::new(Room { + name, + players, + peer_limit: match limit { Some(i) => i, None => usize::MAX }, + public: access.is_some(), + driver: handle, + cmd_stream: cmd_tx, + board_conf, + }))); + if access.is_some() { + pubs.write().await.push(uid.clone()); + } + Ok( + hyper::Response::builder() + .status(hyper::StatusCode::SEE_OTHER) + .header(hyper::header::LOCATION, format!("/room/{uid}")) + .body(hyper::Body::empty()) + .unwrap() + ) + } else { Err(reject::custom(BadFormData)) } + } + }) + }; + let room = { + let rooms_ws = rooms.clone(); + let rooms_lobby = rooms.clone(); + let prefix = get().and(path!("room" / String / ..)); + + // Fixme: better errors + prefix.and(path!("ws")) .and(ws()) .and(addr::remote()) - .map(move |ws: warp::ws::Ws, saddr: Option| { - let room = room.clone(); - println!("conn from {saddr:?}"); - ws.on_upgrade(move |socket| { - conn::lobby(socket, saddr.expect("socket without address"), room.clone()) - }) + .and_then(move |id: String, websocket: warp::ws::Ws, saddr: Option| { + let rooms = rooms_ws.clone(); + async move { + let id = RoomId {0: id}; + match rooms.read().await.get(&id).map(|x| x.clone()) { + Some(r) => { + println!("conn from {saddr:?} into {id}"); + Ok(websocket.on_upgrade(move |socket| { + conn::lobby(socket, saddr.expect("socket without address"), r.clone()) + })) + }, + None => { + println!("conn from {saddr:?} into inexistent room {id}"); + Err(reject()) + } + } + } }) + .or(prefix.and(path::end()) + .and(fs::file(conf.room_pg.clone())) + .then(move |id: String, f: fs::File| { + let rooms = rooms_lobby.clone(); + async move { + if rooms.read().await.contains_key(&RoomId {0: id}) { + f.into_response() + } else { + reply::with_status("No such room", http::StatusCode::BAD_REQUEST).into_response() + } + } + }) + ) }; - let route = any().and(get().and(index).or(style).or(code).or(font).or(listing)).or(post().and(room_form)); - let routes = websocket_route.or(route); - let server = warp::serve(routes) + + let route = get() + .and(index) + .or(style) + .or(code) + .or(font) + .or(listing) + .or(rform_recv) + .or(room) + .recover(error_handler); + + let server = warp::serve(route) .tls() .cert_path(conf.cert) .key_path(conf.pkey) @@ -121,3 +189,19 @@ async fn gameloop(mut move_rx: tokio::sync::mpsc::UnboundedReceiver, p } } } + +use warp::{ reject::{ Reject, Rejection }, reply::{ self, Reply }, http::StatusCode }; +#[derive(Debug)] +struct BadFormData; +impl Reject for BadFormData {} + +async fn error_handler(err: Rejection) -> Result { + if err.is_not_found() { Ok(reply::with_status("No such file", StatusCode::NOT_FOUND)) } + else if let Some(_e) = err.find::() { + Ok(reply::with_status("Bad form data", StatusCode::BAD_REQUEST)) + } else { + println!("unhandled rejection: {err:?}"); + Ok(reply::with_status("Server error", StatusCode::INTERNAL_SERVER_ERROR)) + } +} + -- cgit v1.2.3