#![allow(dead_code, deprecated)]
use std::net::TcpListener;
use std::net::TcpStream;
use std::io::prelude::*;
use std::collections::HashMap;
use utils::guess_response_type;
use webhandler::ThreadHandler;
use std::fs;
use termcolor::Color;
use std::path::Path;
pub use crate::utils::*;
#[derive(Clone)]
pub struct ServerOptions {
pub url:&'static str,
pub port:usize,
pub numthreads:usize,
pub routes:Vec<RouteRoot>,
pub log_status:bool,
pub on_connect:Option<fn(&String)>,
pub statics:Statics,
}
#[derive(Clone)]
pub struct Statics {
pub dir:&'static str,
pub serve:bool,
pub custom404:Option<&'static str>,
}
#[derive(Debug, Clone)]
pub enum RouteRoot {
Stack(&'static str, Vec<RouteRoot>),
Endpoint(&'static str, RouteValue),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Method { Get, Post, Put, None }
#[derive(Copy, Clone, Debug)]
pub enum RouteValue {
File(&'static str),
Function((Method, fn(TcpStream, String, HashMap<String, String>) -> ())),
None
}
pub fn start(options:ServerOptions) {
let server_url = format!("{}:{}",
options.url, options.port.to_string(),
);
let server_listener:TcpListener = TcpListener::bind(&server_url).unwrap();
if options.log_status { log(Color::Rgb(255, 255, 0), format!("Server open on {}", &server_url).as_str()) };
let thread_handler = ThreadHandler::new(options.numthreads);
for stream_in in server_listener.incoming() {
let stream = stream_in.unwrap();
let opt = options.clone();
thread_handler.exec(|| {
handle_connect(stream, opt);
});
}
}
pub fn handle_connect(mut stream:TcpStream, options:ServerOptions) {
let mut data_buffer:[u8;2048] = [0u8;2048];
stream.read(&mut data_buffer).unwrap_or(0);
let request = String::from_utf8_lossy(&data_buffer[..]);
if let Some(on_connect) = options.on_connect { on_connect(&request.to_string()) };
exec_path(request.to_string(), &mut stream, options);
}
pub fn trail(path:&str) -> String {
if path.ends_with("/") {
return path[0..path.len()-1].to_string();
}
return path.to_string();
}
pub fn exec_path(request:String, stream:&mut TcpStream, options:ServerOptions) -> () {
let path = request.split("\n").nth(0).unwrap();
let path = path.split(" ").nth(1).unwrap();
if options.statics.serve {
if send_file(stream, path, options.statics.dir) == true { return; }
};
let custom_404 = options.statics.custom404.unwrap_or("404.html");
let requested_method:&str = &request.split(" ").nth(0).unwrap_or(&"").to_ascii_lowercase();
let requested_method:Method = match requested_method {
"get" => Method::Get,
"post" => Method::Post,
"put" => Method::Put,
_ => {
send_file(stream, custom_404, &options.statics.dir);
Method::None
}
};
let value = iterate_routes(&options.routes, path, 0u8, "", &options, requested_method);
match value.value {
RouteValue::File(file_path) => send_file(stream, file_path, &options.statics.dir),
RouteValue::Function((_, func)) => return func(stream.try_clone().unwrap(), request, value.params),
RouteValue::None => send_file(stream, custom_404, &options.statics.dir),
};
}
fn remove_empty(vec:Vec<&str>) -> Vec<&str> {
let mut new_vec = Vec::new();
for item in vec {
if item.len() > 0 {
new_vec.push(item);
}
}
return new_vec;
}
#[derive(Debug)]
pub struct RoutesReturn {
value:RouteValue,
params:HashMap<String, String>,
}
pub fn iterate_routes(
routes:&Vec<RouteRoot>,
input_path:&str,
index:u8,
path_iter:&str,
options:&ServerOptions,
requested_method:Method
) -> RoutesReturn {
let mut return_value:RoutesReturn = RoutesReturn { value:RouteValue::None, params:HashMap::new() };
'main: for route in routes.iter() {
match route {
RouteRoot::Stack(path,routes) => {
let possible_route = iterate_routes(
&routes.clone(),
input_path,
index+1,
(path_iter.to_string().clone() + *path).as_str(),
&options,
requested_method
);
match possible_route.value {
RouteValue::File(file_path) => return_value = RoutesReturn { value:RouteValue::File(file_path), params:possible_route.params },
RouteValue::Function((method, func)) => return_value = RoutesReturn { value:RouteValue::Function((method, func)), params:possible_route.params },
RouteValue::None => (),
};
},
RouteRoot::Endpoint(enpoint_name, path) => {
let full_path:String;
if !path_iter.ends_with("/") {
full_path = path_iter.to_string() + "/" + enpoint_name;
} else {
full_path = path_iter.to_string() + enpoint_name;
};
let mut map:HashMap<String, String> = HashMap::new();
let mut full_iter = remove_empty(full_path.split("/").collect::<Vec<&str>>());
let mut input_iter = remove_empty(input_path.split("/").collect::<Vec<&str>>());
for (index, param) in full_iter.clone().iter().enumerate() {
if param.starts_with(":") {
let remove_index:usize = full_iter.iter().position(|&x| &x == param).unwrap();
map.insert(param[1..].to_string(), input_iter
.iter()
.nth(remove_index)
.unwrap_or(&"null")
.to_string());
if full_iter.len() > index-1 && input_iter.len() > remove_index {
full_iter .remove(remove_index);
input_iter.remove(remove_index);
};
};
};
match path {
RouteValue::File(file_path) => {
if full_iter == input_iter {
return_value = RoutesReturn { value: RouteValue::File(file_path), params: map };
break 'main;
}
},
RouteValue::Function((required_method, func)) => {
if full_iter == input_iter {
if requested_method == *required_method {
return_value = RoutesReturn { value: RouteValue::Function((*required_method, *func)), params: map };
break 'main;
}
}
},
RouteValue::None => {
if let Some(c) = options.statics.custom404 {
return_value = RoutesReturn { value: RouteValue::File(c), params: map };
}else {
return_value = RoutesReturn { value: RouteValue::File("404.html"), params: map };
};
},
};
},
};
};
return return_value;
}
pub fn send_file(stream:&mut TcpStream, path:&str, static_file_path:&str) -> bool {
let full_path = format!("{}/{}", static_file_path, path);
if !Path::new(&full_path).is_file() { return false; };
let file_content:&str = &fs::read_to_string(
&full_path
)
.unwrap_or(
format!("File not found: \"{full_path}\"")
);
let response_type:ResponseType = guess_response_type(&full_path);
respond(stream, 200u16, Some((response_type, &file_content)), None);
true
}
pub(crate) mod utils;
pub(crate) mod webhandler;