1use crate::{AverList, Header, 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: format!("HttpServer: failed to set read timeout: {}", e),
49 headers: AverList::empty(),
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: format!("HttpServer: failed to set write timeout: {}", e),
60 headers: AverList::empty(),
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: format!("Bad Request: {}", msg),
74 headers: AverList::empty(),
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 = Vec::new();
116 let mut content_length = 0usize;
117
118 loop {
119 let mut line = String::new();
120 let bytes = reader
121 .read_line(&mut line)
122 .map_err(|e| format!("cannot read header line: {}", e))?;
123 if bytes == 0 {
124 break;
125 }
126
127 let trimmed = line.trim_end_matches(&['\r', '\n'][..]);
128 if trimmed.is_empty() {
129 break;
130 }
131
132 let (name, value) = trimmed
133 .split_once(':')
134 .ok_or_else(|| format!("malformed header: '{}'", trimmed))?;
135 let name = name.trim().to_string();
136 let value = value.trim().to_string();
137
138 if name.eq_ignore_ascii_case("Content-Length") {
139 content_length = value
140 .parse::<usize>()
141 .map_err(|_| format!("invalid Content-Length value: '{}'", value))?;
142 if content_length > BODY_LIMIT {
143 return Err(format!("request body exceeds {} bytes limit", BODY_LIMIT));
144 }
145 }
146
147 headers.push(Header { name, value });
148 }
149
150 let mut body_bytes = vec![0_u8; content_length];
151 if content_length > 0 {
152 reader
153 .read_exact(&mut body_bytes)
154 .map_err(|e| format!("cannot read request body: {}", e))?;
155 }
156 let body = String::from_utf8_lossy(&body_bytes).into_owned();
157
158 Ok(HttpRequest {
159 method,
160 path,
161 body,
162 headers: AverList::from_vec(headers),
163 })
164}
165
166fn status_reason(status: i64) -> &'static str {
167 match status {
168 200 => "OK",
169 201 => "Created",
170 204 => "No Content",
171 301 => "Moved Permanently",
172 302 => "Found",
173 304 => "Not Modified",
174 400 => "Bad Request",
175 401 => "Unauthorized",
176 403 => "Forbidden",
177 404 => "Not Found",
178 405 => "Method Not Allowed",
179 409 => "Conflict",
180 422 => "Unprocessable Entity",
181 429 => "Too Many Requests",
182 500 => "Internal Server Error",
183 501 => "Not Implemented",
184 502 => "Bad Gateway",
185 503 => "Service Unavailable",
186 _ => "OK",
187 }
188}
189
190fn write_http_response(stream: &mut TcpStream, response: &HttpResponse) -> std::io::Result<()> {
191 let mut headers = response
192 .headers
193 .iter()
194 .filter(|h| {
195 !h.name.eq_ignore_ascii_case("Content-Length")
196 && !h.name.eq_ignore_ascii_case("Connection")
197 })
198 .map(|h| (h.name.clone(), h.value.clone()))
199 .collect::<Vec<_>>();
200
201 if !headers
202 .iter()
203 .any(|(name, _)| name.eq_ignore_ascii_case("Content-Type"))
204 {
205 headers.push((
206 "Content-Type".to_string(),
207 "text/plain; charset=utf-8".to_string(),
208 ));
209 }
210
211 headers.push((
212 "Content-Length".to_string(),
213 response.body.len().to_string(),
214 ));
215 headers.push(("Connection".to_string(), "close".to_string()));
216
217 let mut head = format!(
218 "HTTP/1.1 {} {}\r\n",
219 response.status,
220 status_reason(response.status)
221 );
222 for (name, value) in headers {
223 head.push_str(&format!("{}: {}\r\n", name, value));
224 }
225 head.push_str("\r\n");
226
227 stream.write_all(head.as_bytes())?;
228 stream.write_all(response.body.as_bytes())?;
229 stream.flush()?;
230 Ok(())
231}