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.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}