Skip to content
Snippets Groups Projects
Commit 53a054d8 authored by codecraft's avatar codecraft :crocodile:
Browse files

Update Code to use structs `Uri` and `RawUri`

parent c4e5ed7a
No related branches found
No related tags found
1 merge request!2Project Completed
......@@ -20,7 +20,6 @@ git clone https://edugit.org/codecraft/webserver
```
cd webserver
git checkout new
cd site
cargo run
```
......
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(),
));
......
......@@ -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]
......
......@@ -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,
......
......@@ -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(),
......
......@@ -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
......
......@@ -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));
......
pub mod mime;
pub mod url_utils;
pub mod urlencoded;
pub mod vec_utils;
......@@ -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),
......
......@@ -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::{
......
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);
}
}
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;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment