1use crate::{AverList, AverStr, HttpHeaders, HttpRequest, HttpResponse};
2use std::io::{BufRead, BufReader, Read, Write};
3use std::net::{TcpListener, TcpStream};
4use std::time::Duration;
5
6pub fn listen<F>(port: i64, mut handler: F) -> Result<(), String>
7where
8 F: FnMut(HttpRequest) -> HttpResponse,
9{
10 let listener = bind_listener(port)?;
11 for incoming in listener.incoming() {
12 let mut stream = incoming
13 .map_err(|e| format!("HttpServer.listen: failed to accept connection: {}", e))?;
14 serve_one(&mut stream, &mut handler);
15 }
16 Ok(())
17}
18
19pub fn listen_with<C, F>(port: i64, context: C, mut handler: F) -> Result<(), String>
20where
21 C: Clone,
22 F: FnMut(C, HttpRequest) -> HttpResponse,
23{
24 let listener = bind_listener(port)?;
25 for incoming in listener.incoming() {
26 let mut stream = incoming
27 .map_err(|e| format!("HttpServer.listen: failed to accept connection: {}", e))?;
28 let ctx = context.clone();
29 serve_one(&mut stream, |request| handler(ctx.clone(), request));
30 }
31 Ok(())
32}
33
34fn bind_listener(port: i64) -> Result<TcpListener, String> {
35 TcpListener::bind(format!("0.0.0.0:{}", port))
36 .map_err(|e| format!("HttpServer.listen: failed to bind on {}: {}", port, e))
37}
38
39fn serve_one<F>(stream: &mut TcpStream, mut handler: F)
40where
41 F: FnMut(HttpRequest) -> HttpResponse,
42{
43 if let Err(e) = stream.set_read_timeout(Some(Duration::from_secs(30))) {
44 let _ = write_http_response(
45 stream,
46 &HttpResponse {
47 status: 500,
48 body: AverStr::from(format!("HttpServer: failed to set read timeout: {}", e)),
49 headers: HttpHeaders::default(),
50 },
51 );
52 return;
53 }
54 if let Err(e) = stream.set_write_timeout(Some(Duration::from_secs(30))) {
55 let _ = write_http_response(
56 stream,
57 &HttpResponse {
58 status: 500,
59 body: AverStr::from(format!("HttpServer: failed to set write timeout: {}", e)),
60 headers: HttpHeaders::default(),
61 },
62 );
63 return;
64 }
65
66 let request = match parse_http_request(stream) {
67 Ok(req) => req,
68 Err(msg) => {
69 let _ = write_http_response(
70 stream,
71 &HttpResponse {
72 status: 400,
73 body: AverStr::from(format!("Bad Request: {}", msg)),
74 headers: HttpHeaders::default(),
75 },
76 );
77 return;
78 }
79 };
80
81 let response = handler(request);
82 let _ = write_http_response(stream, &response);
83}
84
85fn parse_http_request(stream: &mut TcpStream) -> Result<HttpRequest, String> {
86 const BODY_LIMIT: usize = 10 * 1024 * 1024;
87
88 let reader_stream = stream
89 .try_clone()
90 .map_err(|e| format!("cannot clone TCP stream: {}", e))?;
91 let mut reader = BufReader::new(reader_stream);
92
93 let mut request_line = String::new();
94 let line_len = reader
95 .read_line(&mut request_line)
96 .map_err(|e| format!("cannot read request line: {}", e))?;
97 if line_len == 0 {
98 return Err("empty request".to_string());
99 }
100
101 let request_line = request_line.trim_end_matches(&['\r', '\n'][..]);
102 let mut request_parts = request_line.split_whitespace();
103 let method = request_parts
104 .next()
105 .ok_or_else(|| "missing HTTP method".to_string())?
106 .to_string();
107 let path = request_parts
108 .next()
109 .ok_or_else(|| "missing request path".to_string())?
110 .to_string();
111 request_parts
112 .next()
113 .ok_or_else(|| "missing HTTP version".to_string())?;
114
115 let mut headers: HttpHeaders = HttpHeaders::default();
120 let mut content_length = 0usize;
121
122 loop {
123 let mut line = String::new();
124 let bytes = reader
125 .read_line(&mut line)
126 .map_err(|e| format!("cannot read header line: {}", e))?;
127 if bytes == 0 {
128 break;
129 }
130
131 let trimmed = line.trim_end_matches(&['\r', '\n'][..]);
132 if trimmed.is_empty() {
133 break;
134 }
135
136 let (name, value) = trimmed
137 .split_once(':')
138 .ok_or_else(|| format!("malformed header: '{}'", trimmed))?;
139 let name = name.trim().to_ascii_lowercase();
140 let value = value.trim().to_string();
141
142 if name.eq_ignore_ascii_case("Content-Length") {
143 content_length = value
144 .parse::<usize>()
145 .map_err(|_| format!("invalid Content-Length value: '{}'", value))?;
146 if content_length > BODY_LIMIT {
147 return Err(format!("request body exceeds {} bytes limit", BODY_LIMIT));
148 }
149 }
150
151 let key = AverStr::from(name);
152 let value = AverStr::from(value);
153 let entry = match headers.get(&key) {
154 Some(existing) => {
155 let mut buf: Vec<AverStr> = existing.iter().cloned().collect();
156 buf.push(value);
157 AverList::from_vec(buf)
158 }
159 None => AverList::from_vec(vec![value]),
160 };
161 headers = headers.insert(key, entry);
162 }
163
164 let mut body_bytes = vec![0_u8; content_length];
165 if content_length > 0 {
166 reader
167 .read_exact(&mut body_bytes)
168 .map_err(|e| format!("cannot read request body: {}", e))?;
169 }
170 let body = String::from_utf8_lossy(&body_bytes).into_owned();
171
172 Ok(HttpRequest {
173 method: AverStr::from(method),
174 path: AverStr::from(path),
175 body: AverStr::from(body),
176 headers,
177 })
178}
179
180fn status_reason(status: i64) -> &'static str {
181 match status {
182 200 => "OK",
183 201 => "Created",
184 204 => "No Content",
185 301 => "Moved Permanently",
186 302 => "Found",
187 304 => "Not Modified",
188 400 => "Bad Request",
189 401 => "Unauthorized",
190 403 => "Forbidden",
191 404 => "Not Found",
192 405 => "Method Not Allowed",
193 409 => "Conflict",
194 422 => "Unprocessable Entity",
195 429 => "Too Many Requests",
196 500 => "Internal Server Error",
197 501 => "Not Implemented",
198 502 => "Bad Gateway",
199 503 => "Service Unavailable",
200 _ => "OK",
201 }
202}
203
204fn write_http_response(stream: &mut TcpStream, response: &HttpResponse) -> std::io::Result<()> {
205 let mut headers: Vec<(String, String)> = Vec::new();
206 for (name, values) in response.headers.iter() {
207 if name.eq_ignore_ascii_case("Content-Length") || name.eq_ignore_ascii_case("Connection") {
208 continue;
209 }
210 for value in values.iter() {
211 headers.push((name.to_string(), value.to_string()));
212 }
213 }
214
215 if !headers
216 .iter()
217 .any(|(name, _)| name.eq_ignore_ascii_case("Content-Type"))
218 {
219 headers.push((
220 "Content-Type".to_string(),
221 "text/plain; charset=utf-8".to_string(),
222 ));
223 }
224
225 headers.push((
226 "Content-Length".to_string(),
227 response.body.len().to_string(),
228 ));
229 headers.push(("Connection".to_string(), "close".to_string()));
230
231 let mut head = format!(
232 "HTTP/1.1 {} {}\r\n",
233 response.status,
234 status_reason(response.status)
235 );
236 for (name, value) in headers {
237 head.push_str(&format!("{}: {}\r\n", name, value));
238 }
239 head.push_str("\r\n");
240
241 stream.write_all(head.as_bytes())?;
242 stream.write_all(response.body.as_bytes())?;
243 stream.flush()?;
244 Ok(())
245}