From 22faeed7f035ab19f9f7a2bf08af078014033e4f Mon Sep 17 00:00:00 2001 From: CrispyPin Date: Mon, 25 Mar 2024 22:27:27 +0100 Subject: [PATCH 1/2] better logging --- src/http.rs | 11 ++++++++++- src/main.rs | 39 ++++++++++++++++++++++----------------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/http.rs b/src/http.rs index 58a3e7d..7475f42 100644 --- a/src/http.rs +++ b/src/http.rs @@ -13,7 +13,7 @@ pub enum RequestRange { Suffix(usize), } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum Method { Get, Head, @@ -209,6 +209,15 @@ impl Method { } } +impl std::fmt::Display for Method { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Method::Get => write!(f, "GET"), + Method::Head => write!(f, "HEAD"), + } + } +} + impl RequestRange { fn parse(source: &str) -> Option { let source = source.strip_prefix("bytes=")?; diff --git a/src/main.rs b/src/main.rs index 7b4de4b..8c99849 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,37 +30,39 @@ fn main() { Err(err) => println!("Error with incoming stream: {}", err), } threads = threads.into_iter().filter(|j| !j.is_finished()).collect(); - println!("{} threads open", threads.len()); + println!("{} connections open", threads.len()); } } fn handle_connection(mut stream: TcpStream) { - let Ok(peer_addr) = stream.peer_addr() else { + const MAX_REQUEST_SIZE: usize = 1024 * 4; + let Ok(client_ip) = stream.peer_addr() else { return; }; - println!("#### new connection from {peer_addr}"); + println!("[{client_ip}] new connection"); let mut buffer = Vec::with_capacity(2048); loop { let mut b = vec![0; 512]; let Ok(size) = stream.read(&mut b) else { - println!("failed to read "); + println!("[{client_ip}] connection broken"); return; }; if size == 0 { - println!("nothing read"); + println!("[{client_ip}] connection closed by client"); return; } b.truncate(size); buffer.extend_from_slice(&b); - if buffer.len() > 4096 { - println!("request too long"); + if buffer.len() > MAX_REQUEST_SIZE { + println!("[{client_ip}] request over {MAX_REQUEST_SIZE} bytes, closing connection"); return; } if buffer.ends_with(b"\r\n\r\n") { let request = String::from_utf8_lossy(&buffer).to_string(); - // println!("Received {} bytes from {}", buffer.len(), peer_addr); + + println!("[{client_ip}] received {} bytes", buffer.len()); // println!( // "=======\n{}=======\n\n", // request @@ -70,43 +72,46 @@ fn handle_connection(mut stream: TcpStream) { // .replace("\\n", "\n") // ); if handle_request(request, &mut stream) { - println!("closing connection"); + println!("[{client_ip}] closing connection"); return; } - // println!("keeping connection"); buffer.clear(); } } } fn handle_request(request: String, stream: &mut TcpStream) -> bool { + let Ok(client_ip) = stream.peer_addr() else { + return true; + }; let request = Request::parse(&request); let response; let mut end_connection = true; if let Some(request) = request { + println!("[{client_ip}] {} {}", request.method, request.path); let head_only = request.method == Method::Head; let path = request.path.clone(); response = get_file(request) .map(|(content, end_of_file)| { end_connection = end_of_file; + println!("[{client_ip}] sending file content"); Response::new(Status::Ok).with_content(content) }) .unwrap_or_else(|| { + println!("[{client_ip}] file not found"); Response::new(Status::NotFound) - .with_content(Content::text(format!("FILE NOT FOUND - '{}'", path))) + .with_content(Content::text(format!("404 NOT FOUND - '{}'", path))) }) .format(head_only); } else { + println!("[{client_ip}] bad request"); response = Response::new(Status::BadRequest).format(false); } - stream - .write_all(&response) - .unwrap_or_else(|_| println!("failed to respond")); - stream - .flush() - .unwrap_or_else(|_| println!("failed to flush")); + if stream.write_all(&response).is_err() || stream.flush().is_err() { + println!("[{client_ip}] failed to send response"); + } end_connection } From 7a392e9c6c18b6e904efc7bf21b59efa069e8608 Mon Sep 17 00:00:00 2001 From: CrispyPin Date: Mon, 25 Mar 2024 23:12:37 +0100 Subject: [PATCH 2/2] convert index to table and show file size --- src/http.rs | 10 ++++---- src/main.rs | 74 +++++++++++++++++++++++++++++++++++------------------ 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/src/http.rs b/src/http.rs index 7475f42..85c0f6f 100644 --- a/src/http.rs +++ b/src/http.rs @@ -35,7 +35,7 @@ pub enum Status { #[derive(Debug, Clone)] pub struct Content { - content_type: &'static str, + mime_type: &'static str, range: Option<(usize, usize, usize)>, bytes: Vec, } @@ -93,14 +93,14 @@ impl Response { let mut buffer = format!( "{}\r\nContent-Type: {}\r\nAccept-Ranges: bytes\r\nContent-Length: {}\r\n", self.status.header(), - content.content_type, + content.mime_type, content.bytes.len(), ) .into_bytes(); if let Some((start, end, size)) = content.range { buffer.extend_from_slice( - format!("Content-Range: bytes {}-{}/{}\r\n", start, end, size).as_bytes(), - ) + format!("Content-Range: bytes {start}-{end}/{size}\r\n").as_bytes(), + ); } buffer.extend_from_slice(b"\r\n"); if !head_only { @@ -168,7 +168,7 @@ impl Content { } }; Self { - content_type, + mime_type: content_type, range: None, bytes, } diff --git a/src/main.rs b/src/main.rs index 8c99849..44f5eb5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,9 +27,9 @@ fn main() { for stream in listener.incoming() { match stream { Ok(stream) => threads.push(thread::spawn(|| handle_connection(stream))), - Err(err) => println!("Error with incoming stream: {}", err), + Err(err) => println!("Error with incoming stream: {err}"), } - threads = threads.into_iter().filter(|j| !j.is_finished()).collect(); + threads.retain(|j| !j.is_finished()); println!("{} connections open", threads.len()); } } @@ -71,7 +71,7 @@ fn handle_connection(mut stream: TcpStream) { // .replace("\\r\\n", "\n") // .replace("\\n", "\n") // ); - if handle_request(request, &mut stream) { + if handle_request(&request, &mut stream) { println!("[{client_ip}] closing connection"); return; } @@ -80,11 +80,11 @@ fn handle_connection(mut stream: TcpStream) { } } -fn handle_request(request: String, stream: &mut TcpStream) -> bool { +fn handle_request(request: &str, stream: &mut TcpStream) -> bool { let Ok(client_ip) = stream.peer_addr() else { return true; }; - let request = Request::parse(&request); + let request = Request::parse(request); let response; let mut end_connection = true; @@ -92,7 +92,7 @@ fn handle_request(request: String, stream: &mut TcpStream) -> bool { println!("[{client_ip}] {} {}", request.method, request.path); let head_only = request.method == Method::Head; let path = request.path.clone(); - response = get_file(request) + response = get_file(&request) .map(|(content, end_of_file)| { end_connection = end_of_file; println!("[{client_ip}] sending file content"); @@ -101,7 +101,7 @@ fn handle_request(request: String, stream: &mut TcpStream) -> bool { .unwrap_or_else(|| { println!("[{client_ip}] file not found"); Response::new(Status::NotFound) - .with_content(Content::text(format!("404 NOT FOUND - '{}'", path))) + .with_content(Content::text(format!("404 NOT FOUND - '{path}'"))) }) .format(head_only); } else { @@ -115,7 +115,7 @@ fn handle_request(request: String, stream: &mut TcpStream) -> bool { end_connection } -fn get_file(request: Request) -> Option<(Content, bool)> { +fn get_file(request: &Request) -> Option<(Content, bool)> { const MAX_SIZE: usize = 1024 * 1024 * 8; let current_dir = env::current_dir().unwrap(); @@ -177,31 +177,37 @@ fn generate_index(relative_path: &str, path: &Path) -> Option { .ok()? .flatten() .filter_map(|d| { - let is_dir = d.file_type().ok()?.is_dir(); - d.file_name().to_str().map(|s| (s.to_owned(), is_dir)) + let size = if d.file_type().ok()?.is_dir() { + None + } else { + Some(d.metadata().ok()?.len()) + }; + + d.file_name().to_str().map(|s| (s.to_owned(), size)) }) .collect(); - dirs.sort_by(|(name_a, dir_a), (name_b, dir_b)| dir_b.cmp(dir_a).then(name_a.cmp(name_b))); + dirs.sort_by(|(name_a, size_a), (name_b, size_b)| { + size_a + .is_some() + .cmp(&size_b.is_some()) + .then(name_a.cmp(name_b)) + }); let list = dirs .into_iter() - .filter_map(|(name, is_dir)| { - let mut s = format!( - "
  • {}", - PathBuf::from(relative_path).join(&name).display(), - name - ); - if is_dir { - s.push('/'); - } - s.push_str("
  • \n"); - Some(s) + .map(|(name, size)| { + let formatted_size = size.map(format_size).unwrap_or_default(); + format!( + "{name}{trailing_slash}{formatted_size}\n", + href = PathBuf::from(relative_path).join(&name).display(), + trailing_slash = if size.is_some() { "" } else { "/" }, + ) }) .fold(String::new(), |mut content, entry| { content.push_str(&entry); content }); let parent = if relative_path != "/" { - r#"
  • ../
  • "# + "../" } else { "" }; @@ -210,13 +216,31 @@ fn generate_index(relative_path: &str, path: &Path) -> Option { Index of {relative_path} +

    Index of {relative_path}

    -
      -{parent}{list}
    + +{parent} +{list} +
    "#, ); Some(Content::html(page)) } + +fn format_size(bytes: u64) -> String { + if bytes < 1024 { + format!("{bytes} B") + } else if bytes < 1024 * 1024 { + format!("{:.1} KiB", bytes as f64 / 1024.0) + } else if bytes < 1024 * 1024 * 1024 { + format!("{:.1} MiB", bytes as f64 / (1024.0 * 1024.0)) + } else { + format!("{:.1} GiB", bytes as f64 / (1024.0 * 1024.0 * 1024.0)) + } +}