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