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,
|
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
70
src/main.rs
70
src/main.rs
|
@ -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) -> ! {
|
||||||
|
|
Loading…
Reference in a new issue