use std::fs;
use std::fs::File;
use std::io::Read;
use std::net::SocketAddr;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
pub use tokio::runtime::Builder;
#[macro_export]
macro_rules! rohanasan {
($($body:tt)*) => {
use $crate::Builder as why_will_someone_use_this_as_a_name_to_import_task_32194ilqrjf8da;
why_will_someone_use_this_as_a_name_to_import_task_32194ilqrjf8da::new_multi_thread().enable_all()
.build()
.unwrap()
.block_on(
$($body)*
)
};
}
pub const DEFAULT_HTML_HEADER: &str = "HTTP/1.1 200 OK\nContent-Type: text/html";
pub const DEFAULT_JSON_HEADER: &str = "HTTP/1.1 200 OK\nContent-Type: application/json";
pub const ERROR_403_HEADER: &str = "HTTP/1.1 403 Forbidden\nContent-Type: text/html";
pub const DEFAULT_PLAIN_TEXT_HEADER: &str = "HTTP/1.1 200 OK\nContent-Type: text/plain";
pub const DEFAULT_500_HEADER: &str = "HTTP/1.1 500 Internal Server Error\nContent-Type: text/html";
pub const ERROR_404_HEADER: &str = "HTTP/1.1 404 Not Found\nContent-Type: text/html";
pub const DEFAULT_301_HEADER: &str = "HTTP/1.1 301 Moved Permanently\nContent-Type: text/html";
pub const DEFAULT_400_HEADER: &str = "HTTP/1.1 400 Bad Request\nContent-Type: text/html";
pub const DEFAULT_401_HEADER: &str = "HTTP/1.1 401 Unauthorized\nContent-Type: text/html";
pub const DEFAULT_402_HEADER: &str = "HTTP/1.1 402 Payment Required\nContent-Type: text/html";
pub struct Request {
pub method: &'static str,
pub path: &'static str,
pub get_request: &'static str,
pub data: bool,
pub protocol: &'static str,
}
async fn path_exists(path: String) -> bool {
fs::metadata(path).is_ok()
}
async fn handle_connection<F>(mut stream: TcpStream, func: F)
where
F: Fn(Request) -> String + Send,
{
let mut buffer = [0; 1024];
let n = stream
.read(&mut buffer)
.await
.expect("error not able to read socket.");
if n == 0 {
return;
}
let request = &buffer[..n];
let mut headers: Vec<&[u8]> = Vec::new();
let mut current_header_start = 0;
for i in 0..n - 1 {
if request[i] == b'\r' && i + 1 < request.len() && request[i + 1] == b'\n' {
headers.push(&request[current_header_start..=i]);
current_header_start = i + 2;
}
if request[i] == b'\n' {
headers.push(&request[current_header_start..=i]);
current_header_start = i + 2;
}
if request[i] == b'\r'
&& i + 3 < request.len()
&& request[i + 1] == b'\n'
&& request[i + 2] == b'\r'
&& request[i + 3] == b'\n'
{
break;
}
if request[i] == b'\n' && i + 1 < request.len() && request[i + 1] == b'\n' {
break;
}
}
let mut method: &'static str = "POST";
let mut path: &'static str = "";
let mut get_request: &'static str = "";
let mut protocol: &'static str = "";
let mut keep_alive = false;
let mut request_was_correct = true;
for i in headers {
let line_of_header = String::from_utf8(i.to_vec());
match line_of_header {
Ok(line_of_header) => {
let our_line = line_of_header.trim().to_lowercase();
if our_line.starts_with("get") {
method = "GET";
let tokens = our_line
.clone()
.leak()
.split_whitespace()
.collect::<Vec<&str>>(); if tokens.len() > 1 {
if tokens[1].contains('?') {
let parts: Vec<&str> = tokens[1].split('?').collect();
if parts[0].as_bytes()[parts[0].len() - 1] == "/".as_bytes()[0]
&& parts[0] != "/"
{
path = &parts[0][..parts[0].len() - 1];
} else {
path = parts[0];
}
if parts.len() > 1 {
get_request = parts[1];
}
} else if tokens[1].as_bytes()[tokens[1].len() - 1] == "/".as_bytes()[0]
&& tokens[1] != "/"
{
path = &tokens[1][..tokens[1].len() - 1];
} else {
path = tokens[1];
}
}
if tokens.len() > 2 {
protocol = tokens[2];
}
}
if our_line.starts_with("connection")
&& our_line.len() > 11
&& our_line.contains("keep-alive")
{
keep_alive = true;
}
}
Err(_) => {
request_was_correct = false;
}
}
}
if request_was_correct {
if path.starts_with("/static/") && path.len() > 8 {
let file_path = ".".to_owned() + path;
if path_exists(file_path.clone()).await {
let mut content = Vec::new();
let mut file = File::open(&file_path)
.expect("Error opening file (This is not an actual possible error)");
let _ = file.read_to_end(&mut content);
let content_type = determine_content_type(&file_path);
let mut response_headers = format!(
"HTTP/1.1 200 OK\r\nConnection: Close\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n",
content.len(),
content_type
);
if keep_alive {
response_headers = format!(
"HTTP/1.1 200 OK\r\nConnection: Keep-Alive\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n",
content.len(),
content_type
);
}
let mut response = response_headers.into_bytes();
response.extend_from_slice(&content);
stream.write_all(&response).await.expect("Fail to send");
stream.flush().await.expect("");
} else {
let answer = "HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-length: 46\r\nContent-type: text/html\r\n\r\n<h1>404</h1>";
stream
.write_all(answer.as_bytes())
.await
.expect("Fail to send");
stream.flush().await.expect("");
}
} else {
let thing_to_send_to_programmers_function: Request = Request {
method,
path,
get_request,
data: keep_alive,
protocol,
};
let answer = func(thing_to_send_to_programmers_function);
stream
.write_all(answer.as_bytes())
.await
.expect("Fail to send");
stream.flush().await.expect("");
}
} else {
let answer = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-length: 46\r\nContent-type: text/html\r\n\r\n<h1>An invalid http request was received.</h1>";
stream
.write_all(answer.as_bytes())
.await
.expect("Fail to send");
stream.flush().await.expect("");
}
}
pub async fn serve<F>(port: u16, func: F)
where
F: Fn(Request) -> String + Send + 'static + Copy,
{
let addr = SocketAddr::from(([0, 0, 0, 0], port));
let listener = TcpListener::bind(addr).await.expect("");
loop {
let (stream, _) = listener.accept().await.expect("");
tokio::spawn(handle_connection(stream, func));
}
}
pub fn send_http_response(header: &str, body: &str, keep_alive: bool) -> String {
if keep_alive {
format!(
"{}\r\nContent-Length:{}\nConnection:Keep-Alive\r\n\r\n{}",
header,
body.len(),
body
)
} else {
format!(
"{}\r\nContent-Length:{}\nConnection:Close\r\n\r\n{}",
header,
body.len(),
body
)
}
}
pub fn send_file(header: &str, file_path: &str, keep_alive: bool) -> String {
let contents = fs::read_to_string(file_path)
.expect("Please place the html files at the correct place, also check the directory from where you are running this server");
send_http_response(header, &contents, keep_alive)
}
fn determine_content_type(file_path: &str) -> String {
match file_path.rsplit('.').next() {
Some("css") => String::from("text/css"),
Some("txt") => String::from("text/plain"),
Some("js") => String::from("application/javascript"),
Some("png") => String::from("image/png"),
Some("jpg") | Some("jpeg") => String::from("image/jpeg"),
Some("gif") => String::from("image/gif"),
Some("pdf") => String::from("application/pdf"),
Some("htm") | Some("html") => String::from("text/html"),
_ => String::from("application/octet-stream"),
}
}
pub fn url_decode(encoded_string: &str) -> String {
urldecode::decode(encoded_string.to_string())
}