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

Add documentation

parent 47cf59bd
No related branches found
No related tags found
1 merge request!1Initial feature merge
Showing
with 208 additions and 51 deletions
......@@ -14,8 +14,20 @@ use crate::{handling::{
}, utils::urlencoded::UrlEncodeData};
use crate::setup::MountPoint;
/// The Maximal size of the Body of an HTTP-Message in bytes
static MAX_HTTP_MESSAGE_SIZE: u16 = 4196;
/// Function which handles a TCP Connection according to http-Standards by taking in a
/// [tokio::net::TcpStream] and a [`Vec<MountPoint<'_>>`].
///
/// Firstly validates the headers and body, Aborts with error messages if it finds faults.
///
/// Secondly matches the request with a route in the mountpoint Vector and executes its handler
/// function. If it fails, checks for another, if nothing is found writes back a
/// [Status::NotFound]. If the handler function could respond it uses the [Response] of the handler
///
/// # Panics
/// No Panics
pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint<'_>>) {
let mut buf_reader = BufReader::new(&mut stream);
let mut http_request: Vec<String> = Vec::with_capacity(10);
......@@ -168,7 +180,8 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin
}
}
fn failure_handler<'a>(status: Status) -> Response {
/// Dumb function that renders a 404 page from any status given. but still writes that status code.
fn failure_handler(status: Status) -> Response {
let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap();
Response {
cookies: None,
......@@ -178,6 +191,7 @@ fn failure_handler<'a>(status: Status) -> Response {
}
}
/// Handler for len_not_defined errors. writes back directly
async fn len_not_defined(stream: TcpStream, status: Status) -> io::Result<()> {
let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap();
Response {
......@@ -191,6 +205,7 @@ async fn len_not_defined(stream: TcpStream, status: Status) -> io::Result<()> {
Ok(())
}
/// takes in an io error and writes it back in the server console to the user if writing failed
fn error_occured_when_writing(e: io::Error) {
eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m");
}
......@@ -6,9 +6,14 @@ use crate::{
};
#[derive(Debug)]
/// Struct to handle files on the server side.
/// Validates paths ignores actions like `..`
pub struct NamedFile {
/// The length of the file in bytes, format: [usize]
pub content_len: usize,
/// The Mime Type of the file as a [Mime]
pub content_type: Mime,
/// The content of the file as a [`Vec<u8>`]
pub content: Vec<u8>,
}
......@@ -27,6 +32,17 @@ impl ResponseBody for NamedFile {
}
impl NamedFile {
/// Reads in a file as a [NamedFile]. Ignores seqences like `..`
///
/// # Panics
///
/// Panics if a [PathBuf] can't be convertet to a [str]
///
/// # Errors
///
/// Can give a [Status::NotFound] if it can't find the file
///
/// This function will return an error if .
pub fn open(path: PathBuf) -> Result<NamedFile, Status> {
let path = proove_path(path);
let data = fs::read(&path);
......@@ -44,6 +60,11 @@ impl NamedFile {
}
}
/// Validates a path so that seqences like `//` and `..` are omitted
///
/// # Panics
///
/// Panics if it can't convert a [PathBuf] to a [str]
fn proove_path(path: PathBuf) -> PathBuf {
PathBuf::from(
path.to_str()
......
use std::{fmt::Display, str::FromStr};
#[derive(PartialEq, Eq, Clone, Debug, Copy, PartialOrd, Ord)]
/// All HTTP Methods
pub enum Method {
Get,
Head,
......
......@@ -5,25 +5,6 @@ use crate::{
utils::{mime::Mime, urlencoded::UrlEncodeData},
};
pub trait FromRequest: Send {
fn get_data(&self) -> &Self;
fn set_data(&mut self, data: &Self);
fn append(&mut self, data: &Self);
}
impl FromRequest for Vec<u8> {
fn get_data(&self) -> &Self {
self
}
fn set_data(&mut self, data: &Self) {
*self = data.to_vec();
}
fn append(&mut self, data: &Self) {
self.extend_from_slice(data);
}
}
type HeaderMap = Vec<String>;
/// A struct to handle Requests
......@@ -49,9 +30,13 @@ pub struct Request {
// }
#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord)]
/// Media Types in which a Route can be requested to ansewr, optional for routes
pub enum MediaType {
/// Json Data
Json,
/// Plain Text
Plain,
/// HTML Text
Html,
}
......@@ -62,6 +47,7 @@ pub enum ParseErrors {
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
/// Errors that Occur when a Form can't be parsed
pub struct ParseFormError {
pub error: ParseErrors,
}
......
......@@ -3,12 +3,14 @@ use crate::handling::methods::Method;
use super::Request;
impl Request {
/// Checks if the request can have a body
pub fn can_have_body(&self) -> bool {
matches!(
self.method,
Method::Post | Method::Put | Method::Patch | Method::Delete
)
}
/// Checks if a body is mandatory for the Request
pub fn mandatory_body(&self) -> bool {
matches!(self.method, Method::Post | Method::Put | Method::Patch)
}
......
use std::time::Duration;
/// Structure representing a Cookie
/// # Creating a Cookie:
/// ```
/// use http::handling::response::Cookie;
/// use http::handling::response::CookieBuilder;
///
/// let cookie = CookieBuilder::build("name", "value").finish();
/// ```
pub struct Cookie {
/// Storage for the cookie string. Only used if this structure was derived
/// from a string that was subsequently parsed.
......@@ -23,7 +31,9 @@ pub struct Cookie {
}
#[derive(Debug)]
/// SameSite Paremeters
pub enum SameSite {
/// Requires Secure
None,
Lax,
Strict,
......
......@@ -2,11 +2,22 @@ use std::time::Duration;
use super::{Cookie, SameSite};
/// Builder wrapper for a Cookie
///
/// # Example
/// ```
/// use http::handling::response::Cookie;
/// use http::handling::response::CookieBuilder;
///
/// let cookie = CookieBuilder::build("name", "value").path("/").finish();
/// ```
pub struct CookieBuilder {
/// Cookie under the hood
inner: Cookie,
}
impl CookieBuilder {
/// Builds a basic CookieBuilder from a name and a value
pub fn build(name: &str, value: &str) -> Self {
CookieBuilder {
inner: Cookie {
......
......@@ -3,3 +3,4 @@ mod cookie_builder;
pub use cookie::Cookie;
pub use cookie::SameSite;
pub use cookie_builder::CookieBuilder;
......@@ -3,15 +3,37 @@ use super::{Cookie, ResponseBody, Status};
type HeaderMap = Vec<String>;
#[derive(Debug)]
/// Enum for the result of a Handling Function, where...
///
/// [Outcome::Success] represents that the route
/// was successful and the Answer is contained in \[S\].
/// [Outcome::Failure] represents that it was unsuccessful and nobody else is going to be
/// successful. \[E\] represnts the Error Code.
/// [Outcome::Forward] represents that some requirements weren't met for a route to be working with
/// the request so the next one that matches should cover that \[F\] represents the maybe processed
/// data of the request.
///
/// # Example
/// ```
/// use http::handling::{response::{Outcome, Response, Status}, routes::Data, request::Request};
/// fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> {
/// todo!()
/// }
/// ```
pub enum Outcome<S, E, F> {
Success(S),
Failure(E),
Forward(F),
}
/// Response is a wrapper for http responses.
pub struct Response {
/// the [`Vec<String>`] of headers unrelated to `Content-Type` and `Content-Length`
pub headers: HeaderMap,
/// Optional Cookie in the response
pub cookies: Option<Cookie>,
/// Status code of the response
pub status: Option<Status>,
/// Response body and `Content-Type` and `Content-Length` headers.
pub body: Box<dyn ResponseBody>,
}
......@@ -5,6 +5,7 @@ mod status;
mod traits;
pub use cookie_management::Cookie;
pub use cookie_management::CookieBuilder;
pub use cookie_management::SameSite;
pub use datatypes::Outcome;
pub use datatypes::Response;
......
......@@ -6,7 +6,9 @@ use crate::handling::{methods::Method, request::Request, response::Status};
use super::Response;
impl Response<> {
impl Response {
/// Builds a [`Vec<u8>`] valid http response from a [Response] and consumes it. Optionally
/// takes in a request for things like [Method::Head]
pub fn build(self, request: Option<Request>) -> Vec<u8> {
let compiled_headers = format!(
"HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n",
......@@ -29,6 +31,7 @@ impl Response<> {
compiled_out.extend_from_slice(&compiled_body);
compiled_out
}
/// Builds and writes The http-Response, consumes the [tokio::net::TcpStream] [Request] and [Response]
pub async fn write(self, mut stream: TcpStream, request: Option<Request>) -> Result<()> {
let resp = self.build(request);
stream.write_all(&resp).await?;
......
use std::fmt::Display;
#[derive(Debug)]
/// Enum With every http status for complete documentation [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)
pub enum Status {
Continue,
SwitchingProtocols,
WebDavProcessing,
ExperimentalEarlyHints,
/// WebDAV
Processing,
/// Experimental
EarlyHints,
Ok,
Created,
Accepted,
......@@ -13,21 +16,26 @@ pub enum Status {
NoContent,
ResetContent,
PartialContent,
WebDavMultiStatus,
WebDavAlreadyReported,
/// WebDAV
MultiStatus,
/// WebDAV
AlreadyReported,
HttpDataEncodingImUsed,
MultipleChoices,
MovedPermanently,
Found,
SeeOther,
NotModfiied,
DeprecatedUseProxy,
UnusedUnused,
/// Deprecated
UseProxy,
/// Deprecated
Unused,
TemporaryRedirect,
PermanentRedirect,
BadRequest,
Unauthorized,
ExperimentalPaymentRequired,
/// Experimental
PaymentRequired,
Forbidden,
NotFound,
MethodNotAllowed,
......@@ -45,10 +53,14 @@ pub enum Status {
ExpectationFailed,
ImATeapot,
MisdirectedRequest,
WebDavUnprocessableContent,
WebDavLocked,
WebDavFailedDependency,
ExperimenalTooEarly,
/// WebDAV
UnprocessableContent,
/// WebDAV
Locked,
/// WebDAV
FailedDependency,
/// Experimental
TooEarly,
UpgradeRequred,
PreconditionRequired,
TooManyRequests,
......@@ -61,8 +73,10 @@ pub enum Status {
GetawayTimeout,
HttpVersionNotSupported,
VariantAlsoNegotiates,
WebDavInsufficientStorage,
WebDavLoopDetected,
/// WebDAV
InsufficientStorage,
/// WebDAV
LoopDetected,
NotExtended,
NetworkAuthenticationRequired,
}
......@@ -72,8 +86,8 @@ impl Display for Status {
match self {
Status::Continue => write!(f, "100 Continue"),
Status::SwitchingProtocols => write!(f, "101 Switching Protocols"),
Status::WebDavProcessing => write!(f, "102 Processing"),
Status::ExperimentalEarlyHints => write!(f, "103 Early Hints"),
Status::Processing => write!(f, "102 Processing"),
Status::EarlyHints => write!(f, "103 Early Hints"),
Status::Ok => write!(f, "200 OK"),
Status::Created => write!(f, "201 Created"),
Status::Accepted => write!(f, "202 Accepted"),
......@@ -81,8 +95,8 @@ impl Display for Status {
Status::NoContent => write!(f, "204 No Content"),
Status::ResetContent => write!(f, "205 Reset Content"),
Status::PartialContent => write!(f, "206 Partial Content"),
Status::WebDavMultiStatus => write!(f, "207 Mutli-Status"),
Status::WebDavAlreadyReported => write!(f, "208 Already Reported"),
Status::MultiStatus => write!(f, "207 Mutli-Status"),
Status::AlreadyReported => write!(f, "208 Already Reported"),
Status::HttpDataEncodingImUsed => write!(f, "226 IM Used"),
Status::MultipleChoices => write!(f, "300 Multiple Choices"),
Status::MovedPermanently => write!(f, "301 Moved Permanently"),
......@@ -91,11 +105,11 @@ impl Display for Status {
Status::NotModfiied => write!(f, "304 Not Modified"),
Status::TemporaryRedirect => write!(f, "307 Temporary Redirect"),
Status::PermanentRedirect => write!(f, "308 Permanent Redirect"),
Status::DeprecatedUseProxy => write!(f, "305 Use Proxy"),
Status::UnusedUnused => write!(f, "306 unused"),
Status::UseProxy => write!(f, "305 Use Proxy"),
Status::Unused => write!(f, "306 unused"),
Status::BadRequest => write!(f, "400 Bad Request"),
Status::Unauthorized => write!(f, "401 Unauthorized"),
Status::ExperimentalPaymentRequired => write!(f, "402 Payment Required"),
Status::PaymentRequired => write!(f, "402 Payment Required"),
Status::Forbidden => write!(f, "403 Forbidden"),
Status::NotFound => write!(f, "404 Not Found"),
Status::MethodNotAllowed => write!(f, "405 Method Not Allowed"),
......@@ -115,10 +129,10 @@ impl Display for Status {
Status::ExpectationFailed => write!(f, "417 Expectation Failed"),
Status::ImATeapot => write!(f, "418 I'm a Teapot"),
Status::MisdirectedRequest => write!(f, "421 Misdirected Request"),
Status::WebDavUnprocessableContent => write!(f, "422 Unprocessable Content"),
Status::WebDavLocked => write!(f, "423 Locked"),
Status::WebDavFailedDependency => write!(f, "424 Failed Dependency"),
Status::ExperimenalTooEarly => write!(f, "425 Too Early"),
Status::UnprocessableContent => write!(f, "422 Unprocessable Content"),
Status::Locked => write!(f, "423 Locked"),
Status::FailedDependency => write!(f, "424 Failed Dependency"),
Status::TooEarly => write!(f, "425 Too Early"),
Status::UpgradeRequred => write!(f, "426 Upgrade Required"),
Status::PreconditionRequired => write!(f, "428 Precondition Required"),
Status::TooManyRequests => write!(f, "429 Too Many Requests"),
......@@ -135,8 +149,8 @@ impl Display for Status {
Status::GetawayTimeout => write!(f, "504 Getaway Timeout"),
Status::HttpVersionNotSupported => write!(f, "505 HTTP Version Not Supported"),
Status::VariantAlsoNegotiates => write!(f, "506 Variant Also Negotiates"),
Status::WebDavInsufficientStorage => write!(f, "507 Insufficient Storage"),
Status::WebDavLoopDetected => write!(f, "508 Loop Detected"),
Status::InsufficientStorage => write!(f, "507 Insufficient Storage"),
Status::LoopDetected => write!(f, "508 Loop Detected"),
Status::NotExtended => write!(f, "510 Not Extendend"),
Status::NetworkAuthenticationRequired => {
write!(f, "511 Network Authentication Required")
......
use crate::{handling::routes::Body, utils::mime::Mime};
/// Trait for using datatypes as response bodies
pub trait ResponseBody: Send {
/// Get a cloned version of the data as a [`Vec<u8>`]
/// # Ecamples
......
......@@ -7,12 +7,34 @@ use crate::{
utils::mime::Mime,
};
pub struct RoutInfo {
/// A RouteBuilder struct
pub struct RoutBuilder {
/// An optional name of the route
name: Option<&'static str>,
/// The [Method] via which the route is accesable
method: Method,
/// The path of the route, allows special cases:
/// # Examples
/// ```
/// "/home"; // Only /home
/// "/<home>/something";
/// // Variable content the users provides this acts for /<anything>/something
/// "/<home..>";
/// // All Information after this sequence is irrelvent
/// // Matches: /a, /a/b/c ...
/// ```
path: &'static str,
/// 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
/// something went wrong and a [Status] page is need or a [Outcome::Forward] of the requests
/// [Data] for the next [Route] to take care of.
handler: fn(Request, Data) -> Outcome<Response, Status, Data>,
/// The Specific answer format of the [Route] as a [MediaType]. Optional
format: Option<MediaType>,
/// The Optional Rank of the Route, dependent on its specificness. so the rank of a uri `"/home"` would be
/// ranked high, whereas a uri of `"/<anything..>"` would be ranked the lowest
/// If not given generated based on parematers.
rank: Option<isize>,
}
......@@ -48,7 +70,9 @@ pub struct Route<'a> {
}
impl Route<'_> {
pub fn from(routeinfo: RoutInfo) -> Self {
/// generates a Route from a Routebuilder
//TODO: ranking
pub fn from(routeinfo: RoutBuilder) -> Self {
let rank = routeinfo.rank.unwrap_or(0);
Route {
name: routeinfo.name,
......@@ -84,6 +108,7 @@ impl Route<'_> {
}
}
/// Alias for using a &'a str for Uri
pub type Uri<'a> = &'a str;
#[derive(Debug, Clone)]
......@@ -96,18 +121,23 @@ pub struct Body {
}
impl Body {
/// New body of a Response
pub fn new(body: Vec<u8>, mime_type: Mime) -> Self {
Self { body, mime_type }
}
/// Sets the `mime_type` of the Body
pub fn set_mime_type(&mut self, mime_type: Mime) {
self.mime_type = mime_type;
}
/// Reassigns the body
pub fn set_body(&mut self, body: Vec<u8>) {
self.body = body;
}
/// mime_type of the body
pub fn mime_type(&self) -> Mime {
self.mime_type
}
/// cloned body as [`Vec<u8>`]
pub fn body(&self) -> Vec<u8> {
self.body.clone()
}
......@@ -123,6 +153,7 @@ pub struct Data {
}
impl Data {
/// Checks if the buffer.oen() is -0
pub fn is_empty(&self) -> bool {
self.buffer.len() == 0
}
......
......@@ -12,17 +12,27 @@ use crate::{
};
#[derive(Clone)]
/// Represnts a [MountPoint] that can be mounted in the config
pub struct MountPoint<'a> {
/// The prefix of the [MountPoint]
pub mountpoint: Uri<'a>,
/// All Routes mounted on the [MountPoint]. The Routes are all prefixed by the mountpoints
/// mountpoint
pub routes: Vec<Route<'a>>,
}
/// 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>>>,
/// Contains a [tokio::net::TcpListener] that is bound for the server
address: TcpListener,
}
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 {
if let Some(to_check) = &self.mountpoints {
for i in to_check.iter() {
......@@ -33,6 +43,8 @@ impl<'a> Config {
};
false
}
/// 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");
......
use super::mime_enum::Mime;
/// Map with the string version of the Mime types and the values being corresponding [Mime]s
pub static MIME_MAP: phf::Map<&'static str, Mime> = phf::phf_map! {
"application/1d-interleaved-parityfec" => Mime::Application1dInterleavedParityfec,
"application/3gpdash-qoe-report+xml" => Mime::Application3gpdashQoeReportXml,
......
use super::map::MIME_MAP;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
/// An Enum of all Mime types
pub enum Mime {
Application1dInterleavedParityfec,
Application3gpdashQoeReportXml,
......@@ -4074,6 +4075,7 @@ impl std::str::FromStr for Mime {
}
impl Mime {
/// Gets the mime type from filename, defaults to [Mime::TextPlain]
pub fn from_filename(filename: &str) -> Self {
match filename.split('.').last() {
Some(v) => match v {
......
......@@ -3,13 +3,18 @@ use crate::utils::urlencoded::endecode::EnCodable;
use super::endecode::DeCodable;
#[derive(Clone)]
/// A way to store UrlEncoded data
pub struct UrlEncodeData {
/// Encoded string
encoded: String,
/// raw data, unencoded
raw: Vec<u8>,
/// raw string if it exists
raw_string: Option<String>,
}
impl UrlEncodeData {
/// Generates a [UrlEncodeData] from any raw data that can be a slice of [u8]
pub fn from_raw<T: AsRef<[u8]>>(raw: T) -> Self {
Self {
raw: raw.as_ref().to_owned(),
......@@ -17,6 +22,11 @@ impl UrlEncodeData {
raw_string: String::from_utf8(raw.as_ref().into()).ok(),
}
}
/// Generates a [UrlEncodeData] from a correctly encoded string
///
/// # Errors
///
/// errors if the encoded data is wrongly encoded -> %<invalid_character>
pub fn from_encoded(encoded: &str) -> Result<Self, ()> {
Ok(Self {
encoded: encoded.to_owned(),
......@@ -25,12 +35,15 @@ impl UrlEncodeData {
})
}
/// Gets a reference to the encoded data
pub fn encoded(&self) -> &str {
self.encoded.as_ref()
}
/// Get a reference to the raw [u8] slice
pub fn raw(&self) -> &[u8] {
self.raw.as_ref()
}
/// Gets an Optional string slice to the raw data
pub fn raw_string(&self) -> Option<&str> {
self.raw_string.as_ref().map(|x| x.as_str())
}
......
static BASE16_HEXA_DECIMAL: u8 = 16;
static BASE16_HEXA_DECIMAL_POSSIBLE_VALUE_PER_DIGIT: u8 = 15;
/// Base of the HexaDecimal Number system
static BASE16_HEXA_DECIMAL: u8 = 0x10;
/// Highest possible Value per digit
static BASE16_HEXA_DECIMAL_POSSIBLE_VALUE_PER_DIGIT: u8 = 0xF;
/// Bits of a hexa decimal number
static BASE16_HEXA_DECIMAL_DIGIT_BITS: u8 = 4;
pub trait EnCodable {
/// Encodes the give data into a Percent Encoded [String]
fn encode(&self) -> String;
}
pub trait DeCodable {
/// Decodes the given data into a [`Vec<u8>`]
///
/// # Errors
/// Errors if the encoding isn't right
fn decode(&self) -> Result<Vec<u8>, ()>;
}
......@@ -50,6 +58,8 @@ impl DeCodable for &str {
}
}
/// converts a [u8] digit into the ascii code of its counterpart. The digit shouldn't be bigger
/// than [BASE16_HEXA_DECIMAL_POSSIBLE_VALUE_PER_DIGIT]
fn hex_to_digit(digit: u8) -> u8 {
match digit {
0..=9 => b'0' + digit,
......
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