Skip to main content

aver_rt/
http_server.rs

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}