diff --git a/src/handlers.rs b/src/handlers.rs new file mode 100644 index 0000000..8b5e103 --- /dev/null +++ b/src/handlers.rs @@ -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 { + matchers: HashMap, +} + +impl Handlers +where + FnT: Fn(Request) -> Response<'static>, +{ + pub fn new() -> Self { + Handlers:: { + 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(), + } + } +} diff --git a/src/http/requests.rs b/src/http/requests.rs index 8f3e52a..21e80f1 100644 --- a/src/http/requests.rs +++ b/src/http/requests.rs @@ -4,8 +4,6 @@ use std::{ net::TcpStream, }; -use super::responses; - pub struct URL { raw: String, pub path: String, @@ -83,11 +81,7 @@ impl Request { RequestStatus::Ok(Request { stream, method, url }) } - pub fn send_404(&self) -> responses::UnitOrBoxedError { - responses::send_response(&self.stream, 404, "Not Found") - } - - pub fn send_ok(&self, body: &str) -> responses::UnitOrBoxedError { - responses::send_response(&self.stream, 200, body) + pub fn mut_stream(self) -> TcpStream { + self.stream } } diff --git a/src/http/responses.rs b/src/http/responses.rs index 58d074c..3a06be4 100644 --- a/src/http/responses.rs +++ b/src/http/responses.rs @@ -2,27 +2,27 @@ use std::error::Error; use std::io::prelude::*; use std::net::TcpStream; +use super::requests::Request; + pub struct Response<'a> { - stream: &'a mut TcpStream, + request: Request, + pub body: &'a str, pub status: u16, } impl<'a> Response<'a> { - pub fn new(stream: &'a mut TcpStream, status: u16) -> Self { - Response { - stream, - status, - } + pub fn new(request: Request, status: u16, body: &'a str) -> Self { + Response { request, body, status } } - pub fn send(&mut self) -> UnitOrBoxedError { - send_response(self.stream, self.status, "") + pub fn send(self) -> UnitOrBoxedError { + send_response(self.request.mut_stream(), self.status, self.body) } } pub type UnitOrBoxedError = Result<(), Box>; -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 { 200 => "HTTP/1.1 200 OK", 400 => "HTTP/1.1 400 BAD REQUEST", @@ -54,7 +54,3 @@ pub fn send_response(mut stream: &TcpStream, status: u16, body: &str) -> UnitOrB Ok(()) } - -pub fn ok(stream: &TcpStream, body: &str) -> UnitOrBoxedError { - send_response(stream, 200, body) -} diff --git a/src/main.rs b/src/main.rs index 9322b4c..259fc90 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,9 @@ -use std::{ - io::{prelude::*, BufReader}, - net::{TcpListener, TcpStream}, - process, -}; +use std::{net::TcpListener, process}; use ctrlc; -use http::requests::URL; +use http::responses::Response; +mod handlers; mod http; fn main() { @@ -19,62 +16,17 @@ fn main() { }) .unwrap_or_else(|_| fatal("Failed to set termination signal handler")); - for stream in listener.incoming() { - match stream { - Ok(stream) => { - handle_connection(stream); - } - Err(e) => { - eprintln!("Failed to establish connection: {}", e); - } + let mut handlers = handlers::Handlers::new(); + + handlers.add_handler("/", |req| { + if req.method == "GET" { + Response::new(req, 200, "OK - luciders is running") + } else { + Response::new(req, 405, "Method Not Allowed") } - } -} + }); -fn handle_connection(mut stream: TcpStream) { - let reader = BufReader::new(&mut stream); - let mut lines = reader.lines(); - - let request_line = match lines.next() { - 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(); - 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")), - } + handlers.bind(listener); } fn fatal(msg: &'static str) -> ! {