From a6ffcabd22044eb87ce6f2c28ce7405967ab696a Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sun, 14 May 2023 13:25:15 +0200 Subject: [PATCH] Add addable routes --- core/http/src/handlers/handlers.rs | 112 ++++++++++++++--------------- core/http/src/lib.rs | 4 +- core/http/src/routing/methods.rs | 2 +- core/http/src/routing/routes.rs | 42 ++++++++--- core/http/src/setup.rs | 42 ++++++----- site/404.html | 6 +- site/src/main.rs | 43 ++++++++++- 7 files changed, 163 insertions(+), 88 deletions(-) mode change 100644 => 100755 core/http/src/handlers/handlers.rs diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs old mode 100644 new mode 100755 index 4f9073e..c3e06cf --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -1,16 +1,15 @@ use std::{ - fs, io::{BufRead, BufReader, Write}, net::TcpStream, path::PathBuf, }; -use crate::routing::methods::Method; use crate::routing::routes::{Data, Outcome, Request, Response, Status}; +use crate::setup::MountPoint; use super::file_handlers::NamedFile; -pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) { +pub fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint>) { let buf_reader = BufReader::new(&mut stream); let http_request: Vec<String> = buf_reader .lines() @@ -25,7 +24,12 @@ pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) { let request = Request { uri: &request_status_line.split(" ").nth(1).unwrap(), headers: http_request.clone(), - method: Method::Get, + method: request_status_line + .split(" ") + .next() + .unwrap() + .parse() + .unwrap(), }; let data = Data { @@ -33,65 +37,57 @@ pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) { is_complete: true, }; - let handled_response = match handler(request, data) { - Outcome::Success(success) => ( - format!("HTTP/1.1 {} OK\r\n", success.status.unwrap().code) - + &success.headers.join("\r\n") - + "\r\n\r\n", - success.body.get_data(), - ), - Outcome::Failure(error) => { - let content = fs::read("./404.html").unwrap(); - ( - format!( - "HTTP/1.1 {} NOT FOUND\r\nContent-Length: {}\r\nContent-Type: text/html\r\n\r\n", - error.code, - content.len() - ), - content, - ) + let mut handled_response: Option<Outcome<Response, Status, Data>> = None; + for mountpoint in mountpoints { + if !request.uri.starts_with(mountpoint.mountpoint) { + println!("MOUNTPOINT COMPARISON {}", request.uri); + continue; + } + let mounted_request_uri = request.uri.strip_prefix(mountpoint.mountpoint).unwrap(); + for route in mountpoint.routes { + if route.method != request.method { + println!("METHOD COMPARE {}", request.method); + continue; + } + if !route.compare_uri(mounted_request_uri) { + println!("URI COMPARISON {} {}", mounted_request_uri, route.uri); + continue; + } + handled_response = Some((route.handler)( + Request { + uri: mounted_request_uri, + ..request.clone() + }, + data.clone(), + )); } - Outcome::Forward(_) => ( - format!("HTTP/1.1 {} NOT FOUND", 404), - fs::read("./404.html").unwrap(), - ), - }; - - stream.write_all(handled_response.0.as_bytes()).unwrap(); - stream.write(&handled_response.1).unwrap(); -} - -fn parse_request<'a>(request: &'a str, mountpoint: &'a str) -> Result<PathBuf, &'a str> { - let uri = request.split(" ").nth(1).unwrap(); - if !uri.starts_with(mountpoint) { - return Err("Request isn't on mountpoint"); } - let path_elements = uri - .split_once(mountpoint) - .map(|(_, rest)| rest) - .unwrap() - .split("/") - .filter(|&val| val != ".." && val != "") - .collect::<Vec<&str>>(); - Ok(PathBuf::from(format!("./{}", path_elements.join("/")))) -} -fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> { - let response = fileserver(request.uri); - let response = match response { - Ok(dat) => Response { - headers: vec![ - format!("Content-Length: {}", dat.content_len), - format!("Content-Type: {}", dat.content_type), - ], - status: Some(Status { code: 200 }), - body: Box::new(dat), + let response = match handled_response { + Some(val) => match val { + Outcome::Success(success) => ( + format!("HTTP/1.1 {} OK\r\n", success.status.unwrap().code) + + &success.headers.join("\r\n") + + "\r\n\r\n", + success.body.get_data(), + ), + Outcome::Failure(error) => handler_404(error), + Outcome::Forward(_) => handler_404(Status { code: 404 }), }, - Err(_) => return Outcome::Failure(Status { code: 404 }), + None => handler_404(Status { code: 404 }), }; - Outcome::Success(response) + + stream.write_all(response.0.as_bytes()).unwrap(); + stream.write(&response.1).unwrap(); } -fn fileserver(path: &str) -> Result<NamedFile, Status> { - NamedFile::open(PathBuf::from("static/".to_string() + path)) +fn handler_404(status: Status) -> (String, Vec<u8>) { + let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap(); + ( + format!( + "HTTP/1.1 {} NOT FOUND\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", + status.code, page_404.content_len, page_404.content_type + ), + page_404.content, + ) } diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs index 0b86db5..bf072c9 100644 --- a/core/http/src/lib.rs +++ b/core/http/src/lib.rs @@ -1,5 +1,5 @@ -mod handlers; -mod routing; +pub mod handlers; +pub mod routing; mod setup; mod threading; diff --git a/core/http/src/routing/methods.rs b/core/http/src/routing/methods.rs index 328e697..998cfa5 100644 --- a/core/http/src/routing/methods.rs +++ b/core/http/src/routing/methods.rs @@ -1,6 +1,6 @@ use std::{fmt::Display, str::FromStr}; -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone, Debug, Copy)] pub enum Method { Get, Head, diff --git a/core/http/src/routing/routes.rs b/core/http/src/routing/routes.rs index 05ecf74..6da1218 100644 --- a/core/http/src/routing/routes.rs +++ b/core/http/src/routing/routes.rs @@ -15,13 +15,14 @@ pub struct RoutInfo { rank: Option<isize>, } +#[derive(Clone, Copy)] pub struct Route<'a> { - name: Option<&'static str>, - method: Method, - uri: Uri<'a>, - handler: fn(Request, Data) -> Outcome<Response, Status, Data>, - rank: isize, - format: Option<MediaType>, + pub name: Option<&'static str>, + pub method: Method, + pub uri: Uri<'a>, + pub handler: fn(Request, Data) -> Outcome<Response, Status, Data>, + pub rank: isize, + pub format: Option<MediaType>, } impl Route<'_> { @@ -36,8 +37,29 @@ impl Route<'_> { format: routeinfo.format, } } + pub fn compare_uri(self, uri: Uri) -> bool { + let mut iter_comp_str = uri.split("/"); + for true_str in self.uri.split("/") { + let comp_str = if let Some(str) = iter_comp_str.next() { + str + } else { + return false; + }; + if true_str.starts_with("<") && true_str.ends_with("..>") { + return true; + } + if true_str.starts_with("<") && true_str.ends_with(">") { + continue; + } + if true_str != comp_str { + return false; + } + } + return true; + } } +#[derive(Clone)] pub struct Request<'a> { pub uri: Uri<'a>, pub headers: HeaderMap, @@ -51,15 +73,17 @@ struct ConnectionMeta { } type HeaderMap = Vec<String>; -type Uri<'a> = &'a str; +pub type Uri<'a> = &'a str; +#[derive(Debug)] pub enum Outcome<S, E, F> { Success(S), Failure(E), Forward(F), } -enum MediaType { +#[derive(Clone, Debug, Copy)] +pub enum MediaType { Json, Plain, Html, @@ -70,6 +94,7 @@ pub struct Status { pub code: u16, } +#[derive(Debug)] pub struct Body { pub size: Option<usize>, pub body: Vec<u8>, @@ -87,6 +112,7 @@ pub struct Response { pub body: Box<dyn ResponseBody>, } +#[derive(Debug, Clone)] pub struct Data { pub buffer: Vec<u8>, pub is_complete: bool, diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index d3b106c..35c8a06 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -7,26 +7,36 @@ use std::{ thread::available_parallelism, }; -use crate::{handlers::handlers::handle_connection, routing::routes::Route, threading::ThreadPool}; +use crate::{ + handlers::handlers::handle_connection, + routing::routes::{Route, Uri}, + threading::ThreadPool, +}; + +#[derive(Clone)] +pub struct MountPoint<'a> { + pub mountpoint: Uri<'a>, + pub routes: Vec<Route<'a>>, +} -pub struct Config<'a> { - mountpoints: Option<Vec<&'static str>>, +pub struct Config { + mountpoints: Option<Vec<MountPoint<'static>>>, address: TcpListener, threadpool: ThreadPool, - routes: Option<Vec<Route<'a>>>, } -impl<'a> Config<'a> { - pub fn mount(self, mountpoint: &'static str, routes: Vec<Route<'static>>) -> Config<'a> { - if self.mountpoints == None { - return Config { - mountpoints: Some(vec![mountpoint]), - routes: Some(routes), - ..self - }; +impl<'a> Config { + pub fn mount(mut self, mountpoint: Uri<'static>, routes: Vec<Route<'static>>) -> Config { + let mut temp_mountpoints = None; + std::mem::swap(&mut self.mountpoints, &mut temp_mountpoints); + + if let Some(mut mountpoints) = temp_mountpoints { + mountpoints.push(MountPoint { mountpoint, routes }); + self.mountpoints = Some(mountpoints); } else { - return self; + self.mountpoints = Some(vec![MountPoint { mountpoint, routes }]); } + self } pub fn launch(self) { let running = Arc::new(AtomicBool::new(true)); @@ -42,15 +52,14 @@ impl<'a> Config<'a> { running_for_handler.store(false, atomic::Ordering::SeqCst); }) .expect("Error setting Ctrl-C handler"); - for stream in self.address.incoming() { if !running.load(atomic::Ordering::SeqCst) { break; } let stream = stream.unwrap(); - let mountpoint = self.mountpoints.clone().unwrap(); + let mountpoints = self.mountpoints.clone().unwrap(); self.threadpool - .execute(move || handle_connection(stream, &mountpoint[0].to_string())) + .execute(move || handle_connection(stream, mountpoints)); } } } @@ -75,7 +84,6 @@ Server has launched from {ip}:{port}. ); Config { mountpoints: None, - routes: None, address: listener, threadpool, } diff --git a/site/404.html b/site/404.html index 3a6ceb5..550831f 100644 --- a/site/404.html +++ b/site/404.html @@ -3,10 +3,14 @@ <head> <meta charset="utf-8" /> <title>404</title> - <link rel="stylesheet" href="/hello.css" /> </head> <body> <h1>404</h1> <p>Hi from Rust</p> + <form action="/" method="POST"> + <label for="message">Enter your message:</label> + <input type="text" id="message" name="message" /> + <button type="submit">Send</button> + </form> </body> </html> diff --git a/site/src/main.rs b/site/src/main.rs index 55ea439..8974db9 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -1,3 +1,44 @@ +use std::path::PathBuf; + +use http::{ + handlers::file_handlers::NamedFile, + routing::routes::{Data, Outcome, Request, Response, Route, Status}, +}; + +fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> { + println!("called"); + let response = fileserver(request.uri.strip_prefix("static/").unwrap()); + let response = match response { + Ok(dat) => Response { + headers: vec![ + format!("Content-Length: {}", dat.content_len), + format!("Content-Type: {}", dat.content_type), + ], + status: Some(Status { code: 200 }), + body: Box::new(dat), + }, + Err(_) => return Outcome::Failure(Status { code: 404 }), + }; + println!("{:?}", response.headers); + Outcome::Success(response) +} + +fn fileserver(path: &str) -> Result<NamedFile, Status> { + let file = NamedFile::open(PathBuf::from("static/".to_string() + path)); + return file; +} + fn main() { - http::build("127.0.0.1:8000").mount("/", vec![]).launch(); + let fileserver = Route { + format: None, + handler, + name: Some("file_server"), + uri: "static/<path..>", + method: http::routing::methods::Method::Get, + rank: 0, + }; + + http::build("127.0.0.1:8000") + .mount("/", vec![fileserver]) + .launch(); } -- GitLab