refactor(wip): extract req handling logic
This commit is contained in:
parent
db268687f5
commit
f27747291f
4 changed files with 95 additions and 81 deletions
72
src/handlers.rs
Normal file
72
src/handlers.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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 {
|
||||
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)
|
||||
}
|
||||
|
|
72
src/main.rs
72
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) -> ! {
|
||||
|
|
Loading…
Reference in a new issue