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::{ use std::{
fs,
io::{BufRead, BufReader, Write}, io::{BufRead, BufReader, Write},
net::TcpStream, net::TcpStream,
path::PathBuf, path::PathBuf,
}; };
use crate::routing::methods::Method;
use crate::routing::routes::{Data, Outcome, Request, Response, Status}; use crate::routing::routes::{Data, Outcome, Request, Response, Status};
use crate::setup::MountPoint;
use super::file_handlers::NamedFile; 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 buf_reader = BufReader::new(&mut stream);
let http_request: Vec<String> = buf_reader let http_request: Vec<String> = buf_reader
.lines() .lines()
...@@ -25,7 +24,12 @@ pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) { ...@@ -25,7 +24,12 @@ pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) {
let request = Request { let request = Request {
uri: &request_status_line.split(" ").nth(1).unwrap(), uri: &request_status_line.split(" ").nth(1).unwrap(),
headers: http_request.clone(), headers: http_request.clone(),
method: Method::Get, method: request_status_line
.split(" ")
.next()
.unwrap()
.parse()
.unwrap(),
}; };
let data = Data { let data = Data {
...@@ -33,65 +37,57 @@ pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) { ...@@ -33,65 +37,57 @@ pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) {
is_complete: true, is_complete: true,
}; };
let handled_response = match handler(request, data) { let mut handled_response: Option<Outcome<Response, Status, Data>> = None;
Outcome::Success(success) => ( for mountpoint in mountpoints {
format!("HTTP/1.1 {} OK\r\n", success.status.unwrap().code) if !request.uri.starts_with(mountpoint.mountpoint) {
+ &success.headers.join("\r\n") println!("MOUNTPOINT COMPARISON {}", request.uri);
+ "\r\n\r\n", continue;
success.body.get_data(), }
), let mounted_request_uri = request.uri.strip_prefix(mountpoint.mountpoint).unwrap();
Outcome::Failure(error) => { for route in mountpoint.routes {
let content = fs::read("./404.html").unwrap(); if route.method != request.method {
( println!("METHOD COMPARE {}", request.method);
format!( continue;
"HTTP/1.1 {} NOT FOUND\r\nContent-Length: {}\r\nContent-Type: text/html\r\n\r\n", }
error.code, if !route.compare_uri(mounted_request_uri) {
content.len() println!("URI COMPARISON {} {}", mounted_request_uri, route.uri);
), continue;
content, }
) 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 = match handled_response {
let response = fileserver(request.uri); Some(val) => match val {
let response = match response { Outcome::Success(success) => (
Ok(dat) => Response { format!("HTTP/1.1 {} OK\r\n", success.status.unwrap().code)
headers: vec![ + &success.headers.join("\r\n")
format!("Content-Length: {}", dat.content_len), + "\r\n\r\n",
format!("Content-Type: {}", dat.content_type), success.body.get_data(),
], ),
status: Some(Status { code: 200 }), Outcome::Failure(error) => handler_404(error),
body: Box::new(dat), 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> { fn handler_404(status: Status) -> (String, Vec<u8>) {
NamedFile::open(PathBuf::from("static/".to_string() + path)) 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; pub mod handlers;
mod routing; pub mod routing;
mod setup; mod setup;
mod threading; mod threading;
......
use std::{fmt::Display, str::FromStr}; use std::{fmt::Display, str::FromStr};
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq, Clone, Debug, Copy)]
pub enum Method { pub enum Method {
Get, Get,
Head, Head,
......
...@@ -15,13 +15,14 @@ pub struct RoutInfo { ...@@ -15,13 +15,14 @@ pub struct RoutInfo {
rank: Option<isize>, rank: Option<isize>,
} }
#[derive(Clone, Copy)]
pub struct Route<'a> { pub struct Route<'a> {
name: Option<&'static str>, pub name: Option<&'static str>,
method: Method, pub method: Method,
uri: Uri<'a>, pub uri: Uri<'a>,
handler: fn(Request, Data) -> Outcome<Response, Status, Data>, pub handler: fn(Request, Data) -> Outcome<Response, Status, Data>,
rank: isize, pub rank: isize,
format: Option<MediaType>, pub format: Option<MediaType>,
} }
impl Route<'_> { impl Route<'_> {
...@@ -36,8 +37,29 @@ impl Route<'_> { ...@@ -36,8 +37,29 @@ impl Route<'_> {
format: routeinfo.format, 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 struct Request<'a> {
pub uri: Uri<'a>, pub uri: Uri<'a>,
pub headers: HeaderMap, pub headers: HeaderMap,
...@@ -51,15 +73,17 @@ struct ConnectionMeta { ...@@ -51,15 +73,17 @@ struct ConnectionMeta {
} }
type HeaderMap = Vec<String>; type HeaderMap = Vec<String>;
type Uri<'a> = &'a str; pub type Uri<'a> = &'a str;
#[derive(Debug)]
pub enum Outcome<S, E, F> { pub enum Outcome<S, E, F> {
Success(S), Success(S),
Failure(E), Failure(E),
Forward(F), Forward(F),
} }
enum MediaType { #[derive(Clone, Debug, Copy)]
pub enum MediaType {
Json, Json,
Plain, Plain,
Html, Html,
...@@ -70,6 +94,7 @@ pub struct Status { ...@@ -70,6 +94,7 @@ pub struct Status {
pub code: u16, pub code: u16,
} }
#[derive(Debug)]
pub struct Body { pub struct Body {
pub size: Option<usize>, pub size: Option<usize>,
pub body: Vec<u8>, pub body: Vec<u8>,
...@@ -87,6 +112,7 @@ pub struct Response { ...@@ -87,6 +112,7 @@ pub struct Response {
pub body: Box<dyn ResponseBody>, pub body: Box<dyn ResponseBody>,
} }
#[derive(Debug, Clone)]
pub struct Data { pub struct Data {
pub buffer: Vec<u8>, pub buffer: Vec<u8>,
pub is_complete: bool, pub is_complete: bool,
......
...@@ -7,26 +7,36 @@ use std::{ ...@@ -7,26 +7,36 @@ use std::{
thread::available_parallelism, 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> { pub struct Config {
mountpoints: Option<Vec<&'static str>>, mountpoints: Option<Vec<MountPoint<'static>>>,
address: TcpListener, address: TcpListener,
threadpool: ThreadPool, threadpool: ThreadPool,
routes: Option<Vec<Route<'a>>>,
} }
impl<'a> Config<'a> { impl<'a> Config {
pub fn mount(self, mountpoint: &'static str, routes: Vec<Route<'static>>) -> Config<'a> { pub fn mount(mut self, mountpoint: Uri<'static>, routes: Vec<Route<'static>>) -> Config {
if self.mountpoints == None { let mut temp_mountpoints = None;
return Config { std::mem::swap(&mut self.mountpoints, &mut temp_mountpoints);
mountpoints: Some(vec![mountpoint]),
routes: Some(routes), if let Some(mut mountpoints) = temp_mountpoints {
..self mountpoints.push(MountPoint { mountpoint, routes });
}; self.mountpoints = Some(mountpoints);
} else { } else {
return self; self.mountpoints = Some(vec![MountPoint { mountpoint, routes }]);
} }
self
} }
pub fn launch(self) { pub fn launch(self) {
let running = Arc::new(AtomicBool::new(true)); let running = Arc::new(AtomicBool::new(true));
...@@ -42,15 +52,14 @@ impl<'a> Config<'a> { ...@@ -42,15 +52,14 @@ impl<'a> Config<'a> {
running_for_handler.store(false, atomic::Ordering::SeqCst); running_for_handler.store(false, atomic::Ordering::SeqCst);
}) })
.expect("Error setting Ctrl-C handler"); .expect("Error setting Ctrl-C handler");
for stream in self.address.incoming() { for stream in self.address.incoming() {
if !running.load(atomic::Ordering::SeqCst) { if !running.load(atomic::Ordering::SeqCst) {
break; break;
} }
let stream = stream.unwrap(); let stream = stream.unwrap();
let mountpoint = self.mountpoints.clone().unwrap(); let mountpoints = self.mountpoints.clone().unwrap();
self.threadpool 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}. ...@@ -75,7 +84,6 @@ Server has launched from {ip}:{port}.
); );
Config { Config {
mountpoints: None, mountpoints: None,
routes: None,
address: listener, address: listener,
threadpool, threadpool,
} }
......
...@@ -3,10 +3,14 @@ ...@@ -3,10 +3,14 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>404</title> <title>404</title>
<link rel="stylesheet" href="/hello.css" />
</head> </head>
<body> <body>
<h1>404</h1> <h1>404</h1>
<p>Hi from Rust</p> <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> </body>
</html> </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() { 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