Skip to main content

http_srv/
lib.rs

1//! Http Server Crate
2//!
3//! This crate contains all the libraries necessary to run an HTTP Server
4//!
5//! # Example
6//! ```rust,no_run
7//! use http_srv::prelude::*;
8//!
9//! let config = ServerConfig::default();
10//!
11//! let mut handler = Handler::new();
12//! handler.add_default(HttpMethod::GET, handler::cat_handler);
13//! handler.get("/", handler::root_handler);
14//! handler.get("/hello", |req: &mut HttpRequest| {
15//!     let name = req.param("name").unwrap_or("friend");
16//!     let msg = format!("Hello {name}!");
17//!     req.respond_str(&msg)
18//! });
19//!
20//! let mut server = HttpServer::new(config).unwrap();
21//! server.set_handler(handler);
22//! server.run();
23//! ```
24
25#![deny(
26    clippy::unwrap_used,
27    clippy::panic,
28    clippy::expect_used,
29    unused_must_use
30)]
31#![warn(clippy::pedantic)]
32#![allow(
33    clippy::missing_errors_doc,
34    clippy::module_name_repetitions,
35    clippy::unchecked_time_subtraction
36)]
37
38pub use http::{self, HttpRequest, HttpResponse, request, response};
39pub mod config;
40
41pub mod handler;
42
43#[doc(hidden)]
44pub mod prelude {
45    pub use http::{request::HttpRequest, response::*, *};
46
47    pub use crate::{
48        HttpServer,
49        config::*,
50        handler::{self, AuthConfig, Handler},
51    };
52}
53use prelude::*;
54
55/// Result type for the [`http_srv`](self) crate
56///
57/// It serves as a shortcut for an [`std::result::Result`]<T,[`HttpError`]>
58pub type Result<T> = std::result::Result<T, HttpError>;
59
60use std::{
61    io::{self, BufRead, BufReader},
62    net::{TcpListener, TcpStream},
63    sync::Arc,
64    thread,
65    time::{Duration, Instant},
66};
67
68pub use config::ServerConfig;
69use http::HttpStream;
70use pool::ThreadPool;
71
72pub mod log;
73use log::prelude::*;
74
75/// HTTP Server
76///
77/// Represents an HTTP Server, bound to a TCP Port
78///
79/// # Example
80/// ```rust,no_run
81/// use http_srv::HttpServer;
82/// use http_srv::ServerConfig;
83/// use http_srv::handler::Handler;
84/// use http_srv::request::HttpRequest;
85///
86/// let config = ServerConfig::default();
87/// let mut server = HttpServer::new(config).unwrap();
88/// let mut handler = Handler::new();
89/// handler.get("/", |req: &mut HttpRequest| {
90///     req.ok()
91/// });
92/// server.set_handler(handler);
93/// server.run();
94/// ```
95pub struct HttpServer {
96    listener: TcpListener,
97    pool: ThreadPool,
98    handler: Option<Handler>,
99    config: ServerConfig,
100}
101
102fn peek_stream(
103    stream: &mut BufReader<Box<dyn HttpStream>>,
104    duration: Duration,
105) -> io::Result<bool> {
106    stream.get_mut().set_non_blocking(duration)?;
107    let result = match stream.fill_buf() {
108        Ok(b) => !b.is_empty(),
109        Err(err) => match err.kind() {
110            io::ErrorKind::WouldBlock => false,
111            _ => return Err(err),
112        },
113    };
114    stream.get_mut().set_blocking()?;
115    Ok(result)
116}
117
118fn handle_connection(
119    stream: TcpStream,
120    handlers: &Handler,
121    keep_alive_timeout: Duration,
122    keep_alive_requests: u16,
123
124    #[cfg(feature = "tls")] tls_config: Option<&Arc<rustls::ServerConfig>>,
125) -> Result<()> {
126    #[cfg(feature = "tls")]
127    let mut req = match tls_config {
128        Some(config) => {
129            let conn = rustls::ServerConnection::new(Arc::clone(config))
130                .map_err(|err| format!("TLS error: {err}"))?;
131            let tls_stream = rustls::StreamOwned::new(conn, stream);
132            HttpRequest::parse(tls_stream)?
133        }
134        None => HttpRequest::parse(stream)?,
135    };
136
137    #[cfg(not(feature = "tls"))]
138    let mut req = HttpRequest::parse(stream)?;
139
140    handlers.handle(&mut req)?;
141
142    let connection = req.header("Connection");
143    let keep_alive = keep_alive_timeout.as_millis() > 0;
144    if connection.is_some_and(|conn| conn == "keep-alive") && keep_alive {
145        let start = Instant::now();
146        let mut n = 1;
147        log_info!("[{:?}] Start keep alive", thread::current().id());
148        while start.elapsed() < keep_alive_timeout && n < keep_alive_requests {
149            let offset = keep_alive_timeout - start.elapsed();
150
151            match peek_stream(req.stream_mut(), offset) {
152                Ok(false) => break,
153                Err(err) => {
154                    log_error!("Error on peek_stream: {err}");
155                    break;
156                }
157                _ => {}
158            }
159
160            req = req.keep_alive()?;
161            handlers.handle(&mut req)?;
162            n += 1;
163
164            let connection = req.header("Connection");
165            if connection.is_some_and(|conn| conn == "close") {
166                break;
167            }
168        }
169        log_info!("[{:?}] End keep alive", thread::current().id());
170    }
171    Ok(())
172}
173
174#[allow(clippy::unwrap_used)]
175#[allow(clippy::expect_used)]
176#[allow(clippy::panic)]
177impl HttpServer {
178    /// Create a new HTTP Server
179    ///
180    /// # Errors
181    /// - If the server fails to bind to the TCP port
182    /// - If the thread pool fails to initialize
183    ///
184    pub fn new(config: ServerConfig) -> Result<Self> {
185        let address = format!("::0:{}", config.port);
186        let listener = TcpListener::bind(address)
187            .map_err(|err| format!("Could not bind to port {}: {}", config.port, err))?;
188        let pool =
189            ThreadPool::new(config.pool_conf).map_err(|_| "Error initializing thread pool")?;
190        let handler = Some(Handler::new());
191        let srv = Self {
192            listener,
193            pool,
194            handler,
195            config,
196        };
197        Ok(srv)
198    }
199    /// Starts the server
200    #[allow(clippy::missing_panics_doc)]
201    pub fn run(self) {
202        let Self {
203            listener,
204            pool,
205            handler,
206            config,
207        } = self;
208        let handler = handler.unwrap();
209        let timeout = config.keep_alive_timeout;
210        let req = config.keep_alive_requests;
211
212        #[cfg(feature = "tls")]
213        let tls_config = config.tls_config.as_ref();
214
215        println!("Sever listening on port {}", config.port);
216
217        pool.scope(|scope| {
218            for stream in listener.incoming().flatten() {
219                scope.execute(|| {
220                    handle_connection(
221                        stream,
222                        &handler,
223                        timeout,
224                        req,
225                        #[cfg(feature = "tls")]
226                        tls_config,
227                    )
228                    .unwrap_or_else(|err| {
229                        log_error!("{err}");
230                    });
231                });
232            }
233        });
234
235        println!("Shutting down.");
236    }
237    /// Set a [Handler] for all the [requests](HttpRequest) on this [server](HttpServer)
238    pub fn set_handler(&mut self, handler: Handler) {
239        self.handler = Some(handler);
240    }
241}
242
243impl Default for HttpServer {
244    /// Default Server
245    ///
246    /// - Configuration: [`ServerConfig::default`]
247    /// - Handler: [`Handler::default`]
248    ///
249    /// # Panics
250    /// If the [`HttpServer`] fails to initialize
251    fn default() -> Self {
252        let conf = ServerConfig::default();
253        #[allow(clippy::expect_used)]
254        let mut srv = Self::new(conf)
255            .expect("Fatal error: HttpServer failed to initialize with default config");
256        let handler = Handler::default();
257        srv.set_handler(handler);
258        srv
259    }
260}