refactor(wip): extract req handling logic

This commit is contained in:
Compositr 2024-11-01 19:35:25 +11:00
parent db268687f5
commit f27747291f
Signed by: compositr
GPG key ID: 91E3DE20129A0B4A
4 changed files with 95 additions and 81 deletions

72
src/handlers.rs Normal file
View file

@ -0,0 +1,72 @@
use crate::http::{
requests::{Request, RequestStatus},
responses::{Response, UnitOrBoxedError},
};
use std::{
collections::HashMap,
net::{Shutdown, TcpListener},
};
// Collection of handlers for requests
pub struct Handlers<FnT> {
matchers: HashMap<String, FnT>,
}
impl<FnT> Handlers<FnT>
where
FnT: Fn(Request) -> Response<'static>,
{
pub fn new() -> Self {
Handlers::<FnT> {
matchers: HashMap::new(),
}
}
/// Add a request handler
///
/// !! Be sure to bind this Handlers struct to a TcpListener to actually handle requests !!
///
/// path: Path to match (no trailing /)
/// handler: Function to handle the request. Must return a Response
pub fn add_handler(&mut self, path: &str, handler: FnT) {
self.matchers.insert(path.to_string(), handler);
}
/// Bind these handlers to a listener in order to handle incoming requests
/// You will need to pass in a TcpListener
///
/// !! Call this *after* adding all your handlers with add_handler !!
///
/// listener: TcpListener to bind to
pub fn bind(&self, listener: TcpListener) {
for stream in listener.incoming() {
match stream {
Ok(stream) => {
let request = match Request::parse_stream(stream) {
RequestStatus::Ok(req) => req,
RequestStatus::MalformedHTTP(stream) => {
stream.shutdown(Shutdown::Both).unwrap_or_else(|_| {
eprintln!("Failed to close malformed HTTP stream")
});
return;
}
};
self.handle_req(request)
.unwrap_or_else(|_| eprintln!("Failed to send handle request"));
}
Err(e) => {
eprintln!("Failed to establish connection: {}", e);
return;
}
}
}
}
fn handle_req(&self, req: Request) -> UnitOrBoxedError {
match self.matchers.get(&req.url.path) {
Some(handler) => (*handler)(req).send(),
None => Response::new(req, 404, "Not Found").send(),
}
}
}

View file

@ -4,8 +4,6 @@ use std::{
net::TcpStream, net::TcpStream,
}; };
use super::responses;
pub struct URL { pub struct URL {
raw: String, raw: String,
pub path: String, pub path: String,
@ -83,11 +81,7 @@ impl Request {
RequestStatus::Ok(Request { stream, method, url }) RequestStatus::Ok(Request { stream, method, url })
} }
pub fn send_404(&self) -> responses::UnitOrBoxedError { pub fn mut_stream(self) -> TcpStream {
responses::send_response(&self.stream, 404, "Not Found") self.stream
}
pub fn send_ok(&self, body: &str) -> responses::UnitOrBoxedError {
responses::send_response(&self.stream, 200, body)
} }
} }

View file

@ -2,27 +2,27 @@ use std::error::Error;
use std::io::prelude::*; use std::io::prelude::*;
use std::net::TcpStream; use std::net::TcpStream;
use super::requests::Request;
pub struct Response<'a> { pub struct Response<'a> {
stream: &'a mut TcpStream, request: Request,
pub body: &'a str,
pub status: u16, pub status: u16,
} }
impl<'a> Response<'a> { impl<'a> Response<'a> {
pub fn new(stream: &'a mut TcpStream, status: u16) -> Self { pub fn new(request: Request, status: u16, body: &'a str) -> Self {
Response { Response { request, body, status }
stream,
status,
}
} }
pub fn send(&mut self) -> UnitOrBoxedError { pub fn send(self) -> UnitOrBoxedError {
send_response(self.stream, self.status, "") send_response(self.request.mut_stream(), self.status, self.body)
} }
} }
pub type UnitOrBoxedError = Result<(), Box<dyn Error>>; pub type UnitOrBoxedError = Result<(), Box<dyn Error>>;
pub fn send_response(mut stream: &TcpStream, status: u16, body: &str) -> UnitOrBoxedError { fn send_response(mut stream: TcpStream, status: u16, body: &str) -> UnitOrBoxedError {
let status_line = match status { let status_line = match status {
200 => "HTTP/1.1 200 OK", 200 => "HTTP/1.1 200 OK",
400 => "HTTP/1.1 400 BAD REQUEST", 400 => "HTTP/1.1 400 BAD REQUEST",
@ -54,7 +54,3 @@ pub fn send_response(mut stream: &TcpStream, status: u16, body: &str) -> UnitOrB
Ok(()) Ok(())
} }
pub fn ok(stream: &TcpStream, body: &str) -> UnitOrBoxedError {
send_response(stream, 200, body)
}

View file

@ -1,12 +1,9 @@
use std::{ use std::{net::TcpListener, process};
io::{prelude::*, BufReader},
net::{TcpListener, TcpStream},
process,
};
use ctrlc; use ctrlc;
use http::requests::URL; use http::responses::Response;
mod handlers;
mod http; mod http;
fn main() { fn main() {
@ -19,62 +16,17 @@ fn main() {
}) })
.unwrap_or_else(|_| fatal("Failed to set termination signal handler")); .unwrap_or_else(|_| fatal("Failed to set termination signal handler"));
for stream in listener.incoming() { let mut handlers = handlers::Handlers::new();
match stream {
Ok(stream) => {
handle_connection(stream);
}
Err(e) => {
eprintln!("Failed to establish connection: {}", e);
}
}
}
}
fn handle_connection(mut stream: TcpStream) { handlers.add_handler("/", |req| {
let reader = BufReader::new(&mut stream); if req.method == "GET" {
let mut lines = reader.lines(); Response::new(req, 200, "OK - luciders is running")
} else {
let request_line = match lines.next() { Response::new(req, 405, "Method Not Allowed")
Some(Ok(line)) => line,
_ => {
http::responses::send_400(stream)
.unwrap_or_else(|_| error("Failed to send 400 response"));
return;
} }
}; });
let request_line_parts: Vec<&str> = request_line.split_whitespace().collect(); handlers.bind(listener);
if request_line_parts.len() != 3 {
http::responses::send_400(stream).unwrap_or_else(|_| error("Failed to send 400 response"));
return;
}
let (method, url_str) = (request_line_parts[0], request_line_parts[1]);
if method != "GET" {
http::responses::send_405(stream).unwrap_or_else(|_| error("Failed to send 405 response"));
return;
}
let url = match http::requests::URL::new(url_str) {
Some(path) => path,
None => {
http::responses::send_400(stream)
.unwrap_or_else(|_| error("Failed to send 400 response"));
return;
}
};
handle_request(url, stream);
}
fn handle_request(url: URL, stream: TcpStream) {
match url.path {
"/" => http::responses::ok(stream, "OK - luciders is working!")
.unwrap_or_else(|_| error("Failed to send 200 response")),
_ => http::responses::send_404(stream)
.unwrap_or_else(|_| error("Failed to send 404 response")),
}
} }
fn fatal(msg: &'static str) -> ! { fn fatal(msg: &'static str) -> ! {