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

Add routing with outcome functionality, improving fileserver

improve division of code into different modules
parent 9b9f4971
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 mime::Mime;
pub fn handle_connection(mut stream: TcpStream) {
let buf_reader = BufReader::new(&mut stream);
let http_request: Vec<_> = buf_reader
.lines()
.map(|result| result.unwrap())
.take_while(|line| !line.is_empty())
.collect();
let path = parse_request(&http_request[0]);
let status_line;
let mime_type;
let contents = if let Ok(file) = fs::read(&path) {
status_line = "HTTP/1.1 200 OK";
mime_type = find_mimetype(&path.to_str().unwrap().to_string());
file
} else {
status_line = "HTTP/1.1 404 NOT FOUND";
mime_type = find_mimetype(&"html".to_string());
fs::read("404.html").unwrap()
};
println!("{mime_type}");
let response = format!(
"{}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n",
status_line,
contents.len(),
mime_type
);
stream.write_all(response.as_bytes()).unwrap();
stream.write(&contents).unwrap();
}
fn parse_request(request: &str) -> PathBuf {
let path_elements = request
.split(" ")
.nth(1)
.unwrap()
.split("/")
.filter(|&val| val != ".." && val != "")
.collect::<Vec<&str>>();
PathBuf::from(format!("./{}", path_elements.join("/")))
}
fn find_mimetype(filename: &String) -> Mime {
let parts: Vec<&str> = filename.split('.').collect();
let res = match parts.last() {
Some(v) => match *v {
"png" => mime::IMAGE_PNG,
"jpg" => mime::IMAGE_JPEG,
"json" => mime::APPLICATION_JSON,
"html" => mime::TEXT_HTML,
"css" => mime::TEXT_CSS,
&_ => mime::TEXT_PLAIN,
},
None => mime::TEXT_PLAIN,
};
return res;
}
use std::{fs, path::PathBuf};
use mime::Mime;
use crate::routing::routes::{ResponseBody, Status};
#[derive(Debug)]
pub struct NamedFile {
pub content_len: usize,
pub content_type: Mime,
pub content: Vec<u8>,
}
impl ResponseBody for NamedFile {
fn get_data(&self) -> Vec<u8> {
self.content.clone()
}
}
impl NamedFile {
pub fn open(path: PathBuf) -> Result<NamedFile, Status> {
let path = proove_path(path);
let data = fs::read(&path);
let data = match data {
Ok(dat) => {
println!("{:?}", dat);
dat
}
Err(_) => {
return Err(Status { code: 404 });
}
};
println!("{:?}", data);
Ok(NamedFile {
content_len: data.len(),
content_type: find_mimetype(path.to_str().unwrap()),
content: data,
})
}
}
fn proove_path(path: PathBuf) -> PathBuf {
PathBuf::from(
path.to_str()
.unwrap()
.split("/")
.filter(|&val| val != ".." && val != "")
.collect::<Vec<&str>>()
.join("/"),
)
}
pub fn find_mimetype(filename: &str) -> Mime {
match filename.split('.').last() {
Some(v) => match v {
"png" => mime::IMAGE_PNG,
"jpg" => mime::IMAGE_JPEG,
"json" => mime::APPLICATION_JSON,
"html" => mime::TEXT_HTML,
"css" => mime::TEXT_CSS,
&_ => mime::TEXT_PLAIN,
},
None => mime::TEXT_PLAIN,
}
}
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 super::file_handlers::NamedFile;
pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) {
let buf_reader = BufReader::new(&mut stream);
let http_request: Vec<String> = buf_reader
.lines()
.map(|result| result.unwrap())
.take_while(|line| !line.is_empty())
.collect();
let request_status_line = http_request.get(0);
if request_status_line == None {
return;
}
let request_status_line = request_status_line.unwrap();
let request = Request {
uri: &request_status_line.split(" ").nth(1).unwrap(),
headers: http_request.clone(),
method: Method::Get,
};
let data = Data {
buffer: vec![],
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,
)
}
Outcome::Forward(_) => (
format!("HTTP/1.1 {} NOT FOUND", 404),
fs::read("./404.html").unwrap(),
),
};
println!("{:?}", handled_response);
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);
println!("{:?}", response);
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 }),
};
Outcome::Success(response)
}
fn fileserver(path: &str) -> Result<NamedFile, Status> {
println!("fileserver started");
NamedFile::open(PathBuf::from("static/".to_string() + path))
}
pub mod file_handlers;
pub mod handlers;
use std::{fmt::Display, str::FromStr};
#[derive(PartialEq, Eq)]
pub enum Method {
Get,
Head,
Post,
Put,
Delete,
Connect,
Options,
Trace,
Patch,
}
impl Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Get => write!(f, "GET"),
Self::Head => write!(f, "HEAD"),
Self::Post => write!(f, "POST"),
Self::Put => write!(f, "PUT"),
Self::Delete => write!(f, "DELETE"),
Self::Connect => write!(f, "CONNECT"),
Self::Options => write!(f, "OPTIONS"),
Self::Trace => write!(f, "TRACE"),
Self::Patch => write!(f, "PATCH"),
}
}
}
impl FromStr for Method {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"GET" => Ok(Self::Get),
"HEAD" => Ok(Self::Head),
"POST" => Ok(Self::Post),
"PUT" => Ok(Self::Put),
"DELETE" => Ok(Self::Delete),
"CONNECT" => Ok(Self::Connect),
"OPTIONS" => Ok(Self::Options),
"TRACE" => Ok(Self::Trace),
"PATCH" => Ok(Self::Patch),
_ => Err("Not a Method"),
}
}
}
pub mod methods;
pub mod routes;
use std::net::SocketAddr;
use super::methods::Method;
pub trait ResponseBody {
fn get_data(&self) -> Vec<u8>;
}
pub struct RoutInfo {
name: Option<&'static str>,
method: Method,
path: &'static str,
// handler: fn(Request) -> Outcome,
format: Option<Format>,
handler: fn(Request, Data) -> Outcome<Response, Status, Data>,
format: Option<MediaType>,
rank: Option<isize>,
}
pub struct Route {
pub struct Route<'a> {
name: Option<&'static str>,
method: Method,
// handler: fn(Request) -> Outcome,
uri: &'static str,
uri: Uri<'a>,
handler: fn(Request, Data) -> Outcome<Response, Status, Data>,
rank: isize,
format: Option<Format>,
format: Option<MediaType>,
}
impl Route {
impl Route<'_> {
pub fn from(routeinfo: RoutInfo) -> Self {
let rank = routeinfo.rank.unwrap_or(0);
Route {
name: routeinfo.name,
method: routeinfo.method,
// handler: routeinfo.handler,
uri: routeinfo.path,
handler: routeinfo.handler,
rank,
format: routeinfo.format,
}
}
}
struct Request<'a> {
uri: &'a str,
headers: Vec<&'a str>,
pub struct Request<'a> {
pub uri: Uri<'a>,
pub headers: HeaderMap,
pub method: Method,
// pub connection: ConnectionMeta,
}
struct ConnectionMeta {
remote: Option<SocketAddr>,
// certificates
}
enum Outcome<S, E, F> {
type HeaderMap = Vec<String>;
type Uri<'a> = &'a str;
pub enum Outcome<S, E, F> {
Success(S),
Failure(E),
Forward(F),
}
enum Method {
Get,
Head,
Post,
Put,
Delete,
Connect,
Options,
Trace,
Patch,
}
enum Format {
enum MediaType {
Json,
Plain,
Html,
}
#[derive(Debug)]
pub struct Status {
pub code: u16,
}
pub struct Body {
pub size: Option<usize>,
pub body: Vec<u8>,
}
impl ResponseBody for Body {
fn get_data(&self) -> Vec<u8> {
self.body.clone()
}
}
pub struct Response {
pub headers: HeaderMap,
pub status: Option<Status>,
pub body: Box<dyn ResponseBody>,
}
pub struct Data {
pub buffer: Vec<u8>,
pub is_complete: bool,
}
......@@ -7,33 +7,27 @@ use std::{
thread::available_parallelism,
};
use crate::{handlers::handle_connection, routing::Route, threading::ThreadPool};
use crate::{handlers::handlers::handle_connection, routing::routes::Route, threading::ThreadPool};
pub struct PreMountConfig {
address: TcpListener,
workers: usize,
threadpool: ThreadPool,
}
impl PreMountConfig {
pub fn mount(self, mountpoint: &str, routes: Vec<Route>) -> Config {
Config {
mountpoint,
address: self.address,
workers: self.workers,
threadpool: self.threadpool,
routes,
}
}
}
pub struct Config<'a> {
mountpoint: &'a str,
mountpoints: Option<Vec<&'static str>>,
address: TcpListener,
workers: usize,
threadpool: ThreadPool,
routes: Vec<Route>,
routes: Option<Vec<Route<'a>>>,
}
impl Config<'_> {
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
};
} else {
return self;
}
}
pub fn launch(self) {
let running = Arc::new(AtomicBool::new(true));
......@@ -54,11 +48,13 @@ impl Config<'_> {
break;
}
let stream = stream.unwrap();
self.threadpool.execute(|| handle_connection(stream))
let mountpoint = self.mountpoints.clone().unwrap();
self.threadpool
.execute(move || handle_connection(stream, &mountpoint[0].to_string()))
}
}
}
pub fn build(ip: &str) -> PreMountConfig {
pub fn build(ip: &str) -> Config {
let listener = TcpListener::bind(ip).unwrap();
let ip = ip.splitn(2, ":").collect::<Vec<&str>>();
if ip.len() != 2 {
......@@ -77,9 +73,10 @@ pub fn build(ip: &str) -> PreMountConfig {
Server has launched from {ip}:{port}.
"
);
PreMountConfig {
Config {
mountpoints: None,
routes: None,
address: listener,
workers,
threadpool,
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Hello!</title>
<link rel="stylesheet" href="/hello.css" />
</head>
<body>
<h1>Hello!</h1>
<p>Hi from Rust</p>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>JE</title>
</head>
</html>
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