Skip to main content

aver/services/
http_server.rs

1use std::collections::HashMap;
2use std::io::{BufRead, BufReader, Read, Write};
3use std::net::{TcpListener, TcpStream};
4use std::time::Duration;
5
6use crate::value::{list_from_vec, list_to_vec, RuntimeError, Value};
7
8#[derive(Debug, Clone)]
9struct ServerRequest {
10    method: String,
11    path: String,
12    body: String,
13    headers: Vec<(String, String)>,
14}
15
16#[derive(Debug, Clone)]
17struct ServerResponse {
18    status: i64,
19    body: String,
20    headers: Vec<(String, String)>,
21}
22
23pub fn register(global: &mut HashMap<String, Value>) {
24    let mut members = HashMap::new();
25    members.insert(
26        "listen".to_string(),
27        Value::Builtin("HttpServer.listen".to_string()),
28    );
29    members.insert(
30        "listenWith".to_string(),
31        Value::Builtin("HttpServer.listenWith".to_string()),
32    );
33    global.insert(
34        "HttpServer".to_string(),
35        Value::Namespace {
36            name: "HttpServer".to_string(),
37            members,
38        },
39    );
40}
41
42pub fn effects(name: &str) -> &'static [&'static str] {
43    match name {
44        "HttpServer.listen" | "HttpServer.listenWith" => &["HttpServer"],
45        _ => &[],
46    }
47}
48
49pub fn call(_name: &str, _args: &[Value]) -> Option<Result<Value, RuntimeError>> {
50    None
51}
52
53pub fn call_with_runtime<F>(
54    name: &str,
55    args: &[Value],
56    mut invoke_handler: F,
57) -> Option<Result<Value, RuntimeError>>
58where
59    F: FnMut(Value, Vec<Value>, String) -> Result<Value, RuntimeError>,
60{
61    match name {
62        "HttpServer.listen" => Some(listen(args, false, &mut invoke_handler)),
63        "HttpServer.listenWith" => Some(listen(args, true, &mut invoke_handler)),
64        _ => None,
65    }
66}
67
68fn listen<F>(
69    args: &[Value],
70    with_context: bool,
71    invoke_handler: &mut F,
72) -> Result<Value, RuntimeError>
73where
74    F: FnMut(Value, Vec<Value>, String) -> Result<Value, RuntimeError>,
75{
76    let expected = if with_context { 3 } else { 2 };
77    if args.len() != expected {
78        let sig = if with_context {
79            "HttpServer.listenWith(port, context, handler)"
80        } else {
81            "HttpServer.listen(port, handler)"
82        };
83        return Err(RuntimeError::Error(format!(
84            "{} expects {} arguments, got {}",
85            sig,
86            expected,
87            args.len()
88        )));
89    }
90
91    let port = match &args[0] {
92        Value::Int(n) if (0..=65535).contains(n) => *n as u16,
93        Value::Int(n) => {
94            return Err(RuntimeError::Error(format!(
95                "HttpServer.listen: port {} is out of range (0-65535)",
96                n
97            )));
98        }
99        _ => {
100            return Err(RuntimeError::Error(
101                "HttpServer.listen: port must be an Int".to_string(),
102            ));
103        }
104    };
105    let (context, handler) = if with_context {
106        (Some(args[1].clone()), args[2].clone())
107    } else {
108        (None, args[1].clone())
109    };
110
111    let listener = TcpListener::bind(("0.0.0.0", port)).map_err(|e| {
112        RuntimeError::Error(format!(
113            "HttpServer.listen: failed to bind on {}: {}",
114            port, e
115        ))
116    })?;
117
118    for incoming in listener.incoming() {
119        let mut stream = match incoming {
120            Ok(s) => s,
121            Err(e) => {
122                return Err(RuntimeError::Error(format!(
123                    "HttpServer.listen: failed to accept connection: {}",
124                    e
125                )));
126            }
127        };
128
129        if let Err(e) = stream.set_read_timeout(Some(Duration::from_secs(30))) {
130            let _ = write_http_response(
131                &mut stream,
132                &ServerResponse {
133                    status: 500,
134                    body: format!("HttpServer: failed to set read timeout: {}", e),
135                    headers: vec![],
136                },
137            );
138            continue;
139        }
140        if let Err(e) = stream.set_write_timeout(Some(Duration::from_secs(30))) {
141            let _ = write_http_response(
142                &mut stream,
143                &ServerResponse {
144                    status: 500,
145                    body: format!("HttpServer: failed to set write timeout: {}", e),
146                    headers: vec![],
147                },
148            );
149            continue;
150        }
151
152        let request = match parse_http_request(&mut stream) {
153            Ok(req) => req,
154            Err(msg) => {
155                let _ = write_http_response(
156                    &mut stream,
157                    &ServerResponse {
158                        status: 400,
159                        body: format!("Bad Request: {}", msg),
160                        headers: vec![],
161                    },
162                );
163                continue;
164            }
165        };
166
167        let request_value = http_request_to_value(&request);
168        let callback_entry = format!("<HttpServer {} {}>", request.method, request.path);
169        let callback_args = match &context {
170            Some(ctx) => vec![ctx.clone(), request_value],
171            None => vec![request_value],
172        };
173        let callback_result = invoke_handler(handler.clone(), callback_args, callback_entry);
174
175        let response = match callback_result {
176            Ok(value) => match http_response_from_value(value) {
177                Ok(resp) => resp,
178                Err(e) => ServerResponse {
179                    status: 500,
180                    body: format!("HttpServer handler return error: {}", e),
181                    headers: vec![],
182                },
183            },
184            Err(e) => ServerResponse {
185                status: 500,
186                body: format!("HttpServer handler execution error: {}", e),
187                headers: vec![],
188            },
189        };
190
191        let _ = write_http_response(&mut stream, &response);
192    }
193
194    Ok(Value::Unit)
195}
196
197fn parse_http_request(stream: &mut TcpStream) -> Result<ServerRequest, String> {
198    const BODY_LIMIT: usize = 10 * 1024 * 1024; // 10 MB
199
200    let reader_stream = stream
201        .try_clone()
202        .map_err(|e| format!("cannot clone TCP stream: {}", e))?;
203    let mut reader = BufReader::new(reader_stream);
204
205    let mut request_line = String::new();
206    let line_len = reader
207        .read_line(&mut request_line)
208        .map_err(|e| format!("cannot read request line: {}", e))?;
209    if line_len == 0 {
210        return Err("empty request".to_string());
211    }
212
213    let request_line = request_line.trim_end_matches(&['\r', '\n'][..]);
214    let mut request_parts = request_line.split_whitespace();
215    let method = request_parts
216        .next()
217        .ok_or_else(|| "missing HTTP method".to_string())?
218        .to_string();
219    let path = request_parts
220        .next()
221        .ok_or_else(|| "missing request path".to_string())?
222        .to_string();
223    let _version = request_parts
224        .next()
225        .ok_or_else(|| "missing HTTP version".to_string())?;
226
227    let mut headers = Vec::new();
228    let mut content_length = 0usize;
229
230    loop {
231        let mut line = String::new();
232        let bytes = reader
233            .read_line(&mut line)
234            .map_err(|e| format!("cannot read header line: {}", e))?;
235        if bytes == 0 {
236            break;
237        }
238
239        let trimmed = line.trim_end_matches(&['\r', '\n'][..]);
240        if trimmed.is_empty() {
241            break;
242        }
243
244        let (name, value) = trimmed
245            .split_once(':')
246            .ok_or_else(|| format!("malformed header: '{}'", trimmed))?;
247        let name = name.trim().to_string();
248        let value = value.trim().to_string();
249
250        if name.eq_ignore_ascii_case("Content-Length") {
251            content_length = value
252                .parse::<usize>()
253                .map_err(|_| format!("invalid Content-Length value: '{}'", value))?;
254            if content_length > BODY_LIMIT {
255                return Err(format!("request body exceeds {} bytes limit", BODY_LIMIT));
256            }
257        }
258
259        headers.push((name, value));
260    }
261
262    let mut body_bytes = vec![0_u8; content_length];
263    if content_length > 0 {
264        reader
265            .read_exact(&mut body_bytes)
266            .map_err(|e| format!("cannot read request body: {}", e))?;
267    }
268    let body = String::from_utf8_lossy(&body_bytes).into_owned();
269
270    Ok(ServerRequest {
271        method,
272        path,
273        body,
274        headers,
275    })
276}
277
278fn http_request_to_value(req: &ServerRequest) -> Value {
279    let headers = req
280        .headers
281        .iter()
282        .map(|(name, value)| Value::Record {
283            type_name: "Header".to_string(),
284            fields: vec![
285                ("name".to_string(), Value::Str(name.clone())),
286                ("value".to_string(), Value::Str(value.clone())),
287            ],
288        })
289        .collect::<Vec<_>>();
290
291    Value::Record {
292        type_name: "HttpRequest".to_string(),
293        fields: vec![
294            ("method".to_string(), Value::Str(req.method.clone())),
295            ("path".to_string(), Value::Str(req.path.clone())),
296            ("body".to_string(), Value::Str(req.body.clone())),
297            ("headers".to_string(), list_from_vec(headers)),
298        ],
299    }
300}
301
302fn http_response_from_value(val: Value) -> Result<ServerResponse, RuntimeError> {
303    let (type_name, fields) = match val {
304        Value::Record { type_name, fields } => (type_name, fields),
305        _ => {
306            return Err(RuntimeError::Error(
307                "HttpServer handler must return HttpResponse record".to_string(),
308            ));
309        }
310    };
311
312    if type_name != "HttpResponse" {
313        return Err(RuntimeError::Error(format!(
314            "HttpServer handler must return HttpResponse, got {}",
315            type_name
316        )));
317    }
318
319    let mut status = None;
320    let mut body = None;
321    let mut headers = Vec::new();
322
323    for (name, value) in fields {
324        match name.as_str() {
325            "status" => {
326                if let Value::Int(n) = value {
327                    status = Some(n);
328                } else {
329                    return Err(RuntimeError::Error(
330                        "HttpResponse.status must be Int".to_string(),
331                    ));
332                }
333            }
334            "body" => {
335                if let Value::Str(s) = value {
336                    body = Some(s);
337                } else {
338                    return Err(RuntimeError::Error(
339                        "HttpResponse.body must be String".to_string(),
340                    ));
341                }
342            }
343            "headers" => {
344                headers = parse_http_response_headers(value)?;
345            }
346            _ => {}
347        }
348    }
349
350    Ok(ServerResponse {
351        status: status
352            .ok_or_else(|| RuntimeError::Error("HttpResponse.status is required".to_string()))?,
353        body: body
354            .ok_or_else(|| RuntimeError::Error("HttpResponse.body is required".to_string()))?,
355        headers,
356    })
357}
358
359fn parse_http_response_headers(val: Value) -> Result<Vec<(String, String)>, RuntimeError> {
360    let list = list_to_vec(&val).ok_or_else(|| {
361        RuntimeError::Error("HttpResponse.headers must be List<Header>".to_string())
362    })?;
363
364    let mut out = Vec::new();
365    for item in list {
366        let fields = match item {
367            Value::Record { fields, .. } => fields,
368            _ => {
369                return Err(RuntimeError::Error(
370                    "HttpResponse.headers entries must be Header records".to_string(),
371                ));
372            }
373        };
374
375        let mut name = None;
376        let mut value = None;
377        for (field_name, field_val) in fields {
378            match (field_name.as_str(), field_val) {
379                ("name", Value::Str(s)) => name = Some(s),
380                ("value", Value::Str(s)) => value = Some(s),
381                _ => {}
382            }
383        }
384
385        let name = name.ok_or_else(|| {
386            RuntimeError::Error("HttpResponse header missing String 'name'".to_string())
387        })?;
388        let value = value.ok_or_else(|| {
389            RuntimeError::Error("HttpResponse header missing String 'value'".to_string())
390        })?;
391        out.push((name, value));
392    }
393
394    Ok(out)
395}
396
397fn status_reason(status: i64) -> &'static str {
398    match status {
399        200 => "OK",
400        201 => "Created",
401        204 => "No Content",
402        301 => "Moved Permanently",
403        302 => "Found",
404        304 => "Not Modified",
405        400 => "Bad Request",
406        401 => "Unauthorized",
407        403 => "Forbidden",
408        404 => "Not Found",
409        405 => "Method Not Allowed",
410        409 => "Conflict",
411        422 => "Unprocessable Entity",
412        429 => "Too Many Requests",
413        500 => "Internal Server Error",
414        501 => "Not Implemented",
415        502 => "Bad Gateway",
416        503 => "Service Unavailable",
417        _ => "OK",
418    }
419}
420
421fn write_http_response(stream: &mut TcpStream, response: &ServerResponse) -> std::io::Result<()> {
422    let mut headers = response
423        .headers
424        .iter()
425        .filter(|(name, _)| {
426            !name.eq_ignore_ascii_case("Content-Length") && !name.eq_ignore_ascii_case("Connection")
427        })
428        .cloned()
429        .collect::<Vec<_>>();
430
431    if !headers
432        .iter()
433        .any(|(name, _)| name.eq_ignore_ascii_case("Content-Type"))
434    {
435        headers.push((
436            "Content-Type".to_string(),
437            "text/plain; charset=utf-8".to_string(),
438        ));
439    }
440
441    headers.push((
442        "Content-Length".to_string(),
443        response.body.len().to_string(),
444    ));
445    headers.push(("Connection".to_string(), "close".to_string()));
446
447    let mut head = format!(
448        "HTTP/1.1 {} {}\r\n",
449        response.status,
450        status_reason(response.status)
451    );
452    for (name, value) in headers {
453        head.push_str(&format!("{}: {}\r\n", name, value));
454    }
455    head.push_str("\r\n");
456
457    stream.write_all(head.as_bytes())?;
458    stream.write_all(response.body.as_bytes())?;
459    stream.flush()?;
460    Ok(())
461}