use std::error::Error; use std::net::SocketAddr; use std::sync::Arc; use async_std::io::WriteExt; use async_std::net::TcpStream; use async_std::sync::RwLock; use log::{error, info, warn}; use starkingdoms_protocol::PROTOCOL_VERSION; use crate::entity::{Entity, EntityHandler}; use crate::manager::{ClientHandler, ClientManager, PhysicsData}; use crate::{CMGR, ServerPingResponse, ServerPingResponseVersion}; use crate::handler::handle_client; use futures::StreamExt; pub async fn handle_request( conn: TcpStream, remote_addr: SocketAddr, mgr: ClientManager, entities: Arc>, physics_data: Arc>, ) { match _handle_request(conn, remote_addr, mgr, entities, physics_data).await { Ok(_) => (), Err(e) => { error!("[{}] error in handler thread: {}", remote_addr, e); } } } async fn _handle_request( mut conn: TcpStream, remote_addr: SocketAddr, mgr: ClientManager, entities: Arc>, physics_data: Arc>, ) -> Result<(), Box> { let mut peek_buf = [0u8; 9]; loop { let read = conn.peek(&mut peek_buf).await?; if read == 9 { break; } } if peek_buf == *b"GET /ping" { info!("[{}] incoming http connection", remote_addr); let ping_resp = serde_json::to_string(&ServerPingResponse { version: ServerPingResponseVersion { name: env!("STK_VERSION_NAME").to_string(), // Set by build.rs number: env!("STK_VERSION").to_string(), // Set by build.rs protocol: PROTOCOL_VERSION, channel: env!("STK_CHANNEL").to_string(), build: env!("STK_BUILD").to_string(), }, players: u32::try_from(CMGR.usernames.read().await.len())?, description: env!("STK_SLP_DESCRIPTION").to_string(), })?; let resp_str = format!( "HTTP/1.0 200 OK\nAccess-Control-Allow-Origin: *\nContent-Length: {}\n\n{}", ping_resp.len(), ping_resp ); let http_resp = resp_str.as_bytes(); conn.write_all(http_resp).await?; info!( "[{}] sent ping response (200 OK {} bytes)", remote_addr, ping_resp.len() ); return Ok(()); } info!("[{}] incoming websocket connection", remote_addr); // if its not GET /ping, assume its websocket and attempt to handshake with them let ws_stream = async_tungstenite::accept_async(conn).await?; let (ws_write, ws_read) = ws_stream.split(); let (tx, rx) = async_std::channel::unbounded(); let client = ClientHandler { tx }; // Acquire the write lock in a small scope, so it's dropped as quickly as possible { mgr.handlers.write().await.insert(remote_addr, client); } info!("[{}] passing to client handler", remote_addr); //forward the stream to the sink to achieve echo match handle_client( mgr.clone(), entities.clone(), physics_data.clone(), remote_addr, rx, ws_write, ws_read, ) .await { Ok(_) => (), Err(e) if e.is::() => { #[allow(clippy::expect_used)] let e = { e.downcast::() .expect("unable to convert between types safely") }; if matches!(*e, async_tungstenite::tungstenite::Error::ConnectionClosed) { info!("[{}] connection closed normally", remote_addr); } else { error!("[{}] error in client thread: {}", remote_addr, e); } } Err(e) => { error!("[{}] error in client thread: {}", remote_addr, e); } } // clean up values left over { mgr.handlers.write().await.remove(&remote_addr); mgr.usernames.write().await.remove(&remote_addr); // remove player physics body let mut entity_write = entities.write().await; let mut data = physics_data.write().await; let mut rigid_body_set = data.rigid_body_set.clone(); let mut island_manager = data.island_manager.clone(); let mut collider_set = data.collider_set.clone(); let mut impulse_joint_set = data.impulse_joint_set.clone(); let mut multibody_joint_set = data.multibody_joint_set.clone(); let Some(player_id) = entity_write.get_player_id(remote_addr) else { warn!("[{}] player missing from entities.players", remote_addr); return Err("Player missing from entities.players".into()); }; if let Some(Entity::Player(player)) = entity_write.entities.get(&player_id) { rigid_body_set.remove( player.handle, &mut island_manager, &mut collider_set, &mut impulse_joint_set, &mut multibody_joint_set, true, ); for module in player.search_modules(&entity_write) { rigid_body_set.remove( module.handle, &mut island_manager, &mut collider_set, &mut impulse_joint_set, &mut multibody_joint_set, true, ); let module_id = entity_write .get_id_from_attached(&module) .ok_or("Tried to remove nonexistent module")?; entity_write.entities.remove(&module_id); } } else { warn!("tried to remove player that doesnt exist: #{}", player_id); } data.rigid_body_set = rigid_body_set; data.collider_set = collider_set; data.island_manager = island_manager; data.impulse_joint_set = impulse_joint_set; data.multibody_joint_set = multibody_joint_set; entity_write.entities.remove(&player_id); } Ok(()) }