From 964c90d2f2f3944b35023d7aab078908d75e344c Mon Sep 17 00:00:00 2001 From: maxstrb Date: Tue, 4 Nov 2025 18:02:59 +0100 Subject: [PATCH] websocket not working --- public/index.html | 11 ++++++ src/main.rs | 61 ++++++++++++++++++++------------ src/request.rs | 45 +++++++++++++++++++----- src/shared_enums.rs | 1 + src/websoket_connection.rs | 72 +++++++++++++++++++++++++------------- 5 files changed, 136 insertions(+), 54 deletions(-) diff --git a/public/index.html b/public/index.html index 94a7940..a7f1b58 100644 --- a/public/index.html +++ b/public/index.html @@ -4,6 +4,17 @@ Hello World! + diff --git a/src/main.rs b/src/main.rs index a1e8250..0eca6ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,6 @@ async fn handle_connection(stream: TcpStream) -> tokio::io::Result<()> { Ok(()) } - async fn handle_http_connection( mut stream: TcpStream, ) -> tokio::io::Result> { @@ -46,8 +45,8 @@ async fn handle_http_connection( .await { Ok(Ok(r)) => r, - Ok(Err(_)) => { - println!("Wrong request"); + Ok(Err(e)) => { + println!("Wrong request: {e}"); break; } Err(_) => { @@ -55,37 +54,52 @@ async fn handle_http_connection( break; } }; - println!("{req:?}"); - let response = match req.path.to_matchable().as_slice() { + // DEBUG: Print the path matching + let matchable = req.path.to_matchable(); + println!("Path matchable: {:?}", matchable); + println!("Path as slice: {:?}", matchable.as_slice()); + + let response = match matchable.as_slice() { ["public", file] => { + println!("Matched public file: {}", file); match Response::from_file(Path::new(format!("./public/{file}").as_str())) { Ok(resp) => resp, Err(_) => Response::new().with_code(ResponseCode::NotFound), } } ["websocket"] => { - return Ok(Some( - WebsocketConnection::initialize_connection(req, stream).await?, - )); + println!("WebSocket path matched!"); + println!("Initializing WebSocket connection..."); + match WebsocketConnection::initialize_connection(req, stream).await { + Ok(ws) => { + println!("WebSocket connection established successfully!"); + return Ok(Some(ws)); + } + Err(e) => { + println!("WebSocket initialization failed: {}", e); + return Err(e); + } + } + } + [] => { + println!("Matched root path, redirecting"); + Response::new() + .with_code(ResponseCode::PermanentRedirect) + .with_header(ResponseHeader::Connection(Connection::KeepAlive)) + .with_header(ResponseHeader::Location( + ServerPath::from_str("/public/index.html").unwrap(), + )) + } + other => { + println!("No match, path was: {:?}", other); + Response::new().with_code(ResponseCode::NotFound) } - [] => Response::new() - .with_code(ResponseCode::PermanentRedirect) - .with_header(ResponseHeader::Connection(Connection::KeepAlive)) - .with_header(ResponseHeader::Location( - ServerPath::from_str("/public/index.html").unwrap(), - )), - - _ => Response::new().with_code(ResponseCode::NotFound), }; - response.respond(&mut stream).await?; - stream.flush().await?; - timeout = 5000; - if req.headers.contains(&request::RequestHeader::Connection( request::Connection::Close, )) { @@ -93,10 +107,13 @@ async fn handle_http_connection( break; } } - Ok(None) } async fn handle_websocket(mut web_socket: WebsocketConnection) -> tokio::io::Result<()> { - todo!() + loop { + let message = web_socket.read_next_message().await?; + + println!("{:?}", message.data); + } } diff --git a/src/request.rs b/src/request.rs index 2d06657..eaa5bc6 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader, Take}; +use tokio::io::{self, AsyncBufReadExt, AsyncReadExt, BufReader, Take}; use tokio::net::TcpStream; use crate::shared_enums::Content; @@ -51,35 +51,64 @@ impl Connection { #[derive(Debug, PartialEq)] pub struct Upgrade { pub protocol: Protocol, - pub version: Box, + pub version: Option>, } impl Upgrade { pub fn to_str(&self) -> Box { match self.version.as_ref() { - "" => self.protocol.to_str().into(), - _ => format!("{}/{}", self.protocol.to_str(), self.version).into(), + None => self.protocol.to_str().into(), + Some(version) => format!("{}/{}", self.protocol.to_str(), version).into(), } } } impl FromStr for Upgrade { type Err = tokio::io::Error; - fn from_str(_s: &str) -> Result { - todo!() + fn from_str(s: &str) -> Result { + println!("{s}"); + + match s.split('/').collect::>().as_slice() { + [protocol, version] => Ok(Self { + protocol: Protocol::from_str(protocol)?, + version: Some((*version).into()), + }), + [protocol] => Ok(Self { + protocol: Protocol::from_str(protocol)?, + version: None, + }), + _ => Err(tokio::io::Error::new( + io::ErrorKind::InvalidData, + "invalid upgrade error", + )), + } } } #[derive(Debug, PartialEq)] pub enum Protocol { - HTTP, + Http, Websocket, } +impl FromStr for Protocol { + type Err = tokio::io::Error; + fn from_str(s: &str) -> Result { + match s { + "http" => Ok(Self::Http), + "websocket" => Ok(Self::Websocket), + _ => Err(io::Error::new( + io::ErrorKind::InvalidData, + "invalid protocol", + )), + } + } +} + impl Protocol { pub fn to_str(&self) -> &'static str { match self { - Self::HTTP => "HTTP", + Self::Http => "HTTP", Self::Websocket => "websocket", } } diff --git a/src/shared_enums.rs b/src/shared_enums.rs index 656c453..341fbc4 100644 --- a/src/shared_enums.rs +++ b/src/shared_enums.rs @@ -224,6 +224,7 @@ impl FromStr for ContentType { ["application", "json"] => Ok(ContentType::Aplication(ApplicationType::Json)), ["application", "xhtml+xml"] => Ok(ContentType::Aplication(ApplicationType::XhtmlXml)), ["application", "xml"] => Ok(ContentType::Aplication(ApplicationType::Xml)), + ["application", "signed-exchange"] => Ok(ContentType::Aplication(ApplicationType::Any)), ["application", "*"] => Ok(ContentType::Aplication(ApplicationType::Any)), ["image", "png"] => Ok(ContentType::Image(Image::Png)), diff --git a/src/websoket_connection.rs b/src/websoket_connection.rs index eaab754..b98da90 100644 --- a/src/websoket_connection.rs +++ b/src/websoket_connection.rs @@ -21,21 +21,16 @@ struct DataBlock { message_type: FrameType, - is_masked: bool, - - length: u64, - - mask_key: Option, - data: Vec, } -struct DataFrame { - frame_type: FrameType, - data: Box<[u8]>, +pub struct DataFrame { + pub frame_type: FrameType, + pub data: Box<[u8]>, } -enum FrameType { +#[derive(PartialEq, Eq)] +pub enum FrameType { Continuation, TextFrame, BinaryFrame, @@ -52,12 +47,28 @@ impl WebsocketConnection { } pub async fn read_next_message(&mut self) -> io::Result { - let mut data; - let frame_type; - { - let first_line = self.parse_single_block().await?; - data = first_line.data; - frame_type = first_line.message_type; + let first_line = self.parse_single_block().await?; + + println!("Block read"); + + let mut data = first_line.data; + let frame_type = first_line.message_type; + + if !first_line.is_final { + let mut current_line = self.parse_single_block().await?; + + while !current_line.is_final { + if current_line.message_type != FrameType::Continuation { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "That is not how websocket works!!!", + )); + } + data.extend_from_slice(¤t_line.data); + current_line = self.parse_single_block().await?; + } + + data.extend_from_slice(¤t_line.data); } Ok(DataFrame { @@ -66,6 +77,13 @@ impl WebsocketConnection { }) } + fn unmask_block(data: &mut [u8], mask: u32) { + let mask_bytes = mask.to_be_bytes(); + for (i, e) in data.iter_mut().enumerate() { + *e ^= mask_bytes[i % 4]; + } + } + async fn parse_single_block(&mut self) -> io::Result { let mut first_line: [u8; 2] = [0; 2]; self.stream.read_exact(&mut first_line).await?; @@ -100,24 +118,24 @@ impl WebsocketConnection { }; let masking_key = if mask { - Some(self.stream.read_u32().await?) + self.stream.read_u32().await? } else { - None + 0 }; - let mut message_data = Vec::::with_capacity(length as usize); - + let mut message_data = vec![0u8; length as usize]; self.stream.read_exact(&mut message_data).await?; + if mask { + Self::unmask_block(&mut message_data, masking_key); + } + Ok(DataBlock { is_final, e1: extension_bit_1, e2: extension_bit_2, e3: extension_bit_3, message_type, - is_masked: mask, - length, - mask_key: masking_key, data: message_data, }) } @@ -141,6 +159,10 @@ impl WebsocketConnection { RequestHeader::Connection(con) => { if con == Connection::Upgrade { connection = true; + } else if let Connection::Other(c) = con + && c.contains("Upgrade") + { + connection = true; } } RequestHeader::Other { name, value } => { @@ -166,7 +188,7 @@ impl WebsocketConnection { .with_code(crate::response::ResponseCode::SwitchingProtocols) .with_header(crate::response::ResponseHeader::Upgrade(Upgrade { protocol: Protocol::Websocket, - version: "".into(), + version: None, })) .with_header(crate::response::ResponseHeader::Connection( Connection::Upgrade, @@ -178,6 +200,8 @@ impl WebsocketConnection { .respond(&mut stream) .await?; + stream.flush().await?; + Ok(Self { stream }) } else { Response::new()