Skip to main content

aver_rt/
http_server.rs

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    // Build Map<lowercase-name, List<value>>. Multiple lines with the
116    // same name accumulate as separate values (RFC 9110 §5.3 — same
117    // semantics as comma-joining for non-Set-Cookie, kept structural
118    // so consumers can choose).
119    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}