1use std::collections::HashMap;
2use std::sync::Arc as Rc;
3
4use aver_rt::{AverList, AverStr, HttpHeaders, HttpRequest, HttpResponse};
5
6use crate::nan_value::{Arena, NanValue};
7use crate::value::{RuntimeError, Value, list_from_vec, list_view};
8
9pub fn register(global: &mut HashMap<String, Value>) {
10 let mut members = HashMap::new();
11 members.insert(
12 "listen".to_string(),
13 Value::Builtin("HttpServer.listen".to_string()),
14 );
15 members.insert(
16 "listenWith".to_string(),
17 Value::Builtin("HttpServer.listenWith".to_string()),
18 );
19 global.insert(
20 "HttpServer".to_string(),
21 Value::Namespace {
22 name: "HttpServer".to_string(),
23 members,
24 },
25 );
26}
27
28pub fn register_nv(global: &mut HashMap<String, NanValue>, arena: &mut Arena) {
29 let mut members: Vec<(Rc<str>, NanValue)> = Vec::with_capacity(2);
30 let idx1 = arena.push_builtin("HttpServer.listen");
31 members.push((Rc::from("listen"), NanValue::new_builtin(idx1)));
32 let idx2 = arena.push_builtin("HttpServer.listenWith");
33 members.push((Rc::from("listenWith"), NanValue::new_builtin(idx2)));
34 let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
35 name: Rc::from("HttpServer"),
36 members,
37 });
38 global.insert("HttpServer".to_string(), NanValue::new_namespace(ns_idx));
39}
40
41pub const DECLARED_EFFECTS: &[&str] = &["HttpServer.listen", "HttpServer.listenWith"];
42
43pub fn effects(name: &str) -> &'static [&'static str] {
44 match name {
45 "HttpServer.listen" => &["HttpServer.listen"],
46 "HttpServer.listenWith" => &["HttpServer.listenWith"],
47 "SelfHostRuntime.httpServerListen" => &["HttpServer.listen"],
48 "SelfHostRuntime.httpServerListenWith" => &["HttpServer.listenWith"],
49 _ => &[],
50 }
51}
52
53pub fn call(_name: &str, _args: &[Value]) -> Option<Result<Value, RuntimeError>> {
54 None
55}
56
57pub fn call_with_runtime<F>(
58 name: &str,
59 args: &[Value],
60 mut invoke_handler: F,
61 skip_server: bool,
62) -> Option<Result<Value, RuntimeError>>
63where
64 F: FnMut(Value, Vec<Value>, String) -> Result<Value, RuntimeError>,
65{
66 match name {
67 "HttpServer.listen" => Some(listen(args, false, &mut invoke_handler, skip_server)),
68 "HttpServer.listenWith" => Some(listen(args, true, &mut invoke_handler, skip_server)),
69 "SelfHostRuntime.httpServerListen" => {
70 Some(listen(args, false, &mut invoke_handler, skip_server))
71 }
72 "SelfHostRuntime.httpServerListenWith" => {
73 Some(listen(args, true, &mut invoke_handler, skip_server))
74 }
75 _ => None,
76 }
77}
78
79fn listen<F>(
80 args: &[Value],
81 with_context: bool,
82 invoke_handler: &mut F,
83 skip_server: bool,
84) -> Result<Value, RuntimeError>
85where
86 F: FnMut(Value, Vec<Value>, String) -> Result<Value, RuntimeError>,
87{
88 let expected = if with_context { 3 } else { 2 };
89 if args.len() != expected {
90 let sig = if with_context {
91 "HttpServer.listenWith(port, context, handler)"
92 } else {
93 "HttpServer.listen(port, handler)"
94 };
95 return Err(RuntimeError::Error(format!(
96 "{} expects {} arguments, got {}",
97 sig,
98 expected,
99 args.len()
100 )));
101 }
102
103 if skip_server {
104 return Ok(Value::Unit);
105 }
106
107 let port = match &args[0] {
108 Value::Int(n) => match n.to_i64() {
109 Some(p) if (0..=65535).contains(&p) => p,
110 _ => {
111 return Err(RuntimeError::Error(format!(
112 "HttpServer.listen: port {} is out of range (0-65535)",
113 n
114 )));
115 }
116 },
117 _ => {
118 return Err(RuntimeError::Error(
119 "HttpServer.listen: port must be an Int".to_string(),
120 ));
121 }
122 };
123 let handler = if with_context {
124 args[2].clone()
125 } else {
126 args[1].clone()
127 };
128
129 let result = if with_context {
130 let context = args[1].clone();
131 aver_rt::http_server::listen_with(port, context, |ctx, request| {
132 dispatch_handler(&handler, Some(ctx), request, invoke_handler)
133 })
134 } else {
135 aver_rt::http_server::listen(port, |request| {
136 dispatch_handler(&handler, None, request, invoke_handler)
137 })
138 };
139
140 result.map_err(RuntimeError::Error)?;
141 Ok(Value::Unit)
142}
143
144fn dispatch_handler<F>(
145 handler: &Value,
146 context: Option<Value>,
147 request: HttpRequest,
148 invoke_handler: &mut F,
149) -> HttpResponse
150where
151 F: FnMut(Value, Vec<Value>, String) -> Result<Value, RuntimeError>,
152{
153 let callback_entry = format!("<HttpServer {} {}>", &*request.method, &*request.path);
154 let mut callback_args = Vec::new();
155 if let Some(ctx) = context {
156 callback_args.push(ctx);
157 }
158 callback_args.push(http_request_to_value(request));
159
160 let callback_result = invoke_handler(handler.clone(), callback_args, callback_entry);
161 match callback_result {
162 Ok(value) => match http_response_from_value(value) {
163 Ok(resp) => resp,
164 Err(e) => HttpResponse {
165 status: 500,
166 body: aver_rt::AverStr::from(format!("HttpServer handler return error: {}", e)),
167 headers: HttpHeaders::default(),
168 },
169 },
170 Err(e) => HttpResponse {
171 status: 500,
172 body: aver_rt::AverStr::from(format!("HttpServer handler execution error: {}", e)),
173 headers: HttpHeaders::default(),
174 },
175 }
176}
177
178fn http_request_to_value(req: HttpRequest) -> Value {
179 let mut headers = HashMap::new();
180 for (name, values) in req.headers.iter() {
181 let value_list: Vec<Value> = values.iter().map(|v| Value::Str(v.to_string())).collect();
182 headers.insert(Value::Str(name.to_string()), list_from_vec(value_list));
183 }
184
185 Value::Record {
186 type_name: "HttpRequest".to_string(),
187 fields: vec![
188 ("method".to_string(), Value::Str(req.method.to_string())),
189 ("path".to_string(), Value::Str(req.path.to_string())),
190 ("body".to_string(), Value::Str(req.body.to_string())),
191 ("headers".to_string(), Value::Map(headers)),
192 ]
193 .into(),
194 }
195}
196
197fn http_response_from_value(val: Value) -> Result<HttpResponse, RuntimeError> {
198 let (type_name, fields) = match val {
199 Value::Record { type_name, fields } => (type_name, fields),
200 _ => {
201 return Err(RuntimeError::Error(
202 "HttpServer handler must return HttpResponse record".to_string(),
203 ));
204 }
205 };
206
207 if type_name != "HttpResponse" {
208 return Err(RuntimeError::Error(format!(
209 "HttpServer handler must return HttpResponse, got {}",
210 type_name
211 )));
212 }
213
214 let mut status = None;
215 let mut body = None;
216 let mut headers = HttpHeaders::default();
217
218 for (name, value) in fields.iter() {
219 match name.as_str() {
220 "status" => {
221 if let Value::Int(n) = value {
222 status = Some(n.to_i64().ok_or_else(|| {
223 RuntimeError::Error(
224 "HttpResponse.status is out of range for an HTTP status".to_string(),
225 )
226 })?);
227 } else {
228 return Err(RuntimeError::Error(
229 "HttpResponse.status must be Int".to_string(),
230 ));
231 }
232 }
233 "body" => {
234 if let Value::Str(s) = value {
235 body = Some(aver_rt::AverStr::from(s.as_str()));
236 } else {
237 return Err(RuntimeError::Error(
238 "HttpResponse.body must be String".to_string(),
239 ));
240 }
241 }
242 "headers" => {
243 headers = parse_http_response_headers(value.clone())?;
244 }
245 _ => {}
246 }
247 }
248
249 Ok(HttpResponse {
250 status: status
251 .ok_or_else(|| RuntimeError::Error("HttpResponse.status is required".to_string()))?,
252 body: body
253 .ok_or_else(|| RuntimeError::Error("HttpResponse.body is required".to_string()))?,
254 headers,
255 })
256}
257
258fn parse_http_response_headers(val: Value) -> Result<HttpHeaders, RuntimeError> {
259 let map = match val {
260 Value::Map(m) => m,
261 _ => {
262 return Err(RuntimeError::Error(
263 "HttpResponse.headers must be Map<String, List<String>>".to_string(),
264 ));
265 }
266 };
267
268 let mut out = HttpHeaders::default();
269 for (k, v) in map.into_iter() {
270 let name = match k {
271 Value::Str(s) => s.to_ascii_lowercase(),
272 _ => {
273 return Err(RuntimeError::Error(
274 "HttpResponse.headers keys must be Strings".to_string(),
275 ));
276 }
277 };
278 let values = list_view(&v).ok_or_else(|| {
279 RuntimeError::Error("HttpResponse.headers values must be List<String>".to_string())
280 })?;
281 let mut buf = Vec::new();
282 for item in values.iter() {
283 match item {
284 Value::Str(s) => buf.push(AverStr::from(s.as_str())),
285 _ => {
286 return Err(RuntimeError::Error(
287 "HttpResponse.headers values must be Strings".to_string(),
288 ));
289 }
290 }
291 }
292 out = out.insert(AverStr::from(name), AverList::from_vec(buf));
293 }
294
295 Ok(out)
296}