diff options
| author | omagdy7 <omar.professional8777@gmail.com> | 2024-06-05 18:13:08 +0300 |
|---|---|---|
| committer | omagdy7 <omar.professional8777@gmail.com> | 2024-06-05 18:13:08 +0300 |
| commit | ff75fa542a98cf9a79133a81b7716401e717bfd6 (patch) | |
| tree | c9433151c9dc21a16a822d4a8f1f6f7c526b1701 /src/main.rs | |
| parent | 4f9d09b58ae81e4e6144b85ce36ba424e319b779 (diff) | |
| download | tiny-server-ff75fa542a98cf9a79133a81b7716401e717bfd6.tar.xz tiny-server-ff75fa542a98cf9a79133a81b7716401e717bfd6.zip | |
codecrafters submit [skip ci]
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 268 |
1 files changed, 176 insertions, 92 deletions
diff --git a/src/main.rs b/src/main.rs index 1ad6f26..ccdaa37 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,128 @@ #![allow(unused)] +use http_server_starter_rust::router::Router; use itertools::Itertools; use std::collections::HashMap; +use std::fs::File; use std::io::{self, Read, Write}; use std::net::{TcpListener, TcpStream}; -use std::{str, thread}; +use std::str::Utf8Error; +use std::sync::{Arc, Mutex}; +use std::{str, thread, usize}; -use http_server_starter_rust::http_types::*; use http_server_starter_rust::request::*; use http_server_starter_rust::response::*; +use http_server_starter_rust::{extractor, http_types::*}; -fn handle_client(mut stream: TcpStream) { +fn read_file_as_bytes(path: &str) -> io::Result<Vec<u8>> { + // Open the file in read-only mode + let mut file = File::open(path)?; + + // Create a buffer to hold the file contents + let mut buffer = Vec::new(); + + // Read the file contents into the buffer + file.read_to_end(&mut buffer)?; + + // Return the buffer + Ok(buffer) +} + +fn handle_echo(request: &Request, ctx: Option<&HashMap<String, String>>) -> Response { + let mut headers = HashMap::new(); + // Extract the route regardless of the variant + let mut echo_string = "".to_string(); + let route = match request.method() { + Method::Get(route) | Method::Post(route) | Method::Put(route) => route, + }; + + for ch in route.chars().skip(1).skip_while(|&ch| ch != '/').skip(1) { + echo_string.push(ch); + } + if echo_string.chars().last().unwrap() == '/' { + echo_string.pop(); + } + let len = echo_string.len().to_string(); + headers.insert("Content-Type".to_string(), "text/plain".to_string()); + headers.insert("Content-Length".to_string(), len); + let body = echo_string; + Response::new( + "1.1".to_string(), + StatusCode::Ok, + Some(Headers(headers)), + Some(body), + ) +} + +fn handle_files(request: &Request, ctx: Option<&HashMap<String, String>>) -> Response { + // Extract the route regardless of the variant + let mut file = "".to_string(); + let route = match request.method() { + Method::Get(route) | Method::Post(route) | Method::Put(route) => route, + }; + + let mut directory = ctx.unwrap().get(&"dir".to_string()).unwrap().clone(); + directory.pop(); // remove last slash + + for ch in route.chars().skip(1).skip_while(|&ch| ch != '/') { + file.push(ch); + } + if file.chars().last().unwrap() == '/' { + file.pop(); + } + let len = file.len().to_string(); + + let full_path = &(directory + &file); + dbg!(full_path); + + match read_file_as_bytes(full_path) { + Ok(bytes) => { + let mut headers = HashMap::new(); + headers.insert( + "Content-Type".to_string(), + "application/octet-stream".to_string(), + ); + headers.insert("Content-Length".to_string(), bytes.len().to_string()); + let body = String::from_utf8(bytes).unwrap(); + Response::new( + "1.1".to_string(), + StatusCode::Ok, + Some(Headers(headers)), + Some(body), + ) + } + Err(_) => Response::new("1.1".to_string(), StatusCode::NotFound, None, None), + } +} + +fn handle_user_agent(request: &Request, ctx: Option<&HashMap<String, String>>) -> Response { + let mut headers = HashMap::new(); + let user_agent = request.get_tag("User-Agent".to_string()); + let len = user_agent.len().to_string(); + headers.insert("Content-Type".to_string(), "text/plain".to_string()); + headers.insert("Content-Length".to_string(), len); + let body = user_agent.to_string(); + Response::new( + "1.1".to_string(), + StatusCode::Ok, + Some(Headers(headers)), + Some(body), + ) + .into() +} + +fn handle_success(request: &Request, ctx: Option<&HashMap<String, String>>) -> Response { + Response::new("1.1".to_string(), StatusCode::Ok, None, None).into() +} + +fn handle_not_found(request: Request, ctx: Option<&HashMap<String, String>>) -> Response { + Response::new("1.1".to_string(), StatusCode::NotFound, None, None).into() +} + +fn serve( + mut stream: TcpStream, + router: Arc<Mutex<Router>>, + ctx: Arc<Mutex<HashMap<String, String>>>, +) -> io::Result<usize> { // Buffer to store the data received from the client let mut buffer = [0; 512]; @@ -17,107 +130,78 @@ fn handle_client(mut stream: TcpStream) { match stream.read(&mut buffer) { Ok(_) => { // Convert buffer to a string and print the received data - if let Ok(request) = str::from_utf8(&buffer) { - println!("Received request: {}", request); - let request = Request::from(request); - println!("Request after parsing: {:?}", request); - let succses: String = - Response::new("1.1".to_string(), StatusCode::Ok, None, None).into(); - let succses = succses.as_bytes(); - - let not_found: String = - Response::new("1.1".to_string(), StatusCode::NotFound, None, None).into(); - let not_found = not_found.as_bytes(); - - match request.method { - HTTPMethod::Get((endpoint, target)) => { - match (endpoint.as_str(), target.as_str()) { - ("", "") => match stream.write(succses) { - Ok(_) => { - println!("Response sent successfully"); - } - Err(e) => eprintln!("Failed to send response: {}", e), - }, - ("echo", target) => { - let mut headers = HashMap::new(); - headers - .insert("Content-Type".to_string(), "text/plain".to_string()); - headers.insert( - "Content-Length".to_string(), - (target.len() - 1).to_string(), - ); - let body = target[0..target.len() - 1].to_string(); - let response: String = Response::new( - "1.1".to_string(), - StatusCode::Ok, - Some(Headers(headers)), - Some(body), - ) - .into(); - dbg!(&response); - let response = response.as_bytes(); - - match stream.write(response) { - Ok(_) => { - println!("Response sent successfully"); - println!("Hello echo"); - } - Err(e) => eprintln!("Failed to send response: {}", e), - } - } - ("user-agent", _) => { - let mut headers = HashMap::new(); - let user_agent: String = - request.headers.unwrap().0.get("User-Agent").unwrap().into(); - headers - .insert("Content-Type".to_string(), "text/plain".to_string()); - headers.insert( - "Content-Length".to_string(), - user_agent.len().to_string(), - ); - let body = user_agent; - let response: String = Response::new( - "1.1".to_string(), - StatusCode::Ok, - Some(Headers(headers)), - Some(body), - ) - .into(); - dbg!(&response); - let response = response.as_bytes(); - - match stream.write(response) { - Ok(_) => { - println!("Response sent successfully"); - println!("Hello user-agent"); - } - Err(e) => eprintln!("Failed to send response: {}", e), - } - } - _ => match stream.write(not_found) { - Ok(_) => println!("Response sent successfully"), - Err(e) => eprintln!("Failed to send response: {}", e), - }, - } - } - HTTPMethod::Post(_target) => todo!(), - HTTPMethod::Put(_target) => todo!(), + match str::from_utf8(&buffer) { + Ok(request) => { + use Method::*; + println!("Received request:\n{}", request); + let request_lines: Vec<&str> = request.split("\r\n").collect(); + let request = Request::from(request_lines); + let request_string: String = (&request).into(); + + println!("Request after parsing:\n{}", request_string); + dbg!(&request.method); + + let response: String = { + let router = router.lock().unwrap(); + let ctx = ctx.lock().unwrap(); + router.handle(&request, Some(&ctx)).into() + }; + stream.write(response.as_bytes()) } + Err(_) => todo!(), } } - Err(e) => { - eprintln!("Failed to read from stream: {}", e); - } + Err(_) => todo!(), } } fn main() -> io::Result<()> { + // Collect the command-line arguments + let args: Vec<String> = std::env::args().collect(); + + let mut dir = "".to_string(); + + let ctx = Arc::new(Mutex::new(HashMap::new())); + + // Check if the correct number of arguments are provided + if args.len() == 3 { + // Parse the arguments + if args[1] == "--directory" { + dir += &args[2]; + println!("Directory: {}", dir); + } else { + eprintln!("Unknown argument: {}", args[1]); + eprintln!("Usage: {} --directory <path>", args[0]); + return Ok(()); + } + } else { + eprintln!("Usage: {} --directory <path>", args[0]); + } + let listener = TcpListener::bind("127.0.0.1:4221").unwrap(); + let router = Arc::new(Mutex::new(Router::new())); + ctx.lock().unwrap().insert("dir".to_string(), dir); + + { + let mut router = router.lock().unwrap(); + router + .route(get("/"), handle_success) + .route(get("/echo/:var/"), handle_echo) + .route(get("/user-agent/"), handle_user_agent) + .route(get("/files/:file/"), handle_files); + } + for stream in listener.incoming() { match stream { Ok(stream) => { - thread::spawn(move || handle_client(stream)); + let router = Arc::clone(&router); + let ctx = Arc::clone(&ctx); + thread::spawn(move || { + if let Err(e) = serve(stream, router, ctx) { + eprintln!("Failed to serve connection: {}", e); + } + }); } Err(e) => { eprintln!("error: {}", e); |
