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}