oak_http_server/
lib.rs

1#![warn(missing_docs)]
2
3//! **Note:** The library is still in early Alpha/Beta
4//!
5//! A lightweight server library for the HTTP/1.1 protocol
6//!
7//! The aim of this crate is to create a library both easy to use and fast in intercepting incoming connections
8//!
9//! # Quick start
10//!
11//! ```
12//! # #[cfg(feature = "safe")]
13//! # extern crate oak_http_server;
14//! // Library imports
15//! use oak_http_server::{Server, Status};
16//!
17//! fn main() {
18//!     // Save server hostname and port as variables
19//!     let hostname = "localhost";
20//!     let port: u16 = 2300;
21//!     
22//!     // Create a new HTTP server instance (must be mutable since appending handlers to the Server struct modifies its fields)
23//!     let mut server = Server::new(hostname, port);
24//!
25//!     // The following path handler responds to each response to the "/ping" path with "Pong!"
26//!     server.on("/ping", |_request, response| response.send("Pong!"));
27//!     
28//!     // The following path handler responds only to GET requests on the "\headers" path
29//!     // and returns a list of the headers supplied in the corresponding HTTP request
30//!     server.on_get("/headers", |request, response| {
31//!         response.send(format!(
32//!	            "Your browser sent the following headers with the request:\n{}",
33//!	            request
34//!	                .headers
35//!                 .iter()
36//!	                .map(|(name, value)| format!("{}: {}\n", name, value))
37//!	                .collect::<String>(),
38//!         ))
39//!     });
40//!
41//!    // Start the HTTP server. The provided closure/callback function will be called
42//!    // when a connection listener has been successfully established.
43//!    // Once this function is run, the server will begin listening to incoming HTTP requests
44//!    # #[cfg(not)]
45//!    server.start(|| {
46//!        println!("Successfully initiated server");
47//!    });
48//! }
49//! ```
50
51use std::collections::HashMap;
52use std::io::{self, Write};
53use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream};
54use std::process::exit;
55
56mod utils;
57use utils::*;
58
59mod enums;
60pub use enums::*;
61
62mod structs;
63pub use structs::*;
64
65pub mod handlers;
66
67const VERSION: &str = "HTTP/1.1";
68
69/// A custom HTTP method struct that extends [`Method`].
70///
71/// It includes an `Any` field to allow the server to process a [`Request`] of any [`Method`]
72///
73/// There is also a `Directory` field so that the user can create custom URL parsers for a directory or use the ones provided by the library.
74pub enum HandlerMethod {
75    /// Represents a directory handler. Will be run whether the user requests a target that is part of this directory. Also, it is the last handler type in terms of priority
76    Directory,
77    /// A handler that will be run only when a specific [`Method`] is made at the corresponding target
78    Specific(Method),
79    /// Like the [`Specific`](HandlerMethod::Specific) variant, but will run for any type of request
80    Any,
81}
82
83/// The type of the callback function of a [`Handler`]
84pub type HandlerCallback = dyn Fn(Request, Response);
85
86/// The type of a request handler
87pub type Handler = (HandlerMethod, Box<HandlerCallback>);
88
89/// The "heart" of the module; the server struct
90///
91/// It does everything: process requests, pass them to handlers, reject them if they are malformed
92pub struct Server {
93    /// The hostname the server is listening to for requests
94    pub hostname: String,
95    /// The port the server is listening for requests
96    pub port: u16,
97
98    handlers: HashMap<String, Vec<Handler>>,
99}
100
101impl Server {
102    /// Initialize a [`Server`] by passing a hostname and a port number
103    pub fn new<S, N>(hostname: S, port: N) -> Self
104    where
105        S: Into<String>,
106        N: Into<u16>,
107    {
108        Self {
109            hostname: hostname.into(),
110            port: port.into(),
111
112            handlers: HashMap::new(),
113        }
114    }
115
116    /// Start the server and make it process incoming connections
117    pub fn start(&self, callback: fn()) {
118        // Initiate a TCP Listener at localhost port 2300 (port and IP address are subject to change)
119        let listener = TcpListener::bind(format!("{}:{}", self.hostname, self.port))
120            .unwrap_or_else(|err| {
121                eprintln!("Couldn't initiate TCP server. Error message: {}", err);
122                exit(1);
123            });
124
125        callback();
126
127        // For each incoming connection request, accept connection and pass control of connection to "handle_client" function
128        for stream in listener.incoming() {
129            match stream {
130                Ok(stream) => {
131                    self.handle_connection(stream);
132                }
133                Err(e) => {
134                    eprintln!("Failed to establish a new connection. Error message: {}", e);
135                }
136            }
137        }
138    }
139
140    /// Append a function handler that will be called on any request in a specific path
141    pub fn on<S, H>(&mut self, path: S, handler: H)
142    where
143        S: Into<String>,
144        H: Fn(Request, Response) + 'static,
145    {
146        self.append_handler(path.into(), HandlerMethod::Any, handler);
147    }
148
149    /// Same as the [`on()`](`Server::on()`) function, but processes only GET requests
150    pub fn on_get<S, H>(&mut self, path: S, handler: H)
151    where
152        S: Into<String>,
153        H: Fn(Request, Response) + 'static,
154    {
155        self.append_handler(path.into(), HandlerMethod::Specific(Method::GET), handler);
156    }
157
158    /// Same as the [`on()`](`Server::on()`) function, but processes only HEAD requests
159    pub fn on_head<S, H>(&mut self, path: S, handler: H)
160    where
161        S: Into<String>,
162        H: Fn(Request, Response) + 'static,
163    {
164        self.append_handler(path.into(), HandlerMethod::Specific(Method::HEAD), handler);
165    }
166
167    /// Same as the [`on()`](`Server::on()`) function, but processes only POST requests
168    pub fn on_post<S, H>(&mut self, path: S, handler: H)
169    where
170        S: Into<String>,
171        H: Fn(Request, Response) + 'static,
172    {
173        self.append_handler(path.into(), HandlerMethod::Specific(Method::POST), handler);
174    }
175
176    /// Same as the [`on()`](`Server::on()`) function, but processes only PUT requests
177    pub fn on_put<S, H>(&mut self, path: S, handler: H)
178    where
179        S: Into<String>,
180        H: Fn(Request, Response) + 'static,
181    {
182        self.append_handler(path.into(), HandlerMethod::Specific(Method::PUT), handler);
183    }
184
185    /// Same as the [`on()`](`Server::on()`) function, but processes only DELETE requests
186    pub fn on_delete<S, H>(&mut self, path: S, handler: H)
187    where
188        S: Into<String>,
189        H: Fn(Request, Response) + 'static,
190    {
191        self.append_handler(
192            path.into(),
193            HandlerMethod::Specific(Method::DELETE),
194            handler,
195        );
196    }
197
198    /// Append a directory handler that will be called on any request in a specific path
199    pub fn on_directory<S, H>(&mut self, path: S, handler: H)
200    where
201        S: Into<String>,
202        H: Fn(Request, Response) + 'static,
203    {
204        self.append_handler(path.into(), HandlerMethod::Directory, handler);
205    }
206
207    fn append_handler<H>(&mut self, path: String, method: HandlerMethod, handler: H)
208    where
209        H: Fn(Request, Response) + 'static,
210    {
211        match self.handlers.get_mut(&path) {
212            Some(handlers) => {
213                handlers.push((method, Box::new(handler)));
214            }
215            None => {
216                self.handlers
217                    .insert(path, vec![(method, Box::new(handler))]);
218            }
219        };
220    }
221
222    fn handle_connection(&self, stream: TcpStream) {
223        let mut connection = Connection::new(stream);
224
225        let mut connection_open = true;
226
227        'connection_loop: while connection_open {
228            let mut request = match Request::new(&mut connection) {
229                Some(value) => value,
230                None => {
231                    eprintln!("Couldn't create new request for connection. Dropping connection...");
232                    break 'connection_loop;
233                }
234            };
235
236            // Create a HTTP response beforehand that will be used in case an error occurs
237            let mut err_response = Response::new(&mut connection);
238
239            // Before responding, check if the HTTP version of the request is supported (HTTP/1.1)
240            if request.version != Version::new(VERSION).unwrap() {
241                eprintln!(
242                    "Expected HTTP version {}, found {}. Dropping connection...",
243                    VERSION, request.version
244                );
245                err_response.status(Status::new(400).unwrap());
246                err_response.end();
247                break 'connection_loop;
248            }
249
250            // Then check if a `Host` was sent, else respond with a 400 status code
251            if request.version != Version::new(VERSION).unwrap() {
252                eprintln!("Expected 'Host' header, found nothing. Dropping connection...");
253                err_response.status(Status::new(400).unwrap());
254                err_response.end();
255                break 'connection_loop;
256            }
257
258            // Process headers and print them in while doing so
259            for (name, value) in request.headers.iter() {
260                match name.as_str() {
261                    "Connection" => match value.as_str() {
262                        "close" => connection_open = false,
263                        _ => (),
264                    },
265                    _ => (),
266                }
267            }
268
269            // If everything is alright, check if an appropriate handler exists for this request
270            if let Some(handlers) = self.handlers.get(&request.target.full_url()) {
271                for handler in handlers {
272                    match &handler.0 {
273                        HandlerMethod::Specific(method) => {
274                            if request.method == *method {
275                                (handler.1)(request, Response::new(&mut connection))
276                            }
277                            continue 'connection_loop;
278                        }
279                        HandlerMethod::Any => {
280                            (handler.1)(request, Response::new(&mut connection));
281                            continue 'connection_loop;
282                        }
283                        _ => (),
284                    }
285                }
286            } else {
287                let full_url = request.target.full_url();
288                let mut path_sections = full_url.split("/");
289                path_sections.next();
290
291                let mut path_string = String::new();
292
293                for section in path_sections {
294                    path_string.push_str(&format!("/{}", section));
295
296                    if let Some(handlers) = self.handlers.get(&path_string) {
297                        if let Some(handler) = handlers
298                            .iter()
299                            .find(|handler| matches!(handler.0, HandlerMethod::Directory))
300                        {
301                            (request.target.target_path, request.target.relative_path) = (
302                                path_string.clone(),
303                                request
304                                    .target
305                                    .relative_path
306                                    .split_at(path_string.len())
307                                    .1
308                                    .to_string(),
309                            );
310
311                            (handler.1)(request, Response::new(&mut connection));
312                            continue 'connection_loop;
313                        }
314                    }
315                }
316            }
317
318            // Otherwise, respond with a HTTP 404 Not Found status
319            err_response.status(Status::new(404).unwrap());
320            err_response.end();
321            break 'connection_loop;
322        }
323
324        connection.terminate_connection()
325    }
326}
327
328/// A struct representing a HTTP connection between a client and the server
329pub struct Connection {
330    /// The address of the peer client (if known)
331    pub peer_address: io::Result<SocketAddr>,
332
333    stream: TcpStream,
334}
335
336impl Connection {
337    /// Create a new [`Connection`] from a [`TcpStream`]
338    pub fn new(stream: TcpStream) -> Self {
339        // Obtain peer address (if possible) and log it to stdout
340        let peer_address = stream.peer_addr();
341
342        // Code below will probably be uncommented when logging is implemented
343        /*let _readable_peer_address = match peer_address {
344            Ok(sock_addr) => sock_addr.ip().to_string(),
345            Err(_) => String::from("COULDN'T OBTAIN PEER ADDRESS")
346        };*/
347
348        Self {
349            peer_address,
350            stream,
351        }
352    }
353
354    /// Terminates the connection between the client and the server
355    ///
356    /// Note: the [`Connection`] struct shouldn't be used after this function returns
357    pub fn terminate_connection(&self) {
358        loop {
359            match self.stream.shutdown(Shutdown::Both) {
360                Ok(_) => break,
361                Err(_) => (),
362            }
363        }
364    }
365}
366
367/// A HTTP request
368#[derive(Clone)]
369pub struct Request {
370    /// The request's method
371    pub method: Method,
372    /// The target URL of the method
373    pub target: Target,
374    /// The HTTP version the client supports
375    pub version: Version,
376
377    /// A type alias of a Hashmap containing a list of the headers of the [`Request`]
378    pub headers: Headers,
379}
380
381impl Request {
382    /// Create a new [`Request`] from a [`Connection`]
383    pub fn new(parent: &mut Connection) -> Option<Self> {
384        // Begin by reading the first line
385        let first_line = read_line(&mut parent.stream);
386        // Then split it by whitespace
387        let mut splitted_first_line = first_line.split_whitespace();
388
389        // Create a HTTP response beforehand that will be used in case an error occurs
390        let mut err_response = Response::new(parent);
391
392        // Check if the resulting slices aren't three in number (as they should be)
393        if splitted_first_line.clone().count() != 3 {
394            // If yes, print an error message to stderr and immediately terminate connection
395            eprintln!("Invalid HTTP request detected. Dropping connection...");
396            err_response.status(Status::new(400).unwrap());
397            err_response.end();
398            return None;
399        }
400
401        // Else, start obtaining the HTTP method, target and version, terminating the connection in case of errors
402        let Some(method) = Method::new(splitted_first_line.next().unwrap()) else {
403			eprintln!("Invalid HTTP method detected. Dropping connection...");
404			err_response.status(Status::new(501).unwrap());
405			err_response.end();
406			return None;
407		};
408        let target = Target::new(splitted_first_line.next().unwrap());
409        // Note: a HTTP version struct will only check if the HTTP version is in the format "HTTP/{num}.{num}" and won't check if the major and minor revisions of the HTTP protocol exist. This check will occur later on our code
410        let Some(http_version) = Version::new(splitted_first_line.next().unwrap()) else {
411			eprintln!("Invalid HTTP version detected. Dropping connection...");
412			err_response.status(Status::new(400).unwrap());
413			err_response.end();
414			return None;
415		};
416
417        // Create a variable for storing HTTP headers
418        let mut headers: Headers = Headers::new();
419
420        // Obtain available HTTP headers
421        loop {
422            let line = read_line(&mut parent.stream);
423
424            if line == String::from("") {
425                break;
426            }
427
428            if parse_header_line(&mut headers, line).is_none() {
429                eprintln!("Invalid HTTP header syntax detected. Dropping connection...");
430                return None;
431            };
432        }
433
434        Some(Self {
435            method,
436            target,
437            version: http_version,
438            headers,
439        })
440    }
441}
442
443/// A HTTP response for the server to reply to the client
444pub struct Response<'s> {
445    parent: &'s mut Connection,
446
447    /// The HTTP status code of the response
448    pub status: Status,
449    /// The HTTP version of the response
450    pub version: Version,
451
452    /// A type alias of a Hashmap containing the headers of the response
453    pub headers: Headers,
454}
455
456impl<'s> Response<'s> {
457    /// Create a new [`Response`]
458    pub fn new(parent: &'s mut Connection) -> Self {
459        Self {
460            parent,
461            status: Status::new(200).unwrap(),
462            version: Version::new(VERSION).unwrap(),
463            headers: Headers::new(),
464        }
465    }
466
467    /// CHange the [`Status`] of the response
468    pub fn status(&mut self, status: Status) {
469        self.status = status;
470    }
471
472    /// Send the response along with a message (consumes the response)
473    pub fn send<S>(self, message: S)
474    where
475        S: Into<String>,
476    {
477        let message: String = message.into();
478
479        // Send a HTTP status line response
480        self.parent
481            .stream
482            .write(format!("{} {} \r\n", self.version, self.status).as_bytes())
483            .unwrap();
484
485        // Send a header indicating message length
486        self.parent
487            .stream
488            .write(format!("Content-Length: {}\r\n", message.len()).as_bytes())
489            .unwrap();
490
491        // Loop through each header and write them to connection stream
492        for (name, value) in &self.headers {
493            self.parent
494                .stream
495                .write(format!("{}: {}\r\n", name, value).as_bytes())
496                .unwrap();
497        }
498
499        // Send the response to the client (the CRLF before the response is to signal the beginning of message body)
500        // If the message is empty, this will essentialy write "\r\n" to the stream, so it will be like there is a message body of zero length
501        self.parent
502            .stream
503            .write(format!("\r\n{}", message).as_bytes())
504            .unwrap();
505    }
506
507    /// Send an empty response (consumes it)
508    pub fn end(self) {
509        // Basically send an empty response
510        self.send("");
511    }
512}