#![allow(non_snake_case)]
mod App;
mod File;
pub mod Request;
pub mod Response;
mod cache;
mod macros;
pub mod utils;
use cache::Cache;
use libc::{
sigaction as sigaction_syscall, sigaction as sigaction_struct, sigaddset, SA_RESTART, SIGINT,
SIGTERM, SIGTSTP,getsockopt,SOL_SOCKET,SO_SNDBUF
};
use std::{
ffi::c_void, io, net::{IpAddr, TcpListener, TcpStream, UdpSocket}, os::fd::AsRawFd, path::PathBuf, process, ptr::NonNull, sync::Arc, time::Duration
};
use utils::{threadpool::ThreadPool, Method};
use App::{client::Client, AppEnv, Route, RouteFunction, URLRouter};
static mut SIG_FLAG: bool = false;
static mut PORT: u16 = 0;
extern "C" fn handle_shutdown_signal(_: i32) {
unsafe {
SIG_FLAG = true;
let addr = format!("127.0.0.1:{}", PORT);
let _dummy_stream = match TcpStream::connect(&addr) {
Ok(c) => c,
Err(_) => {
process::exit(1);
}
};
}
}
extern "C" fn handle_sigtstp(_: i32) {
log_info!("CTRL + Z is ignored. If want to terminate the server press CTRL + C");
}
fn get_send_buffer_len(sock_fd:i32)->usize{
let mut buf_sz=1<<14;
unsafe {
let n_ptr=Box::into_raw(Box::new(0_usize)) as * mut c_void;
let m_ptr=Box::into_raw(Box::new(std::mem::size_of::<usize>() as u32));
let res=getsockopt(sock_fd, SOL_SOCKET, SO_SNDBUF, n_ptr, m_ptr);
if res==0{
buf_sz=*(n_ptr as * const usize);
}
let _=Box::from_raw(n_ptr as * mut usize);
let _=Box::from_raw(m_ptr);
}
if buf_sz>(1<<16){
buf_sz=1<<14;
}
buf_sz as usize
}
pub struct RastAPI {
pub(crate) routes: NonNull<URLRouter>,
pub total_workers: usize,
pub payload_maximum_size_in_MB: usize,
pub read_time_out: Duration,
pub write_time_out: Duration,
pub keep_alive_time_out: Duration,
pub keep_alive_max_count: u8,
pub(crate) cache: NonNull<Cache<PathBuf>>,
pub file_upload_directory_name:String
}
impl RastAPI {
pub fn new() -> Self {
let total_workers: usize = 10;
let default_read_time_out = Duration::from_secs(5);
let deafault_write_time_out = Duration::from_secs(5);
let deafault_keep_alive_time_out = Duration::from_secs(5);
Self {
routes: unsafe { NonNull::new_unchecked(Box::into_raw(Box::new(URLRouter::new()))) },
total_workers,
payload_maximum_size_in_MB: 512 as usize,
read_time_out: default_read_time_out,
write_time_out: deafault_write_time_out,
keep_alive_time_out: deafault_keep_alive_time_out,
keep_alive_max_count: 10,
cache: unsafe { NonNull::new_unchecked(Box::into_raw(Box::new(Cache::new(10, 40)))) },
file_upload_directory_name:String::from("input_files")
}
}
#[cfg(feature="caching")]
pub fn set_cache_config(&mut self, total_cache_size: usize, total_no_of_cache_stores: usize) {
assert!(
total_cache_size % total_no_of_cache_stores == 0,
"TOTAL CACHE SIZE MUST BE DIVISIBLE BY TOTAL NUMBER OF CACHE STORES"
);
let _ = unsafe { Box::from_raw(self.cache.as_ptr()) };
let eache_cache_store_size = total_cache_size / total_no_of_cache_stores;
self.cache = unsafe {
NonNull::new_unchecked(Box::into_raw(Box::new(Cache::new(
total_no_of_cache_stores,
eache_cache_store_size,
))))
};
}
pub fn register_route(
&mut self,
url: &str,
methods: Vec<&str>,
func: RouteFunction,
) -> Result<(), io::Error> {
let mut method_list: Vec<Method> = Vec::new();
for m in methods {
if let Some(_m_) = Method::from_string(m) {
method_list.push(_m_);
} else {
error!(
"No http method named {}. Try using only uppercase letters like GET,POST",
m
);
return Err(io::ErrorKind::InvalidInput.into());
}
}
let route = Route::new(func, method_list);
unsafe { self.routes.as_mut().add_route(url, route) };
Ok(())
}
fn server_wl01_addr() -> Option<String> {
let udp_socket = match UdpSocket::bind("0.0.0.0:0") {
Ok(udps) => udps,
Err(_) => {
return None;
}
};
let _ = match udp_socket.connect("8.8.8.8:80") {
Ok(_) => (),
Err(_) => {
return None;
}
};
if let Ok(local_addr) = udp_socket.local_addr() {
if let IpAddr::V4(ipv4_addr) = local_addr.ip() {
return Some(ipv4_addr.to_string());
} else {
return None;
}
}
return None;
}
pub fn set_total_workers(&mut self, n: usize) -> () {
self.total_workers = n;
}
pub fn set_maximum_payload_size(&mut self, size_in_MB: usize) -> () {
self.payload_maximum_size_in_MB = size_in_MB;
}
pub fn set_read_time_out(&mut self, time_in_secs: u8) -> () {
self.read_time_out = Duration::from_secs(time_in_secs as u64);
}
pub fn set_write_time_out(&mut self, time_in_secs: u8) -> () {
self.write_time_out = Duration::from_secs(time_in_secs as u64);
}
pub fn set_keep_alive_time_out(&mut self, time_in_secs: u8) {
self.keep_alive_time_out = Duration::from_secs(time_in_secs as u64);
}
pub fn set_maximum_keep_alive_requests(&mut self, n_requests: u8) {
self.keep_alive_max_count = n_requests;
}
pub fn set_incoming_files_directory_name(&mut self,directory_name:&str){
self.file_upload_directory_name=String::from(directory_name);
}
pub fn run(&mut self, host: &str, port: u16) -> () {
unsafe {
let mut sig_int_act: sigaction_struct = std::mem::zeroed();
let mut sig_term_act: sigaction_struct = std::mem::zeroed();
let mut sig_tstp_act: sigaction_struct = std::mem::zeroed();
sig_int_act.sa_sigaction = handle_shutdown_signal as usize;
sig_term_act.sa_sigaction = handle_shutdown_signal as usize;
sig_tstp_act.sa_sigaction = handle_sigtstp as usize;
sigaddset(&mut sig_int_act.sa_mask, SIGTERM);
sigaddset(&mut sig_int_act.sa_mask, SIGTSTP);
sigaddset(&mut sig_term_act.sa_mask, SIGINT);
sigaddset(&mut sig_term_act.sa_mask, SIGTSTP);
sigaddset(&mut sig_tstp_act.sa_mask, SIGINT);
sigaddset(&mut sig_tstp_act.sa_mask, SIGTERM);
sig_int_act.sa_flags = SA_RESTART;
sig_term_act.sa_flags = SA_RESTART;
sig_tstp_act.sa_flags = SA_RESTART;
sigaction_syscall(SIGINT, &sig_int_act, std::ptr::null_mut());
sigaction_syscall(SIGTERM, &sig_term_act, std::ptr::null_mut());
sigaction_syscall(SIGTSTP, &sig_tstp_act, std::ptr::null_mut());
}
let mut addr = String::from(host);
addr.push(':');
addr.push_str(&port.to_string());
let listner = match TcpListener::bind(&addr) {
Ok(l) => l,
Err(e) => {
eprintln!("{}", e);
return;
}
};
let send_buf_sz=get_send_buffer_len(listner.as_raw_fd());
let mut app_env = AppEnv::new(host, port, &self,send_buf_sz);
if let Ok(server_addr) = listner.local_addr() {
if server_addr.ip().is_unspecified() {
if let Some(wl10) = Self::server_wl01_addr() {
addr = wl10.clone();
addr.push(':');
addr.push_str(server_addr.port().to_string().as_str());
app_env.host = wl10;
app_env.port = server_addr.port();
}
} else {
addr = String::from("127.0.0.1");
app_env.host = addr.clone();
addr.push(':');
addr.push_str(server_addr.port().to_string().as_str());
app_env.port = server_addr.port();
}
}
log_info!("SERVER STARTED");
log_info!("Listening to {}", &addr);
log_info!("Press CTRL + C to stop the server.");
log_info!("Process PID : {}", process::id());
unsafe {
PORT = app_env.port;
}
let pool = ThreadPool::new(self.total_workers);
let max_keep_alive_count = app_env.keep_alive_max_count;
let app_env_arc = Arc::new(app_env);
for stream in listner.incoming().flatten() {
unsafe {
if SIG_FLAG {
break;
}
}
let stream = Arc::new(stream);
let app_env_cloned = Arc::clone(&app_env_arc);
let cnt = max_keep_alive_count;
pool.execute(move || {
let _ = Client(stream, app_env_cloned, cnt, true);
});
}
}
}
#[cfg(test)]
mod apitest {
use super::*;
use io::Read;
use reqwest::{
blocking::Client,
header::{self, HeaderMap, HeaderValue}
};
use std::{collections::HashMap, fs};
use std::thread;
use utils::{ContentType, FileType};
use Request::HttpRequest;
use Response::{create_response, send_file, HttpResponse};
enum TestResult {
PASSED,
FAILED(String),
}
fn json_header_path_params(
_req: &HttpRequest,
_path_params: HashMap<String, String>,
) -> HttpResponse {
let resp_json = r#"{
"Foo" : "Bar",
"Dummy" : 5
}"#;
let mut resp = create_response(&resp_json, 200, ContentType::JSON, false).unwrap();
for (key, val) in _req.headers.iter() {
resp.add_header(&key, &val);
}
for (key, val) in _path_params.iter() {
resp.add_header(&key, &val);
}
resp
}
fn file_download(_req:&HttpRequest,_path_params:HashMap<String,String>)->HttpResponse{
let resp=send_file("src/test/test.jpg", None,FileType::JPEG,200,false).unwrap();
return resp;
}
fn file_upload(req:&HttpRequest,_path_params:HashMap<String,String>)->HttpResponse{
if let Some(file_path) =&req.body_location{
let mut uploaded_file=match fs::File::open(file_path){
Ok(f)=>f,
Err(e)=>{
let mut s=String::from("FAILED TO OPEN UPLOADED FILE.\n");
s.push_str(e.to_string().as_str());
let resp=create_response(&s, 400, ContentType::TEXT, false).unwrap();
return resp;
}
};
let mut uploaded_file_buf=Vec::<u8>::new();
let _=match uploaded_file.read_to_end(&mut uploaded_file_buf){
Ok(_)=>(),
Err(e)=>{
let mut s=String::from("FAILED TO READ UPLOADED FILE.\n");
s.push_str(e.to_string().as_str());
let resp=create_response(&s, 400, ContentType::TEXT, false).unwrap();
return resp;
}
};
let mut orignal_file=match fs::File::open("src/test/test.jpg"){
Ok(f)=>f,
Err(e)=>{
let mut s=String::from("FAILED TO OPEN ORIGNAL FILE.\n");
s.push_str(e.to_string().as_str());
let resp=create_response(&s, 400, ContentType::TEXT, false).unwrap();
return resp;
}
};
let mut orignal_file_buf=Vec::<u8>::new();
let _=match orignal_file.read_to_end(&mut orignal_file_buf){
Ok(_)=>(),
Err(e)=>{
let mut s=String::from("FAILED TO READ ORIGNAL FILE.\n");
s.push_str(e.to_string().as_str());
let resp=create_response(&s, 400, ContentType::TEXT, false).unwrap();
return resp;
}
};
if uploaded_file_buf.eq(&orignal_file_buf){
let resp=create_response("SUCCESS", 200, ContentType::TEXT, false).unwrap();
return resp;
}
else {
let resp=create_response("ORIGNAL FILE AND UPLOADED FILE DOESN'T MATCH", 200, ContentType::TEXT, false).unwrap();
return resp;
}
}
else {
let resp=create_response("BODY LOCATION IS NONE.", 400, ContentType::TEXT, false).unwrap();
return resp;
}
}
fn run_server(){
let mut app = RastAPI::new();
let _ = app.register_route("/json/{id}/{name}", vec!["GET"], json_header_path_params).expect("FAILED TO REGISTER 1");
let _ = app.register_route("/download", vec!["GET"], file_download).expect("FAILED TO REGISTER 2");
let _=app.register_route("/upload", vec!["POST"], file_upload);
app.run("127.0.0.1", 5000);
}
#[test]
fn json_header_path_params_test() {
let _handle1 = thread::spawn(|| {
run_server();
});
thread::sleep(std::time::Duration::from_secs(1));
let handle2 = thread::spawn(|| {
let mut headers = header::HeaderMap::new();
headers.insert("X-api-key", HeaderValue::from_static("abcdef12"));
let response = match Client::new()
.get("http://127.0.0.1:5000/json/5/rony")
.headers(headers)
.send(){
Ok(R)=>R,
Err(e)=>{
let mut s=String::from("FAILED TO SEND REQUEST. REASON :\n");
s.push_str(e.to_string().as_str());
return TestResult::FAILED(s);
}
};
let resp_json = r#"{
"Foo" : "Bar",
"Dummy" : 5
}"#;
let resp_json_vec = resp_json.as_bytes().to_vec();
let h = response.headers();
if !(h.get("id").map(|hv| hv.to_str().unwrap()).eq(&Some("5"))){
return TestResult::FAILED("FAILED PATH PARAM 1".to_string());
}
if !(h.get("name").map(|hv| hv.to_str().unwrap()).eq(&Some("rony"))){
return TestResult::FAILED("FAILED PATH PARAM 2".to_string());
}
if !(h.get("X-api-key").map(|hv| hv.to_str().unwrap()).eq(&Some("abcdef12"))){
return TestResult::FAILED("FAILED HEADER".to_string());
}
let body = match response.bytes(){
Ok(b)=>b.to_vec(),
Err(e)=>{
let mut s=String::from("FAILED TO RECIEVE BYTES FROM SERVER. REASON :\n");
s.push_str(e.to_string().as_str());
return TestResult::FAILED(s);
}
};
if !(body.eq(&resp_json_vec)){
return TestResult::FAILED(String::from("BODY MISMATCH"));
}
TestResult::PASSED
});
let res=handle2.join().expect("FAILED TO JOIN");
match res {
TestResult::FAILED(S)=>{
assert!(false,"{}",S);
},
TestResult::PASSED=>()
};
}
#[test]
fn file_download_test(){
let _handle1 = thread::spawn(|| {
run_server();
});
let _=thread::sleep(std::time::Duration::from_secs(1));
let handle2=thread::spawn(||{
let resp=match Client::new().get("http://127.0.0.1:5000/download").send(){
Ok(R)=>R,
Err(e)=>{
let mut s=String::from("FAILED TO SEND REQUEST. REASON :\n");
s.push_str(e.to_string().as_str());
return TestResult::FAILED(s);
}
};
let mut file=match fs::File::open("src/test/test.jpg"){
Ok(F)=>F,
Err(e)=>{
let mut s=String::from("FAILED TO OPEN ORIGNAL FILE. REASON :\n");
s.push_str(e.to_string().as_str());
return TestResult::FAILED(s);
}
};
let mut orignal_buf=Vec::<u8>::new();
let _=match file.read_to_end(&mut orignal_buf){
Ok(_)=>(),
Err(e)=>{
let mut s=String::from("FAILED TO LOAD ORIGNAL FILE. REASON :\n");
s.push_str(e.to_string().as_str());
return TestResult::FAILED(s);
}
};
let downloaded_buf=match resp.bytes(){
Ok(b)=>b.to_vec(),
Err(e)=>{
let mut s=String::from("FAILED TO RECIEVE BYTES FROM SERVER. REASON :\n");
s.push_str(e.to_string().as_str());
return TestResult::FAILED(s);
}
};
if !(orignal_buf.eq(&downloaded_buf)){
return TestResult::FAILED(String::from("ORIGNAL FILE AND DOWNLOADED FILE MISMATCH"));
}
TestResult::PASSED
});
let res=handle2.join().expect("FAILED TO JOIN");
match res {
TestResult::FAILED(S)=>{
assert!(false,"{}",S);
},
TestResult::PASSED=>()
};
}
#[test]
fn file_upload_test(){
let _handle1=thread::spawn(||{
run_server();
});
let handle2=thread::spawn(||{
let test_file=match fs::File::open("src/test/test.jpg"){
Ok(f)=>f,
Err(e)=>{
let mut s=String::from("FAILED TO OPEN TEST FILE. REASON :\n");
s.push_str(e.to_string().as_str());
return TestResult::FAILED(s);
}
};
let mut headers=HeaderMap::new();
headers.insert("Content-Type", HeaderValue::from_static("image/jpeg"));
let resp=match Client::new().post("http://127.0.0.1:5000/upload").headers(headers).body(test_file).send(){
Ok(R)=>R,
Err(e)=>{
let mut s=String::from("FAILED TO SEND REQUEST. REASON :\n");
s.push_str(e.to_string().as_str());
return TestResult::FAILED(s);
}
};
if resp.status().as_u16()!=200_u16{
let content=match resp.text(){
Ok(s)=>s,
Err(e)=>{
let mut s=String::from("FAILED TO GET RESPONSE TEXT. REASON :\n");
s.push_str(e.to_string().as_str());
return TestResult::FAILED(s);
}
};
return TestResult::FAILED(content);
}
TestResult::PASSED
});
let res=handle2.join().expect("FAILED TO JOIN");
match res {
TestResult::FAILED(s)=>{
assert!(false,"{}",s);
},
TestResult::PASSED=>()
}
}
}