#![deny(
clippy::unwrap_used,
clippy::panic,
clippy::expect_used,
unused_must_use
)]
#![warn(clippy::pedantic)]
#![allow(clippy::missing_errors_doc, clippy::module_name_repetitions)]
pub use http::{self, HttpRequest, HttpResponse, request, response};
pub mod config;
pub mod handler;
#[doc(hidden)]
pub mod prelude {
pub use http::{request::HttpRequest, response::*, *};
pub use crate::{
HttpServer,
config::*,
handler::{self, AuthConfig, Handler},
};
}
use prelude::*;
pub type Result<T> = std::result::Result<T, HttpError>;
use std::{
net::{TcpListener, TcpStream},
sync::Arc,
time::{Duration, Instant},
};
pub use config::ServerConfig;
use http::HttpStream;
use pool::ThreadPool;
mod log;
use log::prelude::*;
pub struct HttpServer {
listener: TcpListener,
pool: ThreadPool,
handler: Option<Handler>,
config: ServerConfig,
}
fn peek_stream(stream: &HttpStream, duration: Duration) -> bool {
if stream.set_read_timeout(Some(duration)).is_err() {
return false;
}
let mut buf: [u8; 1] = [0];
let result = match stream.peek(&mut buf) {
Ok(n) => n > 0,
Err(_) => false,
};
if stream.set_read_timeout(None).is_err() {
return false;
}
result
}
fn handle_connection(
stream: TcpStream,
handlers: &Handler,
keep_alive_timeout: Duration,
keep_alive_requests: u16,
) -> Result<()> {
let mut req = HttpRequest::parse(stream)?;
handlers.handle(&mut req)?;
let connection = req.header("Connection");
let keep_alive = keep_alive_timeout.as_millis() > 0;
if connection.is_some_and(|conn| conn == "keep-alive") && keep_alive {
let start = Instant::now();
let mut n = 1;
while start.elapsed() < keep_alive_timeout && n < keep_alive_requests {
let offset = keep_alive_timeout - start.elapsed();
if !peek_stream(req.stream(), offset) {
break;
}
req = req.keep_alive()?;
handlers.handle(&mut req)?;
n += 1;
let connection = req.header("Connection");
if connection.is_some_and(|conn| conn == "close") {
break;
}
}
}
Ok(())
}
#[allow(clippy::unwrap_used)]
#[allow(clippy::expect_used)]
#[allow(clippy::panic)]
impl HttpServer {
pub fn new(config: ServerConfig) -> Result<Self> {
let address = format!("::0:{}", config.port);
let listener = TcpListener::bind(address)
.map_err(|err| format!("Could not bind to port {}: {}", config.port, err))?;
let pool =
ThreadPool::new(config.pool_conf).map_err(|_| "Error initializing thread pool")?;
let handler = Some(Handler::new());
let srv = Self {
listener,
pool,
handler,
config,
};
Ok(srv)
}
#[allow(clippy::missing_panics_doc)]
pub fn run(mut self) {
let handler = Arc::new(self.handler.take().unwrap());
println!("Sever listening on port {}", self.config.port);
for stream in self.listener.incoming().flatten() {
let handler = Arc::clone(&handler);
let timeout = self.config.keep_alive_timeout;
let req = self.config.keep_alive_requests;
self.pool.execute(move || {
handle_connection(stream, &handler, timeout, req).unwrap_or_else(|err| {
log_error!("{err}");
});
});
}
println!("Shutting down.");
}
pub fn set_handler(&mut self, handler: Handler) {
self.handler = Some(handler);
}
}
impl Default for HttpServer {
fn default() -> Self {
let conf = ServerConfig::default();
#[allow(clippy::expect_used)]
let mut srv = Self::new(conf)
.expect("Fatal error: HttpServer failed to initialize with default config");
let handler = Handler::default();
srv.set_handler(handler);
srv
}
}