diff --git a/README.md b/README.md index 008b3d9c8619b3be7adcb945a334e4778fd92ed4..356738042d02f94de03d0fdfb634a06fcde1d81a 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ git clone https://edugit.org/codecraft/webserver ``` cd webserver -git checkout new cd site cargo run ``` diff --git a/core/http/src/handlers/handler.rs b/core/http/src/handlers/handler.rs index 38315a1cea3bf365d89699c3315bb5686edcc2f7..258e2b70aaa007dde0cd444ef5b264515b6cdc8b 100644 --- a/core/http/src/handlers/handler.rs +++ b/core/http/src/handlers/handler.rs @@ -1,4 +1,4 @@ -use std::{io, path::PathBuf}; +use std::{io, path::PathBuf, ops::Deref}; use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader, AsyncWrite, AsyncRead}; @@ -8,8 +8,7 @@ use crate::{handling::{ request::Request, response::{Outcome, Response, ResponseBody, Status}, routes::{Body, Data}, -}, utils::urlencoded::UrlEncodeData}; -use crate::setup::MountPoint; +}, setup::MountPoint}; /// The Maximal size of the Body of an HTTP-Message in bytes static MAX_HTTP_MESSAGE_SIZE: u16 = 4196; @@ -25,7 +24,7 @@ static MAX_HTTP_MESSAGE_SIZE: u16 = 4196; /// /// # Panics /// No Panics -pub async fn handle_connection<T: AsyncRead + AsyncWrite + std::marker::Unpin>(mut stream: T, mountpoints: Vec<MountPoint<'_>>) { +pub async fn handle_connection<T: AsyncRead + AsyncWrite + std::marker::Unpin>(mut stream: T, mountpoints: Vec<MountPoint>) { let mut buf_reader = BufReader::new(&mut stream); let mut http_request: Vec<String> = Vec::with_capacity(10); loop { @@ -49,7 +48,7 @@ pub async fn handle_connection<T: AsyncRead + AsyncWrite + std::marker::Unpin>(m let mut request = Request { uri: if let Some(uri) = &request_status_line.split(' ').nth(1) { - if let Ok(uri) = UrlEncodeData::from_encoded(uri) { + if let Ok(uri) = uri.deref().try_into() { uri } else { eprintln!("\x1b[31mAborting due to invalid uri\x1b[0m"); @@ -132,27 +131,21 @@ pub async fn handle_connection<T: AsyncRead + AsyncWrite + std::marker::Unpin>(m let mut handled_response: Option<Outcome<Response, Status, Data>> = None; for mountpoint in mountpoints { - if request.uri.raw_string().is_none() { - return; - } - if !request.uri.raw_string().unwrap().starts_with(mountpoint.mountpoint) { + if !mountpoint.compare_with_uri(&mut request.uri) { continue; } - let mounted_request_uri = request.uri.raw_string().unwrap().strip_prefix(mountpoint.mountpoint).unwrap(); for route in mountpoint.routes { if (route.method != request.method) && ((route.method != Method::Get) && (request.method == Method::Head)) { continue; } - if !route.compare_uri(mounted_request_uri) { + if !route.uri.compare_uri(&request.uri) { continue; } handled_response = Some((route.handler)( - Request { - uri: UrlEncodeData::from_raw(mounted_request_uri), - ..request.clone() - }, + request.clone() + , data.clone(), )); diff --git a/core/http/src/handling/request/datatypes.rs b/core/http/src/handling/request/datatypes.rs index 44f1cf3e1fe0083f84da94b337f167a14fece4bf..56316f54834e397f3018bafa88577c7f02258ee0 100644 --- a/core/http/src/handling/request/datatypes.rs +++ b/core/http/src/handling/request/datatypes.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, error::Error, fmt::Display}; use crate::{ handling::methods::Method, - utils::{mime::Mime, urlencoded::UrlEncodeData}, + utils::{mime::Mime, url_utils::Uri}, }; type HeaderMap = Vec<String>; @@ -12,7 +12,7 @@ type HeaderMap = Vec<String>; #[derive(Clone)] pub struct Request { /// The requested Uri - pub uri: UrlEncodeData, + pub uri: Uri, /// All headers of the request that haven't been parsed pub headers: HeaderMap, /// The methods Request represented with the [Method] diff --git a/core/http/src/handling/request/form_utils.rs b/core/http/src/handling/request/form_utils.rs index cc8999d7cc5eef4d0fa667c9c93d0dff0442572c..5f2b81b8c53b604180e6810f3dddea70f6f04acb 100644 --- a/core/http/src/handling/request/form_utils.rs +++ b/core/http/src/handling/request/form_utils.rs @@ -66,10 +66,15 @@ impl Request { &'a self, keys: &'a [&str], ) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> { - let Some(uri) = self.uri.raw_string() else { - return Err(ParseFormError { error: ParseErrors::BadData }); - }; - let data = if let Some(val) = uri.split_once('?') { + let data = if let Some(val) = self + .uri + .parts() + .last() + .unwrap() + .raw_string() + .unwrap() + .split_once('?') + { val } else { return Err(ParseFormError { @@ -243,6 +248,8 @@ impl Request { } #[cfg(test)] mod test { + use std::str::FromStr; + use crate::{ handling::{ methods::Method, @@ -251,7 +258,7 @@ mod test { }, utils::{ mime::Mime::{ApplicationXWwwFormUrlencoded, MultipartFormData}, - urlencoded::UrlEncodeData, + url_utils::Uri, }, }; @@ -259,7 +266,7 @@ mod test { #[test] fn try_get_test() { let request = Request { - uri: UrlEncodeData::from_encoded("/form?name=Name&age=Age").unwrap(), + uri: "/form?name=Name&age=Age".try_into().unwrap(), headers: vec![], method: Method::Get, cookies: None, @@ -270,7 +277,7 @@ mod test { assert_eq!(&"Age", right.get("age").unwrap().as_ref().unwrap()); let wrong_request = Request { - uri: UrlEncodeData::from_encoded("/form").unwrap(), + uri: "/form".try_into().unwrap(), ..request.clone() }; assert_eq!( @@ -281,7 +288,7 @@ mod test { ); let bad_data = Request { - uri: UrlEncodeData::from_encoded("/form?age=").unwrap(), + uri: "/form?age=".try_into().unwrap(), ..request.clone() }; let wrong = bad_data.get_get_form_keys(&["name", "age"]).unwrap(); @@ -297,7 +304,7 @@ mod test { #[test] fn try_post_text() { let req = Request { - uri: UrlEncodeData::from_encoded("").unwrap(), + uri: "".try_into().unwrap(), headers: vec!["Content-Type: application/x-www-form-urlencoded".to_string()], method: Method::Post, cookies: None, @@ -317,7 +324,7 @@ mod test { map.get("message1").unwrap().as_ref().unwrap() ); let req = Request { - uri: UrlEncodeData::from_encoded("").unwrap(), + uri: "".try_into().unwrap(), headers: vec!["Content-Type: multipart/form-data; boundary=\"boundary\"".to_string()], method: Method::Post, cookies: None, diff --git a/core/http/src/handling/request/request_mime.rs b/core/http/src/handling/request/request_mime.rs index 75ad1e547677443479764d6b94013aacd57156ec..005be864655f10ddf851e1d239257f4a42fd16ac 100644 --- a/core/http/src/handling/request/request_mime.rs +++ b/core/http/src/handling/request/request_mime.rs @@ -69,15 +69,16 @@ impl Request { #[cfg(test)] mod test { + use crate::{ handling::{methods::Method, request::Request}, - utils::{mime::Mime, urlencoded::UrlEncodeData}, + utils::mime::Mime, }; #[test] pub fn test_mime_parse_from_header_vec() { let mut request = Request { - uri: UrlEncodeData::from_raw("thing"), + uri: "thing".try_into().unwrap(), headers: vec![ "GET / 23".to_string(), "SDF:LKJSD:F".to_string(), @@ -90,7 +91,7 @@ mod test { }; let mut wrong = Request { - uri: UrlEncodeData::from_raw("thing"), + uri: "thing".try_into().unwrap(), headers: vec![ "GET / 23".to_string(), "SDF:LKJSD:F".to_string(), diff --git a/core/http/src/handling/routes.rs b/core/http/src/handling/routes.rs index 02a81a3ac8bfab8712c48018e8589abbcef9b8e6..52ee733ac7d792f2800103e8d846b776b4c6a3ca 100644 --- a/core/http/src/handling/routes.rs +++ b/core/http/src/handling/routes.rs @@ -4,7 +4,7 @@ use crate::{ request::{MediaType, Request}, response::{Outcome, Response, Status}, }, - utils::mime::Mime, + utils::{mime::Mime, url_utils::RawUri}, }; /// A RouteBuilder struct @@ -39,8 +39,8 @@ pub struct RoutBuilder { } /// A struct to define Routes on the Server -#[derive(Clone, Copy)] -pub struct Route<'a> { +#[derive(Clone)] +pub struct Route { /// An optional name of the route pub name: Option<&'static str>, /// The [Method] via which the route is accesable @@ -55,7 +55,7 @@ pub struct Route<'a> { /// // All Information after this sequence is irrelvent /// // Matches: /a, /a/b/c ... /// ``` - pub uri: Uri<'a>, + pub uri: RawUri, /// The Handler function for this route, which gets called when the request need the route. /// Inputs to the function are an [Request] and the [Data] which represents the body of the /// [Request]. The Outcome is expected to be an [Outcome], which is a [Response], A [Status] if @@ -69,7 +69,7 @@ pub struct Route<'a> { pub format: Option<MediaType>, } -impl Route<'_> { +impl Route { /// generates a Route from a Routebuilder //TODO: ranking pub fn from(routeinfo: RoutBuilder) -> Self { @@ -77,39 +77,18 @@ impl Route<'_> { Route { name: routeinfo.name, method: routeinfo.method, - uri: routeinfo.path, + uri: routeinfo + .path + .try_into() + .unwrap_or_else(|_| panic!("Incorrect RawUri for path {}", routeinfo.path)), handler: routeinfo.handler, rank, format: routeinfo.format, } } - /// Matches a [Request] Uri with a [Route] Uri. Respecting special cases like `?` and `<a..>` - 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("..>")) - || (comp_str.starts_with(true_str) && comp_str.contains('?')) - { - return true; - } - if true_str.starts_with('<') && true_str.ends_with('>') { - continue; - } - if true_str != comp_str { - return false; - } - } - true - } } /// Alias for using a &'a str for Uri -pub type Uri<'a> = &'a str; #[derive(Debug, Clone)] /// A basic Body type for respones diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index 9802f0edd3498c8a6ac71a53b5ba66ac29642afc..6b6e85b91788fb451319cfdaceceb8af6ed0a1b7 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -11,7 +11,7 @@ use tokio_native_tls::{native_tls::{Identity, self}, TlsAcceptor}; use crate::{ handlers::handler::handle_connection, - handling::routes::{Route, Uri}, + handling::routes::Route, utils::{url_utils::Uri, vec_utils::remove_n}, }; #[cfg(feature = "secure")] use crate::handling::response::{Response, Status}; @@ -19,19 +19,19 @@ use crate::handling::response::{Response, Status}; #[derive(Clone)] /// Represnts a [MountPoint] that can be mounted in the config -pub struct MountPoint<'a> { +pub struct MountPoint { /// The prefix of the [MountPoint] - pub mountpoint: Uri<'a>, + pub mountpoint: Uri, /// All Routes mounted on the [MountPoint]. The Routes are all prefixed by the mountpoints /// mountpoint - pub routes: Vec<Route<'a>>, + pub routes: Vec<Route>, } /// A server configuration that is run pub struct Config { /// Contains an Optional [`Vec<MountPoint>`]. which contains all [MountPoint]s on the Server. /// Which contain all the [Route]s - mountpoints: Option<Vec<MountPoint<'static>>>, + mountpoints: Option<Vec<MountPoint>>, /// Contains a [tokio::net::TcpListener] that is bound for the server address: TcpListener, #[cfg(feature = "secure")] @@ -40,13 +40,30 @@ pub struct Config { tls_acceptor: TlsAcceptor, } +impl MountPoint { + pub fn compare_with_uri(&self, uri: &mut Uri) -> bool { + let mut uri_iter = uri.parts().iter(); + let to_remove_after_finish = self.mountpoint.parts().len(); + for part in self.mountpoint.parts().iter() { + let Some(part_uri) = uri_iter.next() else { + return false; + }; + if part != part_uri { + return false; + } + } + remove_n(uri.mut_parts(), to_remove_after_finish); + true + } +} + impl<'a> Config { /// Utility that checks if the given mointpoint is already taken. takes in the uri of the to be /// mounted mountpoint - fn check_mountpoint_taken(&self, to_insert: Uri) -> bool { + fn check_mountpoint_taken(&self, to_insert: &Uri) -> bool { if let Some(to_check) = &self.mountpoints { for i in to_check.iter() { - if i.mountpoint == to_insert { + if i.mountpoint == *to_insert { return true; // Found a duplicate &str } } @@ -55,9 +72,9 @@ impl<'a> Config { } /// mounts a [MountPoint] on the [Config] takes in a blank [MountPoint] and the [Route]s to be /// mounted, mounts them and inserts the new [MountPoint]. Also sorts by rank. - pub fn mount(mut self, mountpoint: Uri<'static>, mut routes: Vec<Route<'static>>) -> Self { - if self.check_mountpoint_taken(mountpoint) { - eprintln!("\x1b[31mTrying to reassign a mountpoint, mountpoint `{mountpoint}` already taken.\x1b[0m"); + pub fn mount(mut self, mountpoint: Uri, mut routes: Vec<Route>) -> Self { + if self.check_mountpoint_taken(&mountpoint) { + eprintln!("\x1b[31mTrying to reassign a mountpoint, mountpoint `{}` already taken.\x1b[0m", mountpoint); return self; } routes.sort_by(|a, b| a.rank.cmp(&b.rank)); diff --git a/core/http/src/utils/mod.rs b/core/http/src/utils/mod.rs index 6f54242fa58e4f066dad1846c4b4bc586b002a32..893f1e7201591035ccce0ef4c34ea69a7fd77784 100644 --- a/core/http/src/utils/mod.rs +++ b/core/http/src/utils/mod.rs @@ -1,3 +1,4 @@ pub mod mime; pub mod url_utils; pub mod urlencoded; +pub mod vec_utils; diff --git a/core/http/src/utils/url_utils/datatypes.rs b/core/http/src/utils/url_utils/datatypes.rs index 6e360807ec57cfb988d5e4f2664873f72791b807..8c94a3e02496dbc559c176eec4be0fc8e56f810c 100644 --- a/core/http/src/utils/url_utils/datatypes.rs +++ b/core/http/src/utils/url_utils/datatypes.rs @@ -7,14 +7,14 @@ pub struct Uri { pub(super) parts: Vec<UrlEncodeData>, } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct RawUri { pub(super) raw_string: String, pub(super) infinte_end: bool, pub(super) parts: Vec<RawUriElement>, } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub enum RawUriElement { Variable, Name(UrlEncodeData), diff --git a/core/http/src/utils/url_utils/uri.rs b/core/http/src/utils/url_utils/uri.rs index 6b8146be14f1297f202e4fc59bcaa93928a5fbd5..a8a72bfdefc7e41454d8031fc67393ca6ece3dff 100644 --- a/core/http/src/utils/url_utils/uri.rs +++ b/core/http/src/utils/url_utils/uri.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use crate::utils::{url_utils::datatypes::RawUriElement, urlencoded::UrlEncodeData}; -use super::datatypes::{ParseUriError, RawUri, Uri, UriError}; +use super::datatypes::{ParseUriError, RawUri, Uri}; impl Uri { pub fn new(parts: Vec<&str>) -> Self { @@ -10,6 +10,24 @@ impl Uri { parts: parts.into_iter().map(UrlEncodeData::from_raw).collect(), } } + pub fn parts(&self) -> &[UrlEncodeData] { + self.parts.as_slice() + } + pub fn mut_parts(&mut self) -> &mut Vec<UrlEncodeData> { + self.parts.as_mut() + } + pub fn compare(&self, uri: &Uri) -> bool { + let mut uri_iter = uri.parts.iter(); + for part in self.parts.iter() { + let Some(part_uri) = uri_iter.next() else { + return false; + }; + if part != part_uri { + return false; + } + } + true + } } impl RawUri { @@ -34,7 +52,7 @@ impl RawUri { } result } - pub fn compare_uri(self, uri: Uri) -> bool { + pub fn compare_uri(self, uri: &Uri) -> bool { let mut iter_comp = uri.parts.iter(); let mut counter = 0; for element in self.parts.iter() { @@ -81,6 +99,11 @@ impl std::fmt::Display for Uri { impl FromStr for Uri { type Err = ParseUriError; fn from_str(s: &str) -> Result<Self, Self::Err> { + if s == "/" { + return Ok(Self { + parts: vec![UrlEncodeData::from_encoded("").unwrap()], + }); + } let split = s.split('/'); let mut result = Vec::new(); for sub in split { @@ -98,8 +121,7 @@ impl FromStr for Uri { } impl FromStr for RawUri { - type Err = UriError; - + type Err = ParseUriError; fn from_str(s: &str) -> Result<Self, Self::Err> { let parts = s.split('/').collect::<Vec<&str>>(); let mut result = Self { @@ -127,6 +149,21 @@ impl FromStr for RawUri { } } +impl TryFrom<&str> for Uri { + type Error = ParseUriError; + + fn try_from(value: &str) -> Result<Self, Self::Error> { + Self::from_str(value) + } +} + +impl TryFrom<&str> for RawUri { + type Error = ParseUriError; + fn try_from(value: &str) -> Result<Self, Self::Error> { + Self::from_str(value) + } +} + #[cfg(test)] mod test { use crate::utils::{ diff --git a/core/http/src/utils/vec_utils.rs b/core/http/src/utils/vec_utils.rs new file mode 100644 index 0000000000000000000000000000000000000000..d03ad1ccd7d25b3327680c0d2afda6fa551cde29 --- /dev/null +++ b/core/http/src/utils/vec_utils.rs @@ -0,0 +1,8 @@ +pub(crate) fn remove_n<T>(vec: &mut Vec<T>, n: usize) { + if n > vec.len() { + return; + } + for i in 0..n { + vec.remove(i); + } +} diff --git a/site/src/main.rs b/site/src/main.rs index 3520b3bd3c4dd083cea7c153cd36180312dff64f..c98c43971c1b10604295e10c16cbd3208cfd901b 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -1,7 +1,12 @@ -use http::handling::{methods::Method, routes::{Route, Data}, request::Request, response::{Response, Outcome, Status}, file_handlers::NamedFile}; +use http::handling::{ + methods::Method, + routes::{Route, Data}, + request::Request, + response::{Response, Outcome, Status}, + file_handlers::NamedFile}; fn static_files_handler(request: Request<>, _data: Data) -> Outcome<Response, Status, Data> { - let response = static_files(request.uri.raw_string().unwrap()); + let response = static_files(&request.uri.to_string()); let response = match response { Ok(dat) => Response { headers: vec![], @@ -42,7 +47,7 @@ async fn main() { format: None, handler: index_handler, name: Some("index"), - uri: "", + uri: "".try_into().unwrap(), method: Method::Get, rank: 0, }; @@ -51,7 +56,7 @@ async fn main() { format: None, handler: static_files_handler, name: Some("static files"), - uri: "", + uri: "".try_into().unwrap(), method: Method::Get, rank: 0 }; @@ -60,7 +65,7 @@ async fn main() { format: None, handler: favicon_handler, name: Some("favicon"), - uri: "favicon.ico", + uri: "favicon.ico".try_into().unwrap(), method: Method::Get, rank: 0 }; @@ -68,8 +73,8 @@ async fn main() { // http::build("127.0.0.1:8000") http::build("127.0.0.1:8443", "127.0.0.1:8080") .await - .mount("/", vec![index_route, favicon]) - .mount("/static", vec![static_route]) + .mount("/".try_into().unwrap(), vec![index_route, favicon]) + .mount("/static".try_into().unwrap(), vec![static_route]) .launch() .await; }