#![warn(missing_docs)]
use std::collections::HashMap;
use std::io::{self, Write};
use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream};
use std::process::exit;
mod utils;
use utils::*;
mod enums;
pub use enums::*;
mod structs;
pub use structs::*;
pub mod handlers;
const VERSION: &str = "HTTP/1.1";
pub enum HandlerMethod {
Directory,
Specific(Method),
Any,
}
pub type HandlerCallback = dyn Fn(Request, Response);
pub type Handler = (HandlerMethod, Box<HandlerCallback>);
pub struct Server {
pub hostname: String,
pub port: u16,
handlers: HashMap<String, Vec<Handler>>,
}
impl Server {
pub fn new<S, N>(hostname: S, port: N) -> Self
where
S: Into<String>,
N: Into<u16>,
{
Self {
hostname: hostname.into(),
port: port.into(),
handlers: HashMap::new(),
}
}
pub fn start(&self, callback: fn()) {
let listener = TcpListener::bind(format!("{}:{}", self.hostname, self.port))
.unwrap_or_else(|err| {
eprintln!("Couldn't initiate TCP server. Error message: {}", err);
exit(1);
});
callback();
for stream in listener.incoming() {
match stream {
Ok(stream) => {
self.handle_connection(stream);
}
Err(e) => {
eprintln!("Failed to establish a new connection. Error message: {}", e);
}
}
}
}
pub fn on<S, H>(&mut self, path: S, handler: H)
where
S: Into<String>,
H: Fn(Request, Response) + 'static,
{
self.append_handler(path.into(), HandlerMethod::Any, handler);
}
pub fn on_get<S, H>(&mut self, path: S, handler: H)
where
S: Into<String>,
H: Fn(Request, Response) + 'static,
{
self.append_handler(path.into(), HandlerMethod::Specific(Method::GET), handler);
}
pub fn on_head<S, H>(&mut self, path: S, handler: H)
where
S: Into<String>,
H: Fn(Request, Response) + 'static,
{
self.append_handler(path.into(), HandlerMethod::Specific(Method::HEAD), handler);
}
pub fn on_post<S, H>(&mut self, path: S, handler: H)
where
S: Into<String>,
H: Fn(Request, Response) + 'static,
{
self.append_handler(path.into(), HandlerMethod::Specific(Method::POST), handler);
}
pub fn on_put<S, H>(&mut self, path: S, handler: H)
where
S: Into<String>,
H: Fn(Request, Response) + 'static,
{
self.append_handler(path.into(), HandlerMethod::Specific(Method::PUT), handler);
}
pub fn on_delete<S, H>(&mut self, path: S, handler: H)
where
S: Into<String>,
H: Fn(Request, Response) + 'static,
{
self.append_handler(
path.into(),
HandlerMethod::Specific(Method::DELETE),
handler,
);
}
pub fn on_directory<S, H>(&mut self, path: S, handler: H)
where
S: Into<String>,
H: Fn(Request, Response) + 'static,
{
self.append_handler(path.into(), HandlerMethod::Directory, handler);
}
fn append_handler<H>(&mut self, path: String, method: HandlerMethod, handler: H)
where
H: Fn(Request, Response) + 'static,
{
match self.handlers.get_mut(&path) {
Some(handlers) => {
handlers.push((method, Box::new(handler)));
}
None => {
self.handlers
.insert(path, vec![(method, Box::new(handler))]);
}
};
}
fn handle_connection(&self, stream: TcpStream) {
let mut connection = Connection::new(stream);
let mut connection_open = true;
'connection_loop: while connection_open {
let mut request = match Request::new(&mut connection) {
Some(value) => value,
None => {
eprintln!("Couldn't create new request for connection. Dropping connection...");
break 'connection_loop;
}
};
let mut err_response = Response::new(&mut connection);
if request.version != Version::new(VERSION).unwrap() {
eprintln!(
"Expected HTTP version {}, found {}. Dropping connection...",
VERSION, request.version
);
err_response.status(Status::new(400).unwrap());
err_response.end();
break 'connection_loop;
}
if request.version != Version::new(VERSION).unwrap() {
eprintln!("Expected 'Host' header, found nothing. Dropping connection...");
err_response.status(Status::new(400).unwrap());
err_response.end();
break 'connection_loop;
}
for (name, value) in request.headers.iter() {
match name.as_str() {
"Connection" => match value.as_str() {
"close" => connection_open = false,
_ => (),
},
_ => (),
}
}
if let Some(handlers) = self.handlers.get(&request.target.full_url()) {
for handler in handlers {
match &handler.0 {
HandlerMethod::Specific(method) => {
if request.method == *method {
(handler.1)(request, Response::new(&mut connection))
}
continue 'connection_loop;
}
HandlerMethod::Any => {
(handler.1)(request, Response::new(&mut connection));
continue 'connection_loop;
}
_ => (),
}
}
} else {
let full_url = request.target.full_url();
let mut path_sections = full_url.split("/");
path_sections.next();
let mut path_string = String::new();
for section in path_sections {
path_string.push_str(&format!("/{}", section));
if let Some(handlers) = self.handlers.get(&path_string) {
if let Some(handler) = handlers
.iter()
.find(|handler| matches!(handler.0, HandlerMethod::Directory))
{
(request.target.target_path, request.target.relative_path) = (
path_string.clone(),
request
.target
.relative_path
.split_at(path_string.len())
.1
.to_string(),
);
(handler.1)(request, Response::new(&mut connection));
continue 'connection_loop;
}
}
}
}
err_response.status(Status::new(404).unwrap());
err_response.end();
break 'connection_loop;
}
connection.terminate_connection()
}
}
pub struct Connection {
pub peer_address: io::Result<SocketAddr>,
stream: TcpStream,
}
impl Connection {
pub fn new(stream: TcpStream) -> Self {
let peer_address = stream.peer_addr();
Self {
peer_address,
stream,
}
}
pub fn terminate_connection(&self) {
loop {
match self.stream.shutdown(Shutdown::Both) {
Ok(_) => break,
Err(_) => (),
}
}
}
}
#[derive(Clone)]
pub struct Request {
pub method: Method,
pub target: Target,
pub version: Version,
pub headers: Headers,
}
impl Request {
pub fn new(parent: &mut Connection) -> Option<Self> {
let first_line = read_line(&mut parent.stream);
let mut splitted_first_line = first_line.split_whitespace();
let mut err_response = Response::new(parent);
if splitted_first_line.clone().count() != 3 {
eprintln!("Invalid HTTP request detected. Dropping connection...");
err_response.status(Status::new(400).unwrap());
err_response.end();
return None;
}
let Some(method) = Method::new(splitted_first_line.next().unwrap()) else {
eprintln!("Invalid HTTP method detected. Dropping connection...");
err_response.status(Status::new(501).unwrap());
err_response.end();
return None;
};
let target = Target::new(splitted_first_line.next().unwrap());
let Some(http_version) = Version::new(splitted_first_line.next().unwrap()) else {
eprintln!("Invalid HTTP version detected. Dropping connection...");
err_response.status(Status::new(400).unwrap());
err_response.end();
return None;
};
let mut headers: Headers = Headers::new();
loop {
let line = read_line(&mut parent.stream);
if line == String::from("") {
break;
}
if parse_header_line(&mut headers, line).is_none() {
eprintln!("Invalid HTTP header syntax detected. Dropping connection...");
return None;
};
}
Some(Self {
method,
target,
version: http_version,
headers,
})
}
}
pub struct Response<'s> {
parent: &'s mut Connection,
pub status: Status,
pub version: Version,
pub headers: Headers,
}
impl<'s> Response<'s> {
pub fn new(parent: &'s mut Connection) -> Self {
Self {
parent,
status: Status::new(200).unwrap(),
version: Version::new(VERSION).unwrap(),
headers: Headers::new(),
}
}
pub fn status(&mut self, status: Status) {
self.status = status;
}
pub fn send<S>(self, message: S)
where
S: Into<String>,
{
let message: String = message.into();
self.parent
.stream
.write(format!("{} {} \r\n", self.version, self.status).as_bytes())
.unwrap();
self.parent
.stream
.write(format!("Content-Length: {}\r\n", message.len()).as_bytes())
.unwrap();
for (name, value) in &self.headers {
self.parent
.stream
.write(format!("{}: {}\r\n", name, value).as_bytes())
.unwrap();
}
self.parent
.stream
.write(format!("\r\n{}", message).as_bytes())
.unwrap();
}
pub fn end(self) {
self.send("");
}
}