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}