httplite/
lib.rs

1use std::collections::HashMap;
2use std::io::{Read, Write};
3use std::net::TcpListener;
4use std::sync::{Arc, Mutex};
5
6pub struct Httplite {
7    port: String,
8    routes: Arc<Mutex<HashMap<String, fn(ResponseWriter, Request)>>>,
9}
10
11impl Httplite {
12    pub fn new(port: &str) -> Httplite {
13        Httplite {
14            port: port.to_string(),
15            routes: Arc::new(Mutex::new(HashMap::new())),
16        }
17    }
18
19    pub fn add_route(&self, route: &str, handler: fn(ResponseWriter, Request)) {
20        let mut routes = self.routes.lock().unwrap();
21        routes.insert(route.to_string(), handler);
22    }
23
24    pub fn listen(&self) -> std::io::Result<()> {
25        let addr = if self.port.starts_with(':') {
26            format!("127.0.0.1{}", self.port)
27        } else {
28            self.port.clone()
29        };
30
31        let listener = TcpListener::bind(&addr)?;
32
33        for stream in listener.incoming() {
34            let routes = Arc::clone(&self.routes);
35            let mut stream = stream?;
36            let mut buffer = [0; 1024];
37            stream.read(&mut buffer)?;
38
39            let request_str = String::from_utf8_lossy(&buffer[..]);
40            let request = Request::new(request_str.to_string());
41            let url = request.url().to_string();
42            let routes = routes.lock().unwrap();
43
44            let mut found = false;
45            {
46                let response_writer = ResponseWriter::new(stream.try_clone()?);
47
48                for (route, handler) in routes.iter() {
49                    if url.starts_with(route) {
50                        handler(response_writer, request);
51                        found = true;
52                        break;
53                    }
54                }
55            }
56
57            if !found {
58                let mut response_writer = ResponseWriter::new(stream);
59                response_writer.print_text("404 Not Found")?;
60            }
61        }
62
63        Ok(())
64    }
65}
66
67pub struct Request {
68    raw: String,
69}
70
71impl Request {
72    pub fn new(raw: String) -> Self {
73        Self { raw }
74    }
75
76    pub fn url(&self) -> &str {
77        let request_line = self.raw.lines().next().unwrap_or_default();
78        request_line.split_whitespace().nth(1).unwrap_or("/")
79    }
80
81    #[allow(dead_code)]
82    pub fn method(&self) -> &str {
83        let request_line = self.raw.lines().next().unwrap_or_default();
84        request_line.split_whitespace().next().unwrap_or("GET")
85    }
86}
87
88pub struct ResponseWriter {
89    stream: std::net::TcpStream,
90}
91
92impl ResponseWriter {
93    pub fn new(stream: std::net::TcpStream) -> Self {
94        Self { stream }
95    }
96
97    pub fn write(&mut self, response: &str) -> std::io::Result<()> {
98        self.stream.write(response.as_bytes())?;
99        self.stream.flush()?;
100        Ok(())
101    }
102
103    pub fn print_text(&mut self, text: &str) -> std::io::Result<()> {
104        let response = format!(
105            "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n{}",
106            text
107        );
108        self.write(&response)
109    }
110
111    pub fn print_hashmap_to_json<K, V>(&mut self, hashmap: &HashMap<K, V>) -> std::io::Result<()>
112    where
113        K: ToString,
114        V: ToJson,
115    {
116        let mut json = String::from("{");
117        for (key, value) in hashmap {
118            json.push_str(&format!("\"{}\":{},", key.to_string(), value.to_json()));
119        }
120        json.pop();
121        json.push('}');
122
123        let response = format!(
124            "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n{}",
125            json
126        );
127        self.write(&response)
128    }
129}
130
131pub trait ToJson {
132    fn to_json(&self) -> String;
133}
134
135impl ToJson for String {
136    fn to_json(&self) -> String {
137        format!("\"{}\"", self)
138    }
139}
140
141impl ToJson for &str {
142    fn to_json(&self) -> String {
143        format!("\"{}\"", self)
144    }
145}
146
147impl ToJson for i32 {
148    fn to_json(&self) -> String {
149        self.to_string()
150    }
151}
152
153impl<T: ToJson> ToJson for Vec<T> {
154    fn to_json(&self) -> String {
155        let mut json = String::from("[");
156        for item in self {
157            json.push_str(&format!("{},", item.to_json()));
158        }
159        json.pop();
160        json.push(']');
161        json
162    }
163}
164
165impl<T: ToJson, const N: usize> ToJson for [T; N] {
166    fn to_json(&self) -> String {
167        let mut json = String::from("[");
168        for item in self {
169            json.push_str(&format!("{},", item.to_json()));
170        }
171        json.pop();
172        json.push(']');
173        json
174    }
175}
176
177impl<K: ToJson, V: ToJson> ToJson for HashMap<K, V> {
178    fn to_json(&self) -> String {
179        let mut json = String::from("{");
180        for (key, value) in self {
181            json.push_str(&format!("\"{}\":{},", key.to_json(), value.to_json()));
182        }
183        json.pop();
184        json.push('}');
185        json
186    }
187}