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

Add addable routes

parent d611125a
No related branches found
No related tags found
1 merge request!1Initial feature merge
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,
)
}
mod handlers;
mod routing;
pub mod handlers;
pub mod routing;
mod setup;
mod threading;
......
use std::{fmt::Display, str::FromStr};
#[derive(PartialEq, Eq)]
#[derive(PartialEq, Eq, Clone, Debug, Copy)]
pub enum Method {
Get,
Head,
......
......@@ -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,
......
......@@ -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,
}
......
......@@ -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>
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();
}
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