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