torus_http/
server.rs

1//! The actual http server on which you define your routes
2use std::{
3    collections::HashMap,
4    error::Error,
5    io::{Read, Write},
6    net::{TcpListener, ToSocketAddrs},
7    str::{FromStr, from_utf8},
8};
9
10use crate::{method::HttpMethod, request::Request, response::Response};
11
12type BoxedResponse = Box<dyn Response>;
13type Handler = Box<dyn HandlerFn>;
14
15/// The struct to initialise your http server and finally listen on some port
16///
17/// # Example usage:
18///
19/// ```rust
20/// HttpServer::new(("127.0.0.1", 8080)).run() // no_op http server listening on port 8080
21/// ```
22pub struct HttpServer<A>
23where
24    A: ToSocketAddrs + Clone,
25{
26    address: A,
27    handlers: HashMap<(String, HttpMethod), Handler>,
28    middle_ware: Option<fn(req: Request) -> Request>,
29}
30
31/// A generic trait to allow many different types of handlers to be passed into our http server
32pub trait HandlerFn {
33    fn call(&self, req: Request) -> BoxedResponse;
34}
35
36impl<F, T> HandlerFn for F
37where
38    F: Fn(Request) -> T,
39    T: Response + 'static,
40{
41    fn call(&self, req: Request) -> BoxedResponse {
42        Box::new(self(req))
43    }
44}
45
46impl<Addr> HttpServer<Addr>
47where
48    Addr: ToSocketAddrs + Clone,
49{
50    /// Initialise an http server on an address
51    pub fn new(address: Addr) -> Self {
52        Self {
53            address,
54            handlers: HashMap::new(),
55            middle_ware: None,
56        }
57    }
58
59    /// Initialises middleware or replaces if there was already some added
60    ///
61    /// subject to change
62    ///
63    /// # Example usage:
64    ///
65    /// ```rust
66    /// HttpServer::new(("127.0.0.1", 8080)).add_middleware(|req| {
67    ///     println!("we got request: {req:#?}");
68    ///     req
69    /// })
70    /// ```
71    #[must_use]
72    pub fn add_middleware(mut self, f: fn(req: Request) -> Request) -> Self {
73        self.middle_ware.replace(f);
74        self
75    }
76
77    /// Register a custom route
78    ///
79    /// # Example usage:
80    ///
81    /// ```rust
82    /// HttpServer::new(("127.0.0.1", 8080)).route("/some_path", HttpMethod::Other("custom"), |_| {"hi"})
83    /// ```
84    #[must_use]
85    pub fn route<F: HandlerFn + 'static>(
86        mut self,
87        path: impl Into<String>,
88        method: HttpMethod,
89        f: F,
90    ) -> Self {
91        self.handlers.insert((path.into(), method), Box::new(f));
92        self
93    }
94
95    /// Register a **GET** method
96    ///
97    /// # Example usage:
98    ///
99    /// ```rust
100    /// fn home_method(_req: HttpRequest) -> impl Response {
101    ///     "hello, world"
102    /// }
103    /// HttpServer::new(("127.0.0.1", 8080)).get("/home", )
104    /// ```
105    ///
106    /// ## Note:
107    ///
108    /// I drop the body for get requests as that is apparently standard
109    #[must_use]
110    pub fn get<F: HandlerFn + 'static>(self, path: impl Into<String>, f: F) -> Self {
111        self.route(path, HttpMethod::Get, f)
112    }
113
114    /// Register a **POST** method
115    ///
116    /// # Example usage:
117    ///
118    /// ```rust
119    /// fn my_post(_req: HttpRequest) -> impl Response {
120    ///     // ... Super complex DB activity
121    ///     "I'll keep you posted"
122    /// }
123    /// HttpServer::new(("127.0.0.1", 8080)).post("/drop/prod/db", my_post)
124    /// ```
125    #[must_use]
126    pub fn post<F: HandlerFn + 'static>(self, path: impl Into<String>, f: F) -> Self {
127        self.route(path, HttpMethod::Post, f)
128    }
129
130    /// Register a **DELETE** method
131    ///
132    /// # Example usage:
133    ///
134    /// ```rust
135    /// fn my_delete(_req: HttpRequest) -> impl Response {
136    ///     // delete browser history ...
137    ///     "Yeah I don't use the internet bro trust me..."
138    /// }
139    /// HttpServer::new(("127.0.0.1", 8080)).delete("/homework", my_delete)
140    /// ```
141    #[must_use]
142    pub fn delete<F: HandlerFn + 'static>(self, path: impl Into<String>, f: F) -> Self {
143        self.route(path, HttpMethod::Delete, f)
144    }
145
146    /// Register an **UPDATE** method
147    ///
148    /// # Example usage:
149    ///
150    /// ```rust
151    /// fn im_getting_tired_of_writing_these(_req: HttpRequest) -> impl Response {
152    ///     // just read the others like .get() and .post() bro
153    ///     "Yeah I don't use the internet bro trust me..."
154    /// }
155    /// HttpServer::new(("127.0.0.1", 8080)).delete("/homework", im_getting_tired_of_writing_these)
156    /// ```
157    #[must_use]
158    pub fn update<F: HandlerFn + 'static>(self, path: impl Into<String>, f: F) -> Self {
159        self.route(path, HttpMethod::Update, f)
160    }
161
162    /// Register a **PUT** method
163    ///
164    /// # Example usage:
165    ///
166    /// ```rust
167    /// fn im_getting_tired_of_writing_these(_req: HttpRequest) -> impl Response {
168    ///     "WHY THE HECK DID I ADD SO MANY OF THESE THINGS"
169    /// }
170    /// HttpServer::new(("127.0.0.1", 8080)).delete("/us-east1", im_getting_tired_of_writing_these)
171    /// ```
172    #[must_use]
173    pub fn put<F: HandlerFn + 'static>(self, path: impl Into<String>, f: F) -> Self {
174        self.route(path, HttpMethod::Put, f)
175    }
176
177    /// like `.post()` but patch
178    #[must_use]
179    pub fn patch<F: HandlerFn + 'static>(self, path: impl Into<String>, f: F) -> Self {
180        self.route(path, HttpMethod::Patch, f)
181    }
182
183    /// I just took this one from hoppscotch I never heard of the head method before
184    /// read `.post()` and stuff for documentation
185    #[must_use]
186    pub fn head<F: HandlerFn + 'static>(self, path: impl Into<String>, f: F) -> Self {
187        self.route(path, HttpMethod::Head, f)
188    }
189
190    /// Shoutout to chatgpt for this one:
191    /// Register an **OPTIONS** method
192    ///
193    /// This attaches a handler to the given path that responds to http `OPTIONS`
194    /// requests. Typically used for capability discovery, CORS preflight checks,
195    /// or politely telling browsers what they are allowed to do.
196    ///
197    /// # Example usage:
198    ///
199    /// ```rust
200    /// fn options_method(_req: HttpRequest) -> impl Response {
201    ///     ""
202    /// }
203    ///
204    /// HttpServer::new(("127.0.0.1", 8080)).options("/home", options_method);
205    /// ```
206    ///
207    /// ## Note:
208    ///
209    /// `OPTIONS` requests are generally expected to return headers describing
210    /// allowed methods and behaviors. A response body is usually unnecessary and
211    /// often ignored, but nothing is stopping you from adding one if you enjoy
212    /// disappointing strict HTTP purists.
213    #[must_use]
214    pub fn options<F: HandlerFn + 'static>(self, path: impl Into<String>, f: F) -> Self {
215        self.route(path, HttpMethod::Options, f)
216    }
217
218    /// Start your http server
219    ///
220    /// # Errors
221    ///
222    /// - Failed binding listener to address
223    /// - Failed reading the stream to the buffer
224    /// - Failed getting the stream
225    /// - Failed parsing the request
226    /// - Failed flushing to the stream
227    pub fn run(&self) -> Result<(), Box<dyn Error>> {
228        let listener = TcpListener::bind(self.address.clone())?;
229        loop {
230            let (mut stream, _) = listener.accept()?;
231            // TODO: handle differently as this can probably easily overflow
232            let mut buf = [0; 4096];
233
234            let n = stream.read(&mut buf)?;
235            let request = {
236                let request = Request::from_str(from_utf8(&buf[..n])?)?;
237                if let Some(middle_ware) = self.middle_ware {
238                    middle_ware(request)
239                } else {
240                    request
241                }
242            };
243            let path = request.path.clone();
244            let method = request.method.clone();
245            // TODO: handle this error
246            let _write_success = if let Some(intercept) = self.handlers.get(&(path, method)) {
247                let ret = intercept.call(request);
248                stream.write_all(ret.to_response().into_bytes().as_slice())
249            } else {
250                stream.write_all(&"no method found".to_response().into_bytes())
251            };
252
253            stream.flush()?;
254        }
255    }
256}