2025-06-16 17:57:51 +01:00
|
|
|
use std::{collections::HashMap, io::{BufRead, BufReader, Error, ErrorKind, Result, Write}, net::{SocketAddr, TcpListener, TcpStream}};
|
2025-04-14 17:14:22 +01:00
|
|
|
|
|
|
|
|
use chrono::Local;
|
|
|
|
|
|
|
|
|
|
use crate::ThreadPool;
|
|
|
|
|
|
2025-04-14 18:36:16 +01:00
|
|
|
#[derive(Clone, Copy)]
|
2025-04-14 17:14:22 +01:00
|
|
|
pub enum StatusCode {
|
|
|
|
|
OK,
|
2025-04-14 18:36:16 +01:00
|
|
|
BadRequest,
|
2025-04-14 17:14:22 +01:00
|
|
|
NotFound,
|
2025-04-14 18:36:16 +01:00
|
|
|
InternalServerError,
|
2025-04-14 17:14:22 +01:00
|
|
|
// ImATeapot,
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 18:36:16 +01:00
|
|
|
impl StatusCode {
|
|
|
|
|
pub fn code(&self) -> u16 {
|
|
|
|
|
match self {
|
|
|
|
|
StatusCode::OK => 200,
|
|
|
|
|
StatusCode::BadRequest => 400,
|
|
|
|
|
StatusCode::NotFound => 404,
|
|
|
|
|
// StatusCode::ImATeapot => 418,
|
|
|
|
|
StatusCode::InternalServerError => 500,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn reason(&self) -> &str {
|
|
|
|
|
match self {
|
|
|
|
|
StatusCode::OK => "OK",
|
|
|
|
|
StatusCode::BadRequest => "Bad Request",
|
|
|
|
|
StatusCode::NotFound => "Not Found",
|
|
|
|
|
// StatusCode::ImATeapot => "I'm a teapot",
|
|
|
|
|
StatusCode::InternalServerError => "Internal Server Error",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type HttpHandlerFunc = fn(&Request, Response) -> Result<StatusCode>;
|
|
|
|
|
|
2025-04-14 17:14:22 +01:00
|
|
|
pub struct Request<'a> {
|
|
|
|
|
stream: &'a TcpStream,
|
|
|
|
|
path: &'a str,
|
|
|
|
|
method: &'a str,
|
|
|
|
|
version: &'a str,
|
|
|
|
|
headers: HashMap<&'a str, &'a str>,
|
2025-04-14 18:36:16 +01:00
|
|
|
query: HashMap<&'a str, &'a str>,
|
2025-04-14 17:14:22 +01:00
|
|
|
body: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Request<'a> {
|
|
|
|
|
pub fn new(stream: &'a TcpStream, lines: &'a Vec<String>) -> Result<Request<'a>> {
|
|
|
|
|
let request_line = lines[0].as_str();
|
|
|
|
|
let request_line_split: Vec<&str> = request_line.split(" ").collect();
|
2025-06-16 17:57:51 +01:00
|
|
|
if request_line_split.len() < 3 {
|
|
|
|
|
return Err(Error::new(ErrorKind::Other, "invalid request start-line"));
|
|
|
|
|
}
|
2025-04-14 17:14:22 +01:00
|
|
|
let method = request_line_split[0];
|
2025-06-16 17:57:51 +01:00
|
|
|
let mut path = request_line_split[1];
|
2025-04-14 17:14:22 +01:00
|
|
|
let version = request_line_split[2];
|
|
|
|
|
|
2025-04-14 18:36:16 +01:00
|
|
|
let mut query: HashMap<&'a str, &'a str> = HashMap::new();
|
2025-06-16 17:57:51 +01:00
|
|
|
match request_line_split[1].split_once("?") {
|
|
|
|
|
Some((path_without_query, query_string)) => {
|
|
|
|
|
path = path_without_query;
|
2025-04-14 18:36:16 +01:00
|
|
|
let query_splits: Vec<&'a str> = query_string.split("&").collect();
|
|
|
|
|
for pair in query_splits {
|
|
|
|
|
match pair.split_once("=") {
|
|
|
|
|
Some((name, value)) => {
|
|
|
|
|
query.insert(name, value);
|
|
|
|
|
}
|
|
|
|
|
None => { continue; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None => {},
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 17:14:22 +01:00
|
|
|
let mut headers: HashMap<&'a str, &'a str> = HashMap::new();
|
|
|
|
|
if lines.len() > 1 {
|
|
|
|
|
let mut i: usize = 1;
|
|
|
|
|
loop {
|
|
|
|
|
if i >= lines.len() { break; }
|
|
|
|
|
let line = &lines[i];
|
|
|
|
|
if line.len() == 0 || !line.contains(":") { break; }
|
|
|
|
|
let (name, value) = line.split_once(":").unwrap();
|
|
|
|
|
headers.insert(name, value.trim());
|
|
|
|
|
i += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut body: Option<String> = None;
|
|
|
|
|
if lines.len() > headers.len() + 2 &&
|
|
|
|
|
(method == "POST" || method == "PUT") &&
|
|
|
|
|
lines[headers.len() + 1] == "\r\n"
|
|
|
|
|
{
|
|
|
|
|
body = Some(lines[headers.len() + 2..].join("\n"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(Request {
|
|
|
|
|
stream,
|
|
|
|
|
path,
|
|
|
|
|
method,
|
|
|
|
|
version,
|
|
|
|
|
headers,
|
2025-04-14 18:36:16 +01:00
|
|
|
query,
|
2025-04-14 17:14:22 +01:00
|
|
|
body,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn address(&self) -> Result<SocketAddr> {
|
|
|
|
|
self.stream.peer_addr()
|
|
|
|
|
}
|
|
|
|
|
pub fn path(&self) -> &'a str {
|
|
|
|
|
self.path
|
|
|
|
|
}
|
|
|
|
|
pub fn method(&self) -> &'a str {
|
|
|
|
|
self.method
|
|
|
|
|
}
|
|
|
|
|
pub fn version(&self) -> &'a str {
|
|
|
|
|
self.version
|
|
|
|
|
}
|
|
|
|
|
pub fn body(&self) -> &Option<String> {
|
|
|
|
|
&self.body
|
|
|
|
|
}
|
|
|
|
|
pub fn headers(&self) -> &HashMap<&'a str, &'a str> {
|
|
|
|
|
&self.headers
|
|
|
|
|
}
|
2025-04-14 18:36:16 +01:00
|
|
|
pub fn query(&self) -> &HashMap<&'a str, &'a str> {
|
|
|
|
|
&self.query
|
|
|
|
|
}
|
2025-04-14 17:14:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct Response<'a> {
|
|
|
|
|
stream: &'a TcpStream,
|
|
|
|
|
status: StatusCode,
|
|
|
|
|
headers: HashMap<&'a str, String>,
|
|
|
|
|
body: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Response<'a> {
|
|
|
|
|
pub fn new(stream: &'a TcpStream) -> Response<'a> {
|
|
|
|
|
Response {
|
|
|
|
|
stream,
|
|
|
|
|
status: StatusCode::OK,
|
|
|
|
|
headers: HashMap::from([
|
|
|
|
|
("Server", "mcstatusface".to_string()),
|
|
|
|
|
("Content-Type", "text/plain".to_string()),
|
|
|
|
|
]),
|
|
|
|
|
body: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn status(&mut self, status: StatusCode) {
|
|
|
|
|
self.status = status;
|
|
|
|
|
}
|
|
|
|
|
pub fn headers(&self) -> &HashMap<&'a str, String> {
|
|
|
|
|
&self.headers
|
|
|
|
|
}
|
|
|
|
|
pub fn set_header(&mut self, name: &'a str, value: String) {
|
|
|
|
|
self.headers.insert(name, value);
|
|
|
|
|
}
|
|
|
|
|
pub fn body(&mut self, body: String) {
|
|
|
|
|
self.body = Some(body);
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 18:36:16 +01:00
|
|
|
pub fn send(&mut self) -> Result<StatusCode> {
|
|
|
|
|
// let mut len: usize = 0;
|
|
|
|
|
self.stream.write(format!("HTTP/1.1 {} {}\r\n", self.status.code(), self.status.reason()).as_bytes()).unwrap();
|
2025-04-14 17:14:22 +01:00
|
|
|
|
|
|
|
|
let mut content_length: usize = 0;
|
|
|
|
|
if self.body.is_some() {
|
|
|
|
|
content_length = self.body.as_ref().unwrap().len();
|
|
|
|
|
}
|
|
|
|
|
self.set_header("Content-Length", content_length.to_string());
|
|
|
|
|
for (name, value) in &self.headers {
|
2025-04-14 18:36:16 +01:00
|
|
|
self.stream.write(format!("{name}: {value}\r\n").as_bytes()).unwrap();
|
2025-04-14 17:14:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.body.is_some() {
|
2025-04-14 18:36:16 +01:00
|
|
|
self.stream.write("\r\n".as_bytes()).unwrap();
|
|
|
|
|
self.stream.write(self.body.as_ref().unwrap().as_bytes()).unwrap();
|
2025-04-14 17:14:22 +01:00
|
|
|
}
|
2025-04-14 18:36:16 +01:00
|
|
|
Ok(self.status)
|
2025-04-14 17:14:22 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct HttpServer {
|
|
|
|
|
address: String,
|
|
|
|
|
port: u16,
|
|
|
|
|
max_connections: usize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl HttpServer {
|
2025-04-14 18:36:16 +01:00
|
|
|
pub fn new(address: String, max_connections: usize) -> HttpServer {
|
|
|
|
|
let mut _address = address.clone();
|
|
|
|
|
let mut _port: u16 = 8080;
|
|
|
|
|
match address.split_once(":") {
|
|
|
|
|
Some((ip, port)) => {
|
|
|
|
|
_address = ip.to_string();
|
|
|
|
|
_port = port.parse::<u16>().expect(format!("Invalid port {}", port).as_str());
|
|
|
|
|
}
|
|
|
|
|
None => {}
|
|
|
|
|
}
|
2025-04-14 17:14:22 +01:00
|
|
|
HttpServer {
|
2025-04-14 18:36:16 +01:00
|
|
|
address: _address,
|
|
|
|
|
port: _port,
|
|
|
|
|
max_connections,
|
2025-04-14 17:14:22 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 18:36:16 +01:00
|
|
|
pub fn start(&self, handler: HttpHandlerFunc) -> Result<()> {
|
2025-04-14 17:14:22 +01:00
|
|
|
let pool = ThreadPool::new(self.max_connections);
|
|
|
|
|
let listener = TcpListener::bind(format!("{}:{}", self.address, self.port)).expect("Failed to bind to port");
|
|
|
|
|
|
|
|
|
|
println!("Now listening on {}:{}", self.address, self.port);
|
|
|
|
|
|
|
|
|
|
for stream in listener.incoming() {
|
|
|
|
|
match stream {
|
|
|
|
|
Ok(stream) => {
|
|
|
|
|
pool.execute(move || {
|
2025-04-14 18:36:16 +01:00
|
|
|
HttpServer::handle_client(&stream, handler);
|
2025-04-14 17:14:22 +01:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
Err(e) => {
|
|
|
|
|
eprintln!("Failed to handle incoming connection: {e}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 18:36:16 +01:00
|
|
|
fn handle_client(stream: &TcpStream, handler: HttpHandlerFunc) {
|
2025-04-14 17:14:22 +01:00
|
|
|
let buf_reader = BufReader::new(stream);
|
|
|
|
|
let http_request: Vec<String> = buf_reader
|
|
|
|
|
.lines()
|
|
|
|
|
.map(|result| result.unwrap())
|
|
|
|
|
.take_while(|line| !line.is_empty())
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
let request = Request::new(stream, &http_request);
|
|
|
|
|
if request.is_err() {
|
|
|
|
|
eprintln!("Failed to process request: {}", request.err().unwrap());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let request = request.unwrap();
|
|
|
|
|
|
|
|
|
|
let response = Response::new(stream);
|
|
|
|
|
|
|
|
|
|
let start_date = Local::now();
|
2025-04-14 18:36:16 +01:00
|
|
|
match handler(&request, response) {
|
|
|
|
|
Ok(status) => {
|
2025-04-14 17:14:22 +01:00
|
|
|
let end_date = Local::now();
|
|
|
|
|
println!(
|
2025-04-14 18:36:16 +01:00
|
|
|
"[{}] {} {} {} - {} {} - {}ms - {} ({})",
|
2025-04-14 17:14:22 +01:00
|
|
|
start_date.format("%Y-%m-%d %H:%M:%S"),
|
2025-04-14 18:36:16 +01:00
|
|
|
|
2025-04-14 17:14:22 +01:00
|
|
|
request.method(),
|
|
|
|
|
request.path(),
|
|
|
|
|
request.version(),
|
2025-04-14 18:36:16 +01:00
|
|
|
|
|
|
|
|
status.code(),
|
|
|
|
|
status.reason(),
|
|
|
|
|
|
2025-04-14 17:14:22 +01:00
|
|
|
(end_date - start_date).num_milliseconds(),
|
2025-04-14 18:36:16 +01:00
|
|
|
|
|
|
|
|
request.headers().get("User-Agent").map_or("[]", |v| v),
|
2025-04-14 17:14:22 +01:00
|
|
|
request.address().unwrap().ip(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
Err(e) => {
|
|
|
|
|
eprintln!("Failed to handle request: {e}")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|