#![allow(
unused_imports,
unused_mut,
dead_code,
unused_variables
)]
pub mod utils;
pub mod request_info;
pub mod response;
pub mod request;
pub use request_info::{ RequestInfo, Method };
use terminal_link::Link;
use response::{
respond,
Respond,
ResponseType,
not_found,
with_file
};
use ansi_term;
use std::{
net::{
TcpStream,
TcpListener
},
io::{
Read, Error,
},
thread::spawn,
path::Path,
collections::HashMap,
fs,
};
const DATA_BUF_INIT:usize = 1024usize;
#[derive(Clone, Copy)]
pub struct ServerConfig {
pub addr: &'static str,
pub port: u16,
pub serve: Option<&'static str>,
pub not_found: Option<&'static str>,
pub routes: Route<'static>
}
#[derive(Clone, Copy)]
pub enum Function {
S(fn( &mut TcpStream )),
SB(fn( &mut TcpStream, &String )),
SH(fn( &mut TcpStream, &HashMap<
&str,
&str
> )),
SHB(fn(
&mut TcpStream,
&HashMap<
&str,
&str
>,
&String
)),
}
#[derive(Clone, Copy)]
pub enum Route<'lf> {
Stack(
&'lf str,
&'lf [Route<'lf>]
),
Tail(
Method,
&'lf str,
Function
)
}
pub fn start(__sconfig:ServerConfig) -> Result<(), Error> {
let bind_to = &format!(
"{}:{}",
__sconfig.addr, __sconfig.port
);
let stream = match TcpListener::bind(bind_to) {
Ok(listener) => listener,
Err(e) => return Err(e)
};
println!("{}",
&format!(
"{} {}",
ansi_term::Color::RGB(123, 149, 250).paint(
"Server opened on"
),
ansi_term::Color::RGB(255, 255, 0).underline().paint(
format!("{}", Link::new(
&format!("http://{}", &bind_to),
bind_to,
))
)
)
);
for request in stream.incoming() {
spawn(move || {
handle_req(match request {
Ok(req) => req,
Err(_) => return,
}, &__sconfig);
});
};
Ok(())
}
fn handle_req(mut stream:TcpStream, config:&ServerConfig) -> () {
let buffer:&mut [u8] = &mut [0u8; DATA_BUF_INIT];
match stream.read(buffer) {
Ok(data) => data,
Err(_) => return
};
let request:String = String::from_utf8_lossy(&buffer[..]).to_string();
let headers:HashMap<&str, &str> = utils::headers::parse_headers(&request);
let info:RequestInfo = match RequestInfo::parse_req(&request) {
Ok(e) => e,
Err(_) => return
};
let mut body:String = String::new();
if info.method == Method::POST {
body = request.split("\r\n\r\n").last().unwrap().to_string();
}
match call_endpoint(&config.routes, info, String::new(), (&headers, &mut stream, &body)) {
Ok(_) => (),
Err(_) => {
if let Some(static_path) = config.serve {
match serve_static_dir(&static_path, info.path, &stream) {
Ok(_) => (),
Err(_) => {
return not_found(&mut stream, *config);
}
};
}else {
return not_found(&mut stream, *config);
};
},
};
}
fn call_endpoint(
route:&Route,
info:RequestInfo,
mut final_path:String,
(
headers,
stream,
body
):(
&HashMap<&str, &str>,
&mut TcpStream,
&String
)
) -> Result<(), ()> {
match route {
Route::Stack(pathname, routes) => {
let mut tail_found:bool = false;
'tail_search: for route in routes.iter() {
let mut possible_final_path = final_path.clone();
possible_final_path.push_str(pathname);
possible_final_path.push_str("/");
match call_endpoint(route, info, possible_final_path.clone(), (headers, stream, body)) {
Ok(_) => {
tail_found = true;
final_path.push_str(pathname);
final_path.push_str("/");
break 'tail_search;
},
Err(_) => continue
};
};
if tail_found { return Ok(()); }
else { return Err(()); }
},
Route::Tail(method, pathname, function) => {
if [final_path, pathname.to_string()].concat() == info.path {
if method == &info.method {
Function::call_fn(*function, stream, headers, body);
return Ok(());
}else {
return Err(());
}
}else {
return Err(());
}
}
};
}
fn serve_static_dir(dir:&str, request_path:&str, mut stream:&TcpStream) -> Result<(), ()> {
let path = &[dir, request_path].concat();
let file_path:&Path = Path::new(path);
match file_path.is_file() {
true => (),
false => return Err(())
};
match fs::File::open(file_path) {
Ok(mut e) => {
let mut content:String = String::new();
match e.read_to_string(&mut content) {
Ok(_) => (),
Err(_) => (),
};
respond(
&mut stream,
200u16,
Some(Respond {
response_type: ResponseType::guess(file_path),
content
})
)
},
Err(_) => return Err(())
}
Ok(())
}
impl Function {
pub fn call_fn(self, stream:&mut TcpStream, headers:&HashMap<&str, &str>, body:&String) -> () {
match self {
Self::S(e) => e(stream),
Self::SB(e) => e(stream, body),
Self::SH(e) => e(stream, headers),
Self::SHB(e) => e(stream, headers, body),
}
}
}