use std::collections::HashMap;
use std::io::{Read, Write};
use std::net::ToSocketAddrs;
use std::net::{TcpListener, TcpStream};
use std::time::Duration;
use crate::{response, Query};
use response::Response;
use scoped_threadpool::Pool;
use url::Url;
type Handler = dyn Fn(String, Query) -> Response + 'static + Send + Sync;
pub struct WebServer {
listener: TcpListener,
route_handler: Box<Handler>,
read_timeout: Option<Duration>,
}
impl WebServer {
pub fn bind<T: ToSocketAddrs, F: Fn(String, Query) -> Response + Send + Sync + 'static>(
addr: T,
route_handler: F,
) -> std::io::Result<Self> {
Ok(Self {
listener: TcpListener::bind(addr)?,
route_handler: Box::new(route_handler),
read_timeout: None,
})
}
pub fn read_timeout(mut self, timeout: Duration) -> Self {
self.read_timeout = Some(timeout);
self
}
pub fn get_addr(&self) -> std::io::Result<std::net::SocketAddr> {
self.listener.local_addr()
}
pub fn start(&self) -> ! {
let mut incoming = self.listener.incoming();
let mut pool = Pool::new(crate::NUM_THREADS);
loop {
let stream = incoming.next().unwrap();
let stream = stream.expect("Error handling TCP stream.");
stream
.set_read_timeout(self.read_timeout)
.expect("[Error] Couldn't set read timeout on socket");
pool.scoped(|scope| {
scope.execute(|| self.handle_connection(stream));
});
}
}
fn handle_connection(&self, mut stream: TcpStream) {
#[cfg(debug)]
println!("New connection: {}", stream.local_addr().unwrap());
let mut buffer = [0; 4096];
stream.read(&mut buffer).unwrap();
let buffer_str = std::str::from_utf8(&buffer).unwrap();
let path = Self::get_requested_path(buffer_str);
let url = Url::parse(&format!("http://localhost{path}")).unwrap();
let mut query_map: HashMap<String, Vec<String>> =
HashMap::with_capacity(url.query_pairs().count());
let query_pairs = url.query_pairs();
query_pairs.into_iter().for_each(|(k, v)| {
query_map
.entry(k.into_owned())
.or_default()
.push(v.into_owned())
});
let response = self.route_handler.as_ref()(url.path().to_string(), query_map);
write!(stream, "{}", response).expect("Failed to respond");
stream.flush().expect("Failed to respond");
}
fn get_requested_path(request: &str) -> &str {
let request_line = request.lines().next().unwrap_or("");
let path = request_line.split_whitespace().nth(1).unwrap_or("/");
path
}
}