express_rs/
lib.rs

1pub mod http;
2
3use http::{Method, Request, Response};
4use std::fmt::Debug;
5use std::io::Read;
6use std::io::Write;
7use std::net::TcpListener;
8
9pub struct Express {
10    mounts: Vec<Mount>,
11}
12
13/// This macro saves a ton of work when adding new HTTP methods.
14/// It generates struct methods that specify the HTTP method, path, and callback.define_method!
15///
16/// # Examples
17/// ```
18/// define_method!(get, Method::GET);
19///
20/// // exposes
21///
22/// app.get("/", |req, res| res.send("Hello"))
23///
24/// ```
25macro_rules! define_method {
26    ($func_name:ident, $method:expr) => {
27        pub fn $func_name<F: 'static>(&mut self, path: &str, callback: F) -> &mut Self
28        where
29            F: FnMut(&Request, &mut Response),
30            Self: Sized,
31        {
32            let mount = Mount {
33                method: $method,
34                path: path.to_string(),
35                callback: Box::new(callback),
36            };
37            self.mounts.push(mount);
38            self
39        }
40    };
41}
42
43/// Main application object
44///
45/// Provides ways to mount path and method combinations
46/// and assign functions to them.
47impl Express {
48    pub fn new() -> Self {
49        Express { mounts: Vec::new() }
50    }
51
52    define_method!(get, Method::GET);
53    define_method!(post, Method::POST);
54    define_method!(put, Method::PUT);
55    define_method!(delete, Method::DELETE);
56    define_method!(patch, Method::PATCH);
57
58    /// Port numbers can range from 1-65535, therefore a u16 is used here
59    ///
60    /// # Panics
61    /// Panics, if:
62    /// - a port is not between 1-65535 or
63    /// - address on host is already in use
64    pub fn listen(&mut self, port: u16) {
65        if port == 0 {
66            panic!("Port must be between 1-65535")
67        }
68
69        let address = format!("0.0.0.0:{}", port);
70        let listener = TcpListener::bind(address)
71            .unwrap_or_else(|_| panic!("Could not bind to port {}", port));
72
73        for stream in listener.incoming() {
74            if let Ok(mut stream) = stream {
75                let mut buffer = [0; 1024];
76                if let Err(e) = stream.read(&mut buffer) {
77                    println!("Could not read to stream: {}", e)
78                }
79                let request =
80                    Request::from_string(String::from_utf8_lossy(&buffer[..]).to_string());
81
82                let mut response = Response::new();
83                if let Ok(request) = request {
84                    self.mounts
85                        .iter_mut()
86                        .filter(|mount| mount.matches(&request))
87                        .for_each(|mount| (mount.callback)(&request, &mut response));
88
89                    let response_text = format!("HTTP/1.1 {} OK\r\n\r\n", response.status);
90                    if let Err(e) = stream.write(response_text.as_bytes()) {
91                        println!("Could not write to response stream: {}", e)
92                    }
93                } else {
94                    if let Err(e) = stream.write(b"HTTP/1.1 400 Bad Request\r\n\r\n") {
95                        println!("Could not write to response stream: {}", e)
96                    }
97                    println!("Request could not be handled");
98                }
99
100                if let Err(e) = stream.write(response.stream.as_bytes()) {
101                    println!("Could not write to response stream: {}", e)
102                }
103
104                if let Err(e) = stream.flush() {
105                    println!("Could not flush response stream: {}", e)
106                }
107            }
108        }
109    }
110}
111
112impl Default for Express {
113    fn default() -> Self {
114        Express::new()
115    }
116}
117
118/// Represents a path with a method.
119pub struct Mount {
120    pub method: Method,
121    pub path: String,
122    pub callback: Box<dyn FnMut(&Request, &mut Response)>,
123}
124
125impl Debug for Mount {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
127        f.debug_struct("Mount")
128            .field("method", &self.method)
129            .field("path", &self.path)
130            .finish()
131    }
132}
133
134impl Mount {
135    fn matches(&self, other: &Request) -> bool {
136        self.method == other.method && self.path == other.path
137    }
138}