aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoromagdy7 <omar.professional8777@gmail.com>2024-06-05 19:10:30 +0300
committeromagdy7 <omar.professional8777@gmail.com>2024-06-05 19:10:30 +0300
commit07c54da628ec776627e68c25196539389c8d965c (patch)
tree30255789673c09d4a74e8372796f8ba4988cf4f4 /src
parentff75fa542a98cf9a79133a81b7716401e717bfd6 (diff)
downloadtiny-server-07c54da628ec776627e68c25196539389c8d965c.tar.xz
tiny-server-07c54da628ec776627e68c25196539389c8d965c.zip
codecrafters submit [skip ci]
Diffstat (limited to 'src')
-rw-r--r--src/extractor.rs2
-rw-r--r--src/http_types.rs2
-rw-r--r--src/main.rs49
-rw-r--r--src/request.rs10
-rw-r--r--src/router.rs61
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<StatusCode> 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<Vec<u8>> {
// 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<String, String>>) -> Resp
)
}
+fn handle_post_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);
+ 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<String, String>>) -> 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<String, String>>) -> 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<Vec<&str>> 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::<usize>()
+ .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<String, String>>) -> 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()
}
}