Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • codecraft/webserver
  • sorcaMriete/webserver
  • opnonmicgo/webserver
  • loposuezo/webserver
  • diufeYmike/webserver
  • contbuspecmi/webserver
  • mogamuboun/webserver
  • glabalwelre/webserver
8 results
Show changes
Commits on Source (5)
Showing
with 168 additions and 164 deletions
......@@ -20,7 +20,6 @@ git clone https://edugit.org/codecraft/webserver
```
cd webserver
git checkout new
cd site
cargo run
```
......
/target
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "codegen"
version = "0.1.0"
[package]
name = "codegen"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
/target
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "html"
version = "0.1.0"
[package]
name = "html"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
[package]
name = "http"
version = "0.1.0"
version = "1.0.0"
edition = "2021"
[features]
default = []
secure = []
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
......
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,29 +131,19 @@ 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()
},
data.clone(),
));
handled_response = Some((route.handler)(request.clone(), data.clone()));
if let Some(Outcome::Forward(_)) = handled_response {
continue;
......
......@@ -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 {
......@@ -249,17 +254,14 @@ mod test {
request::{datatypes::ParseErrors, ParseFormError},
routes::Data,
},
utils::{
mime::Mime::{ApplicationXWwwFormUrlencoded, MultipartFormData},
urlencoded::UrlEncodeData,
},
utils::mime::Mime::{ApplicationXWwwFormUrlencoded, MultipartFormData},
};
use super::Request;
#[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 +272,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 +283,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 +299,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 +319,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,
};
#[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,14 @@ pub struct Config {
tls_acceptor: TlsAcceptor,
}
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,13 +56,13 @@ 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));
let mut mount_message = format!(" >> \x1b[35m{}\x1b[0m\n", mountpoint);
let mut mount_message = format!(" >> \x1b[35m{}\x1b[0m\n", mountpoint.to_pretty_print_string());
for (index, route) in routes.iter().enumerate() {
let indent_sign = match index {
i if i == routes.len() - 1 => "└─",
......@@ -69,11 +70,10 @@ impl<'a> Config {
};
mount_message += &format!(
" \x1b[35m{indent_sign}\x1b[0m \x1b[36m(\x1b[0m{}\x1b[36m)\x1b[0m \x1b[32m{}\x1b[0m \x1b[34;4m{}\x1b[24m{}\x1b[0m\n",
" \x1b[35m{indent_sign}\x1b[0m \x1b[36m(\x1b[0m{}\x1b[36m)\x1b[0m \x1b[32m{}\x1b[0m {}\n",
route.name.unwrap_or(""),
route.method,
mountpoint,
route.uri
route.uri.to_pretty_print_string(&mountpoint)
)
}
......
pub mod mime;
pub mod url_utils;
pub mod urlencoded;
pub mod vec_utils;
......@@ -5,16 +5,17 @@ use crate::utils::urlencoded::UrlEncodeData;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct Uri {
pub(super) parts: Vec<UrlEncodeData>,
pub raw: String,
}
#[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),
......
use std::str::FromStr;
use crate::utils::{url_utils::datatypes::RawUriElement, urlencoded::UrlEncodeData};
use crate::{
setup::MountPoint,
utils::{
url_utils::datatypes::RawUriElement,
urlencoded::{EnCodable, UrlEncodeData},
vec_utils::remove_n,
},
};
use super::datatypes::{ParseUriError, RawUri, Uri, UriError};
use super::datatypes::{ParseUriError, RawUri, Uri};
impl MountPoint {
pub fn compare_with_uri(&self, uri: &mut Uri) -> bool {
if !uri.raw.starts_with(&self.mountpoint.raw) {
return false;
}
let to_remove_after_finish = self.mountpoint.parts.len();
remove_n(uri.mut_parts(), to_remove_after_finish);
true
}
}
impl Uri {
pub fn new(parts: Vec<&str>) -> Self {
Self {
raw: "/".to_owned()
+ &parts
.iter()
.map(|part| part.encode())
.collect::<Vec<String>>()
.join("/"),
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
}
pub(crate) fn to_pretty_print_string(&self) -> String {
if self.parts.is_empty() {
"/".to_string()
} else {
let url = self
.parts
.iter()
.map(|part| part.encoded())
.collect::<Vec<_>>();
"/".to_string() + &url.join("/")
}
}
}
impl RawUri {
......@@ -17,7 +71,7 @@ impl RawUri {
let mut result = Self {
infinte_end: false,
parts: Vec::with_capacity(parts.len()),
raw_string: "/".to_owned() + &parts.join("/"),
raw_string: parts.join("/"),
};
for part in parts {
if part.starts_with('<') && part.ends_with("..>") {
......@@ -34,15 +88,15 @@ 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;
if uri.parts().len() != self.parts.len() && !self.infinte_end {
return false;
}
for element in self.parts.iter() {
counter += 1;
let Some(compare_element) = iter_comp.next() else {
return false;
};
if *element == RawUriElement::Variable {
continue;
}
......@@ -54,11 +108,19 @@ impl RawUri {
return false;
}
}
if counter > self.parts.len() && !self.infinte_end {
return false;
if uri.parts.len() > self.parts.len() && self.infinte_end {
return true;
}
true
}
pub(crate) fn to_pretty_print_string(&self, is_after: &Uri) -> String {
let is_after = is_after.to_pretty_print_string();
if is_after == "/" {
format!("\x1b[34;4m/\x1b[24m{}\x1b[0m", self)
} else {
format!("\x1b[34;4m{is_after}/\x1b[24m{self}\x1b[0m")
}
}
}
impl std::fmt::Display for RawUri {
......@@ -69,12 +131,16 @@ impl std::fmt::Display for RawUri {
impl std::fmt::Display for Uri {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let url = self
.parts
.iter()
.map(|part| part.encoded())
.collect::<Vec<_>>();
write!(f, "/{}", url.join("/"))
if self.parts.is_empty() {
write!(f, "/")
} else {
let url = self
.parts
.iter()
.map(|part| part.encoded())
.collect::<Vec<_>>();
write!(f, "{}", url.join("/"))
}
}
}
......@@ -93,19 +159,21 @@ impl FromStr for Uri {
UrlEncodeData::from_raw(sub)
});
}
Ok(Self { parts: result })
Ok(Self {
parts: result,
raw: s.to_string(),
})
}
}
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 {
infinte_end: false,
parts: Vec::new(),
raw_string: parts.join("/"),
raw_string: s.to_string(),
};
for part in parts {
if part.is_empty() {
......@@ -127,6 +195,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::{
......@@ -139,7 +222,7 @@ mod test {
#[test]
fn uri_to_string() {
assert_eq!("/a/%20", Uri::new(vec!["a", " "]).to_string());
assert_eq!("a/%20", Uri::new(vec!["a", " "]).to_string());
}
#[test]
......
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);
}
}