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
No related merge requests found
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