From 07c54da628ec776627e68c25196539389c8d965c Mon Sep 17 00:00:00 2001 From: omagdy7 Date: Wed, 5 Jun 2024 19:10:30 +0300 Subject: codecrafters submit [skip ci] --- src/extractor.rs | 2 +- src/http_types.rs | 2 ++ src/main.rs | 49 ++++++++++++++++++++++++++++++++++++++++---- src/request.rs | 10 ++++++++- src/router.rs | 61 ++++++++++++++++++++++++++++++++++++------------------- 5 files changed, 97 insertions(+), 27 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index edc7b2e..9b53215 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -6,7 +6,7 @@ pub fn build_regex_from_path(path_template: &str) -> Regex { for component in path_template.split('/') { if component.starts_with(':') { // Replace placeholder with regex to capture alphanumeric, underscores, or hyphens - regex_string.push_str("/([a-zA-Z0-9_-]+)"); + regex_string.push_str("/([a-zA-Z0-9_.-]+)"); } else if !component.is_empty() { // Escape and add literal components to the regex regex_string.push('/'); diff --git a/src/http_types.rs b/src/http_types.rs index a196f25..79bf015 100644 --- a/src/http_types.rs +++ b/src/http_types.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; pub enum StatusCode { // 2xx Success Ok, + Created, // 4xx Client Error BadRequest, @@ -36,6 +37,7 @@ impl From for String { use StatusCode::*; match val { Ok => "200 OK".to_string(), + Created => "201 Created".to_string(), BadRequest => "400 Bad Request".to_string(), NotFound => "404 Not Found".to_string(), } diff --git a/src/main.rs b/src/main.rs index ccdaa37..bba2ccf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #![allow(unused)] use http_server_starter_rust::router::Router; use itertools::Itertools; +use nom::AsBytes; use std::collections::HashMap; use std::fs::File; use std::io::{self, Read, Write}; @@ -13,6 +14,12 @@ use http_server_starter_rust::request::*; use http_server_starter_rust::response::*; use http_server_starter_rust::{extractor, http_types::*}; +fn save_bytes_to_file(bytes: &[u8], file_path: &str) -> io::Result<()> { + let mut file = File::create(file_path)?; + file.write_all(bytes)?; + Ok(()) +} + fn read_file_as_bytes(path: &str) -> io::Result> { // Open the file in read-only mode let mut file = File::open(path)?; @@ -53,6 +60,39 @@ fn handle_echo(request: &Request, ctx: Option<&HashMap>) -> Resp ) } +fn handle_post_files(request: &Request, ctx: Option<&HashMap>) -> 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); + println!("post_files"); + dbg!(full_path); + let bytes = request.body().as_ref().unwrap(); + let body = bytes.as_bytes(); + + match save_bytes_to_file(body, full_path) { + Ok(bytes) => Response::new("1.1".to_string(), StatusCode::Created, None, None), + Err(err) => { + println!("Error: {err}"); + Response::new("1.1".to_string(), StatusCode::NotFound, None, None) + } + } +} + fn handle_files(request: &Request, ctx: Option<&HashMap>) -> Response { // Extract the route regardless of the variant let mut file = "".to_string(); @@ -72,6 +112,7 @@ fn handle_files(request: &Request, ctx: Option<&HashMap>) -> Res let len = file.len().to_string(); let full_path = &(directory + &file); + println!("handle_files"); dbg!(full_path); match read_file_as_bytes(full_path) { @@ -135,11 +176,10 @@ fn serve( use Method::*; println!("Received request:\n{}", request); let request_lines: Vec<&str> = request.split("\r\n").collect(); + dbg!(&request_lines); let request = Request::from(request_lines); let request_string: String = (&request).into(); - - println!("Request after parsing:\n{}", request_string); - dbg!(&request.method); + println!("body:\n{:?}", request.body()); let response: String = { let router = router.lock().unwrap(); @@ -189,7 +229,8 @@ fn main() -> io::Result<()> { .route(get("/"), handle_success) .route(get("/echo/:var/"), handle_echo) .route(get("/user-agent/"), handle_user_agent) - .route(get("/files/:file/"), handle_files); + .route(get("/files/:file/"), handle_files) + .route(post("/files/:file/"), handle_post_files); } for stream in listener.incoming() { diff --git a/src/request.rs b/src/request.rs index 27a41f5..aed69e5 100644 --- a/src/request.rs +++ b/src/request.rs @@ -47,7 +47,15 @@ impl From> for Request { [request_line, headers @ .., body] => { let (method, headers, body) = (Method::from(*request_line), Headers::from(headers), body); - Request::new(method, headers, (*body).to_string()) + if let Some(content_length) = headers.0.get("Content-Length") { + println!("Well hello there"); + let content_length = content_length + .parse::() + .expect("Content-Length should be parsable to usize"); + Request::new(method, headers, (body[0..content_length]).to_string()) + } else { + Request::new(method, headers, (*body).to_string()) + } } _ => { unreachable!(); diff --git a/src/router.rs b/src/router.rs index 1d8e6f2..cbbc64a 100644 --- a/src/router.rs +++ b/src/router.rs @@ -29,34 +29,53 @@ impl Router { // Add a route to the router pub fn route(&mut self, method: Method, handler: Handler) -> &mut Self { use Method::*; - - let method_string = match &method { - Get(s) | Post(s) | Put(s) => s, - }; - - let re = build_regex_from_path(method_string.as_str()); - let meth = Get(re.to_string()); - dbg!(&meth); - self.routes.insert(meth, handler); + match method { + Get(route) => { + let re = build_regex_from_path(&route); + let meth = Get(re.to_string()); + dbg!(&meth); + self.routes.insert(meth, handler); + } + Post(route) => { + let re = build_regex_from_path(&route); + let meth = Post(re.to_string()); + dbg!(&meth); + self.routes.insert(meth, handler); + } + Put(_) => todo!(), + } self } // Handle incoming requests pub fn handle(&self, request: &Request, ctx: Option<&HashMap>) -> Response { use Method::*; - let method_string = match &request.method { - Get(s) | Post(s) | Put(s) => s, - }; - for (method, handler) in self.routes() { - let route_method = match method { - Get(s) | Post(s) | Put(s) => s.as_str(), - }; - let re = Regex::new(route_method).unwrap(); - dbg!(&re, method_string); - if re.is_match(method_string) { - return handler(request, ctx); + match &request.method { + Get(request_method) => { + for (method, handler) in self.routes() { + if let Get(method_string) = method { + let re = Regex::new(method_string).unwrap(); + dbg!(&re, request_method); + if re.is_match(request_method) { + return handler(request, ctx); + } + } + } + Response::new("1.1".to_string(), StatusCode::NotFound, None, None).into() + } + Post(request_method) => { + for (method, handler) in self.routes() { + if let Post(method_string) = method { + let re = Regex::new(method_string).unwrap(); + dbg!(&re, request_method); + if re.is_match(request_method) { + return handler(request, ctx); + } + } + } + Response::new("1.1".to_string(), StatusCode::NotFound, None, None).into() } + Put(_) => todo!(), } - Response::new("1.1".to_string(), StatusCode::NotFound, None, None).into() } } -- cgit v1.2.3