1#![allow(clippy::result_large_err)]
21
22use crate::policy::Policy;
23use indexmap::IndexMap;
24use lex_bytecode::vm::Vm;
25use lex_bytecode::{Program, Value};
26use std::net::TcpListener;
27use std::sync::atomic::{AtomicU64, Ordering};
28use std::sync::mpsc;
29use std::sync::{Arc, Mutex};
30use std::thread;
31use std::time::Duration;
32
33struct Conn {
35 room: String,
36 outbound: mpsc::Sender<String>,
40}
41
42#[derive(Default)]
44pub struct ChatRegistry {
45 conns: Mutex<IndexMap<u64, Conn>>,
46}
47
48impl ChatRegistry {
49 fn register(&self, room: String, outbound: mpsc::Sender<String>) -> u64 {
50 static NEXT_ID: AtomicU64 = AtomicU64::new(1);
51 let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
52 self.conns.lock().unwrap().insert(id, Conn { room, outbound });
53 id
54 }
55 fn unregister(&self, id: u64) {
56 self.conns.lock().unwrap().shift_remove(&id);
57 }
58 fn broadcast(&self, room: &str, body: &str) {
59 let conns = self.conns.lock().unwrap();
60 for c in conns.values() {
61 if c.room == room {
62 let _ = c.outbound.send(body.to_string());
63 }
64 }
65 }
66 fn send_to(&self, id: u64, body: &str) -> bool {
67 if let Some(c) = self.conns.lock().unwrap().get(&id) {
68 let _ = c.outbound.send(body.to_string());
69 true
70 } else {
71 false
72 }
73 }
74}
75
76pub fn chat_broadcast(reg: &Arc<ChatRegistry>, room: &str, body: &str) {
79 reg.broadcast(room, body);
80}
81
82pub fn chat_send(reg: &Arc<ChatRegistry>, conn_id: u64, body: &str) -> bool {
83 reg.send_to(conn_id, body)
84}
85
86pub fn ws_bind_host() -> String {
106 std::env::var("LEX_WS_HOST")
107 .ok()
108 .filter(|h| !h.trim().is_empty())
109 .unwrap_or_else(|| "127.0.0.1".to_string())
110}
111
112pub fn serve_ws(
113 port: u16,
114 handler_name: String,
115 program: Arc<Program>,
116 policy: Policy,
117 registry: Arc<ChatRegistry>,
118) -> Result<Value, String> {
119 let host = ws_bind_host();
120 let listener = TcpListener::bind((host.as_str(), port))
121 .map_err(|e| format!("net.serve_ws bind {host}:{port}: {e}"))?;
122 eprintln!("net.serve_ws: listening on ws://{host}:{port}");
123 for stream in listener.incoming() {
124 let stream = match stream {
125 Ok(s) => s,
126 Err(e) => { eprintln!("net.serve_ws accept: {e}"); continue; }
127 };
128 let program = Arc::clone(&program);
129 let policy = policy.clone();
130 let handler_name = handler_name.clone();
131 let registry = Arc::clone(®istry);
132 thread::spawn(move || {
133 if let Err(e) = handle_connection(stream, program, policy, handler_name, registry) {
134 eprintln!("net.serve_ws connection error: {e}");
135 }
136 });
137 }
138 Ok(Value::Unit)
139}
140
141fn accept_or_drop_probe<Cb>(
154 result: Result<
155 tungstenite::WebSocket<std::net::TcpStream>,
156 tungstenite::HandshakeError<
157 tungstenite::handshake::server::ServerHandshake<std::net::TcpStream, Cb>,
158 >,
159 >,
160) -> Result<Option<tungstenite::WebSocket<std::net::TcpStream>>, String>
161where
162 Cb: tungstenite::handshake::server::Callback,
163{
164 match result {
165 Ok(ws) => Ok(Some(ws)),
166 Err(tungstenite::HandshakeError::Failure(tungstenite::Error::Protocol(
167 tungstenite::error::ProtocolError::HandshakeIncomplete,
168 ))) => Ok(None),
169 Err(e) => Err(format!("ws handshake: {e}")),
170 }
171}
172
173fn handle_connection(
174 stream: std::net::TcpStream,
175 program: Arc<Program>,
176 policy: Policy,
177 handler_name: String,
178 registry: Arc<ChatRegistry>,
179) -> Result<(), String> {
180 use tungstenite::{accept_hdr, handshake::server::{Request, Response}};
181
182 let mut path = String::new();
184 let path_ref = &mut path;
185 let accepted = accept_hdr(stream, |req: &Request, resp: Response| {
186 *path_ref = req.uri().path().to_string();
187 Ok(resp)
188 });
189 let mut ws = match accept_or_drop_probe(accepted)? {
190 Some(ws) => ws,
191 None => return Ok(()), };
193
194 let room = path.trim_start_matches('/').to_string();
195
196 let (tx, rx) = mpsc::channel::<String>();
199 let conn_id = registry.register(room.clone(), tx);
200
201 let _ = ws.get_mut().set_read_timeout(Some(Duration::from_millis(50)));
205
206 let result = run_loop(&mut ws, &rx, conn_id, &room, &program, &policy, &handler_name, ®istry);
207 registry.unregister(conn_id);
208 let _ = ws.close(None);
209 result
210}
211
212#[allow(clippy::too_many_arguments)]
213fn run_loop(
214 ws: &mut tungstenite::WebSocket<std::net::TcpStream>,
215 rx: &mpsc::Receiver<String>,
216 conn_id: u64,
217 room: &str,
218 program: &Arc<Program>,
219 policy: &Policy,
220 handler_name: &str,
221 registry: &Arc<ChatRegistry>,
222) -> Result<(), String> {
223 use tungstenite::Message;
224 use std::io::ErrorKind;
225 loop {
226 match ws.read() {
228 Ok(Message::Text(body)) => {
229 let ev = build_ws_event(conn_id, room, &body);
230 let handler = crate::handler::DefaultHandler::new(policy.clone())
231 .with_program(Arc::clone(program))
232 .with_chat_registry(Arc::clone(registry));
233 let mut vm = Vm::with_handler(program, Box::new(handler));
234 if let Err(e) = vm.call(handler_name, vec![ev]) {
235 eprintln!("on_message {conn_id}: {e}");
236 }
237 }
238 Ok(Message::Binary(_)) => { }
239 Ok(Message::Close(_)) | Err(tungstenite::Error::ConnectionClosed) => break,
240 Ok(_) => {} Err(tungstenite::Error::Io(ref e)) if e.kind() == ErrorKind::WouldBlock
242 || e.kind() == ErrorKind::TimedOut => {}
243 Err(e) => return Err(format!("ws read: {e}")),
244 }
245 loop {
247 match rx.try_recv() {
248 Ok(msg) => {
249 if let Err(e) = ws.send(Message::Text(msg.into())) {
250 return Err(format!("ws send: {e}"));
251 }
252 }
253 Err(mpsc::TryRecvError::Empty) => break,
254 Err(mpsc::TryRecvError::Disconnected) => return Ok(()),
255 }
256 }
257 }
258 Ok(())
259}
260
261fn build_ws_event(conn_id: u64, room: &str, body: &str) -> Value {
262 let mut rec = IndexMap::new();
263 rec.insert("body".into(), Value::Str(body.into()));
264 rec.insert("conn_id".into(), Value::Int(conn_id as i64));
265 rec.insert("room".into(), Value::Str(room.into()));
266 Value::record_dynamic(rec)
267}
268
269fn build_ws_conn(conn_id: u64, path: &str, subprotocol: &str) -> Value {
273 let mut rec = IndexMap::new();
274 rec.insert("id".into(), Value::Str(conn_id.to_string().into()));
275 rec.insert("path".into(), Value::Str(path.into()));
276 rec.insert("subprotocol".into(), Value::Str(subprotocol.into()));
277 Value::record_dynamic(rec)
278}
279
280fn build_ws_message_text(body: &str) -> Value {
282 Value::Variant { name: "WsText".into(), args: vec![Value::Str(body.into())] }
283}
284
285fn build_ws_message_close() -> Value {
286 Value::Variant { name: "WsClose".into(), args: vec![] }
287}
288
289fn build_ws_message_ping() -> Value {
290 Value::Variant { name: "WsPing".into(), args: vec![] }
291}
292
293fn build_ws_message_binary(payload: &[u8]) -> Value {
294 let bytes = payload.iter().map(|b| Value::Int(*b as i64)).collect();
295 Value::Variant { name: "WsBinary".into(), args: vec![Value::List(bytes)] }
296}
297
298fn apply_ws_action<S: std::io::Read + std::io::Write>(
303 action: &Value,
304 ws: &mut tungstenite::WebSocket<S>,
305) -> Result<(), String> {
306 use tungstenite::Message;
307 match action {
308 Value::Variant { name, args } if name == "WsSend" => {
309 let text = match args.first() {
310 Some(Value::Str(s)) => s.clone(),
311 _ => return Err("WsSend payload must be Str".into()),
312 };
313 ws.send(Message::Text(text.to_string().into()))
314 .map_err(|e| format!("ws send: {e}"))
315 }
316 Value::Variant { name, args } if name == "WsSendBinary" => {
317 let bytes: Vec<u8> = match args.first() {
318 Some(Value::List(elems)) => elems
319 .iter()
320 .map(|v| match v {
321 Value::Int(n) => Ok(*n as u8),
322 _ => Err("WsSendBinary payload must be List[Int]".into()),
323 })
324 .collect::<Result<Vec<_>, String>>()?,
325 _ => return Err("WsSendBinary payload must be List[Int]".into()),
326 };
327 ws.send(Message::Binary(bytes.into()))
328 .map_err(|e| format!("ws send binary: {e}"))
329 }
330 Value::Variant { name, .. } if name == "WsNoOp" => Ok(()),
331 other => Err(format!("unexpected WsAction: {other:?}")),
332 }
333}
334
335pub fn serve_ws_fn(
337 port: u16,
338 subprotocol: String,
339 closure: Value,
340 program: Arc<Program>,
341 policy: Policy,
342 registry: Arc<ChatRegistry>,
343) -> Result<Value, String> {
344 if !subprotocol.is_empty() {
349 if let Err(e) =
350 tungstenite::http::HeaderValue::from_str(&subprotocol)
351 {
352 return Err(format!(
353 "net.serve_ws_fn: subprotocol {subprotocol:?} is not a valid \
354 HTTP header value: {e}"
355 ));
356 }
357 }
358 let host = ws_bind_host();
359 let listener = TcpListener::bind((host.as_str(), port))
360 .map_err(|e| format!("net.serve_ws_fn bind {host}:{port}: {e}"))?;
361 eprintln!("net.serve_ws_fn: listening on ws://{host}:{port}");
362 for stream in listener.incoming() {
363 let stream = match stream {
364 Ok(s) => s,
365 Err(e) => { eprintln!("net.serve_ws_fn accept: {e}"); continue; }
366 };
367 let program = Arc::clone(&program);
368 let policy = policy.clone();
369 let closure = closure.clone();
370 let subprotocol = subprotocol.clone();
371 let registry = Arc::clone(®istry);
372 thread::spawn(move || {
373 if let Err(e) = handle_connection_fn(
374 stream, program, policy, closure, subprotocol, registry,
375 ) {
376 eprintln!("net.serve_ws_fn connection error: {e}");
377 }
378 });
379 }
380 Ok(Value::Unit)
381}
382
383fn handle_connection_fn(
384 stream: std::net::TcpStream,
385 program: Arc<Program>,
386 policy: Policy,
387 closure: Value,
388 subprotocol: String,
389 registry: Arc<ChatRegistry>,
390) -> Result<(), String> {
391 use tungstenite::{accept_hdr, handshake::server::{Request, Response}};
392
393 let mut path = String::new();
394 let path_ref = &mut path;
395 let subproto_for_handshake = subprotocol.clone();
396 let accepted = accept_hdr(stream, |req: &Request, mut resp: Response| {
397 *path_ref = req.uri().path().to_string();
398 maybe_echo_subprotocol(req, &mut resp, &subproto_for_handshake);
399 Ok(resp)
400 });
401 let mut ws = match accept_or_drop_probe(accepted)? {
402 Some(ws) => ws,
403 None => return Ok(()), };
405
406 let (tx, rx) = mpsc::channel::<String>();
407 let conn_id = registry.register(path.trim_start_matches('/').to_string(), tx);
408 let _ = ws.get_mut().set_read_timeout(Some(Duration::from_millis(50)));
409
410 let result = run_loop_fn(
411 &mut ws, &rx, conn_id, &path, &subprotocol,
412 &program, &policy, &closure, ®istry,
413 );
414 registry.unregister(conn_id);
415 let _ = ws.close(None);
416 result
417}
418
419fn maybe_echo_subprotocol(
429 req: &tungstenite::handshake::server::Request,
430 resp: &mut tungstenite::handshake::server::Response,
431 subprotocol: &str,
432) {
433 use tungstenite::http::HeaderValue;
434 if subprotocol.is_empty() {
435 return;
436 }
437 let offered = match req.headers().get("Sec-WebSocket-Protocol") {
438 Some(v) => v,
439 None => return,
440 };
441 let offered_str = match offered.to_str() {
442 Ok(s) => s,
443 Err(_) => return,
444 };
445 let matches = offered_str
446 .split(',')
447 .map(|p| p.trim())
448 .any(|p| p == subprotocol);
449 if !matches {
450 return;
451 }
452 if let Ok(h) = HeaderValue::from_str(subprotocol) {
456 resp.headers_mut().insert("Sec-WebSocket-Protocol", h);
457 }
458}
459
460pub fn serve_ws_fn_auth(
491 port: u16,
492 subprotocol: String,
493 auth_closure: Value,
494 handler_closure: Value,
495 program: Arc<Program>,
496 policy: Policy,
497 registry: Arc<ChatRegistry>,
498) -> Result<Value, String> {
499 if !subprotocol.is_empty() {
500 if let Err(e) =
501 tungstenite::http::HeaderValue::from_str(&subprotocol)
502 {
503 return Err(format!(
504 "net.serve_ws_fn_auth: subprotocol {subprotocol:?} is not a \
505 valid HTTP header value: {e}"
506 ));
507 }
508 }
509 let host = ws_bind_host();
510 let listener = TcpListener::bind((host.as_str(), port))
511 .map_err(|e| format!("net.serve_ws_fn_auth bind {host}:{port}: {e}"))?;
512 eprintln!("net.serve_ws_fn_auth: listening on ws://{host}:{port}");
513 for stream in listener.incoming() {
514 let stream = match stream {
515 Ok(s) => s,
516 Err(e) => {
517 eprintln!("net.serve_ws_fn_auth accept: {e}");
518 continue;
519 }
520 };
521 let program = Arc::clone(&program);
522 let policy = policy.clone();
523 let auth_closure = auth_closure.clone();
524 let handler_closure = handler_closure.clone();
525 let subprotocol = subprotocol.clone();
526 let registry = Arc::clone(®istry);
527 thread::spawn(move || {
528 if let Err(e) = handle_connection_fn_auth(
529 stream, program, policy, auth_closure, handler_closure,
530 subprotocol, registry,
531 ) {
532 eprintln!("net.serve_ws_fn_auth connection error: {e}");
533 }
534 });
535 }
536 Ok(Value::Unit)
537}
538
539#[allow(clippy::too_many_arguments)]
540fn handle_connection_fn_auth(
541 stream: std::net::TcpStream,
542 program: Arc<Program>,
543 policy: Policy,
544 auth_closure: Value,
545 handler_closure: Value,
546 subprotocol: String,
547 registry: Arc<ChatRegistry>,
548) -> Result<(), String> {
549 use tungstenite::{accept_hdr, handshake::server::{Request, Response}};
550 use tungstenite::http::StatusCode;
551
552 let mut path = String::new();
553 let path_ref = &mut path;
554 let subproto_for_handshake = subprotocol.clone();
555 let auth_program = Arc::clone(&program);
559 let auth_policy = policy.clone();
560 let auth_registry = Arc::clone(®istry);
561 let auth_closure_for_cb = auth_closure;
562
563 let accepted = accept_hdr(stream, move |req: &Request, mut resp: Response| {
564 *path_ref = req.uri().path().to_string();
565
566 let headers_value = build_headers_value(req);
567 let path_arg = Value::Str(path_ref.clone().into());
568
569 let dh = crate::handler::DefaultHandler::new(auth_policy.clone())
570 .with_program(Arc::clone(&auth_program))
571 .with_chat_registry(Arc::clone(&auth_registry));
572 let mut vm = Vm::with_handler(&auth_program, Box::new(dh));
573 let auth_result = vm.invoke_closure_value(
574 auth_closure_for_cb,
575 vec![path_arg, headers_value],
576 );
577
578 match auth_result {
579 Ok(Value::Variant { name, .. }) if name == "Ok" => {
580 maybe_echo_subprotocol(req, &mut resp, &subproto_for_handshake);
581 Ok(resp)
582 }
583 Ok(Value::Variant { name, args }) if name == "Err" => {
584 let msg = match args.first() {
585 Some(Value::Str(s)) => s.to_string(),
586 _ => "unauthorized".to_string(),
587 };
588 let err = build_unauthorized_response(StatusCode::UNAUTHORIZED, msg);
589 Err(err)
590 }
591 Ok(other) => {
592 let err = build_unauthorized_response(
593 StatusCode::INTERNAL_SERVER_ERROR,
594 format!(
595 "net.serve_ws_fn_auth: auth callback returned \
596 non-Result value: {other:?}"
597 ),
598 );
599 Err(err)
600 }
601 Err(e) => {
602 let err = build_unauthorized_response(
603 StatusCode::INTERNAL_SERVER_ERROR,
604 format!("net.serve_ws_fn_auth: auth callback error: {e:?}"),
605 );
606 Err(err)
607 }
608 }
609 });
610 let mut ws = match accept_or_drop_probe(accepted)? {
611 Some(ws) => ws,
612 None => return Ok(()), };
614
615 let (tx, rx) = mpsc::channel::<String>();
616 let conn_id = registry.register(path.trim_start_matches('/').to_string(), tx);
617 let _ = ws.get_mut().set_read_timeout(Some(Duration::from_millis(50)));
618
619 let result = run_loop_fn(
620 &mut ws, &rx, conn_id, &path, &subprotocol,
621 &program, &policy, &handler_closure, ®istry,
622 );
623 registry.unregister(conn_id);
624 let _ = ws.close(None);
625 result
626}
627
628fn build_headers_value(req: &tungstenite::handshake::server::Request) -> Value {
634 let mut items: std::collections::VecDeque<Value> = std::collections::VecDeque::new();
635 for (name, val) in req.headers().iter() {
636 let v = match val.to_str() {
637 Ok(s) => s.to_string(),
638 Err(_) => continue,
639 };
640 let mut rec = IndexMap::new();
641 rec.insert("name".into(), Value::Str(name.as_str().into()));
642 rec.insert("value".into(), Value::Str(v.into()));
643 items.push_back(Value::record_dynamic(rec));
644 }
645 Value::List(items.into())
646}
647
648fn build_unauthorized_response(
649 status: tungstenite::http::StatusCode,
650 msg: String,
651) -> tungstenite::handshake::server::ErrorResponse {
652 tungstenite::http::Response::builder()
653 .status(status)
654 .header("Content-Type", "text/plain; charset=utf-8")
655 .body(Some(msg))
656 .expect("ErrorResponse builder")
657}
658
659#[allow(clippy::too_many_arguments)]
660fn run_loop_fn(
661 ws: &mut tungstenite::WebSocket<std::net::TcpStream>,
662 rx: &mpsc::Receiver<String>,
663 conn_id: u64,
664 path: &str,
665 subprotocol: &str,
666 program: &Arc<Program>,
667 policy: &Policy,
668 closure: &Value,
669 registry: &Arc<ChatRegistry>,
670) -> Result<(), String> {
671 use tungstenite::Message;
672 use std::io::ErrorKind;
673
674 let ws_conn = build_ws_conn(conn_id, path, subprotocol);
675
676 loop {
677 let ws_msg = match ws.read() {
678 Ok(Message::Text(body)) => Some(build_ws_message_text(&body)),
679 Ok(Message::Binary(_)) => None,
680 Ok(Message::Ping(_)) => Some(build_ws_message_ping()),
681 Ok(Message::Close(_)) | Err(tungstenite::Error::ConnectionClosed) => {
682 let handler = crate::handler::DefaultHandler::new(policy.clone())
684 .with_program(Arc::clone(program))
685 .with_chat_registry(Arc::clone(registry));
686 let mut vm = Vm::with_handler(program, Box::new(handler));
687 let _ = vm.invoke_closure_value(
688 closure.clone(),
689 vec![ws_conn.clone(), build_ws_message_close()],
690 );
691 break;
692 }
693 Ok(_) => None, Err(tungstenite::Error::Io(ref e))
695 if e.kind() == ErrorKind::WouldBlock || e.kind() == ErrorKind::TimedOut => None,
696 Err(e) => return Err(format!("ws read: {e}")),
697 };
698
699 if let Some(msg) = ws_msg {
700 let handler = crate::handler::DefaultHandler::new(policy.clone())
701 .with_program(Arc::clone(program))
702 .with_chat_registry(Arc::clone(registry));
703 let mut vm = Vm::with_handler(program, Box::new(handler));
704 match vm.invoke_closure_value(closure.clone(), vec![ws_conn.clone(), msg]) {
705 Ok(action) => {
706 if let Err(e) = apply_ws_action(&action, ws) {
707 eprintln!("ws action {conn_id}: {e}");
708 }
709 }
710 Err(e) => eprintln!("ws handler {conn_id}: {e}"),
711 }
712 }
713
714 loop {
716 match rx.try_recv() {
717 Ok(msg) => {
718 if let Err(e) = ws.send(Message::Text(msg.into())) {
719 return Err(format!("ws send: {e}"));
720 }
721 }
722 Err(mpsc::TryRecvError::Empty) => break,
723 Err(mpsc::TryRecvError::Disconnected) => return Ok(()),
724 }
725 }
726 }
727 Ok(())
728}
729
730#[allow(clippy::too_many_arguments)]
769pub fn serve_ws_fn_actor(
770 port: u16,
771 subprotocol: String,
772 name_of_closure: Value,
773 on_message_closure: Value,
774 program: Arc<Program>,
775 policy: Policy,
776 registry: Arc<ChatRegistry>,
777) -> Result<Value, String> {
778 serve_ws_fn_actor_on(
779 ws_bind_host(),
780 port,
781 subprotocol,
782 name_of_closure,
783 on_message_closure,
784 program,
785 policy,
786 registry,
787 )
788}
789
790#[allow(clippy::too_many_arguments)]
798pub fn serve_ws_fn_actor_on(
799 host: String,
800 port: u16,
801 subprotocol: String,
802 name_of_closure: Value,
803 on_message_closure: Value,
804 program: Arc<Program>,
805 policy: Policy,
806 registry: Arc<ChatRegistry>,
807) -> Result<Value, String> {
808 if !subprotocol.is_empty() {
809 if let Err(e) =
810 tungstenite::http::HeaderValue::from_str(&subprotocol)
811 {
812 return Err(format!(
813 "net.serve_ws_fn_actor: subprotocol {subprotocol:?} is not a valid \
814 HTTP header value: {e}"
815 ));
816 }
817 }
818 let listener = TcpListener::bind((host.as_str(), port))
819 .map_err(|e| format!("net.serve_ws_fn_actor bind {host}:{port}: {e}"))?;
820 eprintln!("net.serve_ws_fn_actor: listening on ws://{host}:{port}");
821 for stream in listener.incoming() {
822 let stream = match stream {
823 Ok(s) => s,
824 Err(e) => { eprintln!("net.serve_ws_fn_actor accept: {e}"); continue; }
825 };
826 let program = Arc::clone(&program);
827 let policy = policy.clone();
828 let name_of_closure = name_of_closure.clone();
829 let on_message_closure = on_message_closure.clone();
830 let subprotocol = subprotocol.clone();
831 let registry = Arc::clone(®istry);
832 thread::spawn(move || {
833 if let Err(e) = handle_connection_fn_actor(
834 stream, program, policy, name_of_closure, on_message_closure,
835 subprotocol, registry,
836 ) {
837 eprintln!("net.serve_ws_fn_actor connection error: {e}");
838 }
839 });
840 }
841 Ok(Value::Unit)
842}
843
844#[allow(clippy::too_many_arguments)]
845fn handle_connection_fn_actor(
846 stream: std::net::TcpStream,
847 program: Arc<Program>,
848 policy: Policy,
849 name_of_closure: Value,
850 on_message_closure: Value,
851 subprotocol: String,
852 registry: Arc<ChatRegistry>,
853) -> Result<(), String> {
854 use tungstenite::{accept_hdr, handshake::server::{Request, Response}};
855
856 let mut path = String::new();
857 let path_ref = &mut path;
858 let subproto_for_handshake = subprotocol.clone();
859 let accepted = accept_hdr(stream, |req: &Request, mut resp: Response| {
860 *path_ref = req.uri().path().to_string();
861 maybe_echo_subprotocol(req, &mut resp, &subproto_for_handshake);
862 Ok(resp)
863 });
864 let mut ws = match accept_or_drop_probe(accepted)? {
865 Some(ws) => ws,
866 None => return Ok(()), };
868
869 let (tx, rx) = mpsc::channel::<String>();
870 let conn_id = registry.register(path.trim_start_matches('/').to_string(), tx.clone());
871 let _ = ws.get_mut().set_read_timeout(Some(Duration::from_millis(50)));
872
873 let ws_conn = build_ws_conn(conn_id, &path, &subprotocol);
882 let registered_name: Option<String> = {
883 let handler = crate::handler::DefaultHandler::new(policy.clone())
884 .with_program(Arc::clone(&program))
885 .with_chat_registry(Arc::clone(®istry));
886 let mut vm = Vm::with_handler(&program, Box::new(handler));
887 match vm.invoke_closure_value(name_of_closure.clone(), vec![ws_conn.clone()]) {
888 Ok(Value::Str(s)) if !s.is_empty() => Some(s.to_string()),
889 Ok(Value::Str(_)) => None,
890 Ok(other) => {
891 registry.unregister(conn_id);
892 let _ = ws.close(None);
893 return Err(format!(
894 "net.serve_ws_fn_actor: name_of must return Str, got {other:?}"
895 ));
896 }
897 Err(e) => {
898 registry.unregister(conn_id);
899 let _ = ws.close(None);
900 return Err(format!(
901 "net.serve_ws_fn_actor: name_of error: {e:?}"
902 ));
903 }
904 }
905 };
906
907 if let Some(ref name) = registered_name {
911 let tx_for_bridge = tx.clone();
912 let bridge = lex_bytecode::value::NativeActorHandler {
913 send: Box::new(move |msg: Value| -> Result<Value, String> {
914 match msg {
915 Value::Str(s) => {
916 tx_for_bridge.send(s.to_string()).map_err(|e| {
917 format!("net.serve_ws_fn_actor: outbound channel closed: {e}")
918 })?;
919 Ok(Value::Unit)
920 }
921 other => Err(format!(
922 "net.serve_ws_fn_actor: native bridge accepts Str messages only, got {other:?}"
923 )),
924 }
925 }),
926 };
927 let cell = Value::Actor(Arc::new(Mutex::new(lex_bytecode::value::ActorCell {
928 state: Value::Unit,
929 handler: lex_bytecode::value::ActorHandler::Native(Arc::new(bridge)),
930 })));
931 if let Err(e) = lex_bytecode::conc_registry::register(name, cell) {
932 registry.unregister(conn_id);
936 let _ = ws.close(None);
937 return Err(format!(
938 "net.serve_ws_fn_actor: conc.register({name:?}) failed: {e:?}"
939 ));
940 }
941 }
942
943 let result = run_loop_fn(
944 &mut ws, &rx, conn_id, &path, &subprotocol,
945 &program, &policy, &on_message_closure, ®istry,
946 );
947
948 if let Some(ref name) = registered_name {
951 let _ = lex_bytecode::conc_registry::unregister(name);
952 }
953 registry.unregister(conn_id);
954 let _ = ws.close(None);
955 result
956}
957
958fn build_dial_result(ok: Result<(), String>) -> Value {
983 match ok {
984 Ok(()) => Value::Variant {
985 name: "Ok".into(),
986 args: vec![Value::Unit],
987 },
988 Err(msg) => Value::Variant {
989 name: "Err".into(),
990 args: vec![Value::Str(msg.into())],
991 },
992 }
993}
994
995pub fn dial_ws(
1000 url: String,
1001 subprotocol: String,
1002 on_open: Value,
1003 on_message: Value,
1004 program: Arc<Program>,
1005 policy: Policy,
1006) -> Result<Value, String> {
1007 use tungstenite::client::IntoClientRequest;
1008 use tungstenite::http::HeaderValue;
1009
1010 let mut req = match url.as_str().into_client_request() {
1020 Ok(r) => r,
1021 Err(e) => {
1022 return Ok(build_dial_result(Err(format!(
1023 "net.dial_ws: bad URL `{url}`: {e}"
1024 ))));
1025 }
1026 };
1027 if !subprotocol.is_empty() {
1028 let header = match HeaderValue::from_str(&subprotocol) {
1029 Ok(h) => h,
1030 Err(e) => {
1031 return Ok(build_dial_result(Err(format!(
1032 "net.dial_ws: invalid subprotocol `{subprotocol}`: {e}"
1033 ))));
1034 }
1035 };
1036 req.headers_mut().insert("Sec-WebSocket-Protocol", header);
1037 }
1038
1039 let (mut ws, _resp) = match tungstenite::connect(req) {
1040 Ok(pair) => pair,
1041 Err(e) => {
1042 return Ok(build_dial_result(Err(format!(
1043 "net.dial_ws: connect to `{url}`: {e}"
1044 ))));
1045 }
1046 };
1047
1048 if let Some(stream) = stream_for(&mut ws) {
1051 let _ = stream.set_read_timeout(Some(Duration::from_millis(50)));
1052 }
1053
1054 {
1056 let handler = crate::handler::DefaultHandler::new(policy.clone())
1057 .with_program(Arc::clone(&program));
1058 let mut vm = Vm::with_handler(&program, Box::new(handler));
1059 match vm.invoke_closure_value(on_open.clone(), vec![]) {
1060 Ok(action) => {
1061 if let Err(e) = apply_ws_action(&action, &mut ws) {
1062 return Ok(build_dial_result(Err(format!(
1063 "net.dial_ws: on_open action: {e}"
1064 ))));
1065 }
1066 }
1067 Err(e) => {
1068 return Ok(build_dial_result(Err(format!(
1069 "net.dial_ws: on_open: {e}"
1070 ))));
1071 }
1072 }
1073 }
1074
1075 let loop_result = dial_run_loop(&mut ws, &on_message, &program, &policy);
1077 let _ = ws.close(None);
1078 Ok(build_dial_result(loop_result))
1079}
1080
1081fn stream_for(
1087 ws: &mut tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>,
1088) -> Option<&mut std::net::TcpStream> {
1089 use tungstenite::stream::MaybeTlsStream;
1090 match ws.get_mut() {
1091 MaybeTlsStream::Plain(s) => Some(s),
1092 MaybeTlsStream::Rustls(s) => Some(s.get_mut()),
1093 _ => None,
1094 }
1095}
1096
1097pub fn dial_ws_actor(
1112 url: String,
1113 subprotocol: String,
1114 name: String,
1115 on_open: Value,
1116 on_message: Value,
1117 program: Arc<Program>,
1118 policy: Policy,
1119) -> Result<Value, String> {
1120 use tungstenite::client::IntoClientRequest;
1121 use tungstenite::http::HeaderValue;
1122
1123 let mut req = match url.as_str().into_client_request() {
1124 Ok(r) => r,
1125 Err(e) => {
1126 return Ok(build_dial_result(Err(format!(
1127 "net.dial_ws_actor: bad URL `{url}`: {e}"
1128 ))));
1129 }
1130 };
1131 if !subprotocol.is_empty() {
1132 let header = match HeaderValue::from_str(&subprotocol) {
1133 Ok(h) => h,
1134 Err(e) => {
1135 return Ok(build_dial_result(Err(format!(
1136 "net.dial_ws_actor: invalid subprotocol `{subprotocol}`: {e}"
1137 ))));
1138 }
1139 };
1140 req.headers_mut().insert("Sec-WebSocket-Protocol", header);
1141 }
1142
1143 let (mut ws, _resp) = match tungstenite::connect(req) {
1144 Ok(pair) => pair,
1145 Err(e) => {
1146 return Ok(build_dial_result(Err(format!(
1147 "net.dial_ws_actor: connect to `{url}`: {e}"
1148 ))));
1149 }
1150 };
1151 if let Some(stream) = stream_for(&mut ws) {
1152 let _ = stream.set_read_timeout(Some(Duration::from_millis(50)));
1153 }
1154
1155 let (tx, rx) = mpsc::channel::<String>();
1158 if !name.is_empty() {
1159 let tx_for_bridge = tx.clone();
1160 let bridge = lex_bytecode::value::NativeActorHandler {
1161 send: Box::new(move |msg: Value| -> Result<Value, String> {
1162 match msg {
1163 Value::Str(s) => tx_for_bridge
1164 .send(s.to_string())
1165 .map(|_| Value::Unit)
1166 .map_err(|e| format!("net.dial_ws_actor: outbound channel closed: {e}")),
1167 other => Err(format!(
1168 "net.dial_ws_actor: native bridge accepts Str only, got {other:?}"
1169 )),
1170 }
1171 }),
1172 };
1173 let cell = Value::Actor(Arc::new(Mutex::new(lex_bytecode::value::ActorCell {
1174 state: Value::Unit,
1175 handler: lex_bytecode::value::ActorHandler::Native(Arc::new(bridge)),
1176 })));
1177 if let Err(e) = lex_bytecode::conc_registry::register(&name, cell) {
1178 return Ok(build_dial_result(Err(format!(
1179 "net.dial_ws_actor: conc.register({name:?}) failed: {e:?}"
1180 ))));
1181 }
1182 }
1183
1184 {
1186 let handler = crate::handler::DefaultHandler::new(policy.clone())
1187 .with_program(Arc::clone(&program));
1188 let mut vm = Vm::with_handler(&program, Box::new(handler));
1189 match vm.invoke_closure_value(on_open.clone(), vec![]) {
1190 Ok(action) => {
1191 if let Err(e) = apply_ws_action(&action, &mut ws) {
1192 if !name.is_empty() {
1193 let _ = lex_bytecode::conc_registry::unregister(&name);
1194 }
1195 return Ok(build_dial_result(Err(format!(
1196 "net.dial_ws_actor: on_open action: {e}"
1197 ))));
1198 }
1199 }
1200 Err(e) => {
1201 if !name.is_empty() {
1202 let _ = lex_bytecode::conc_registry::unregister(&name);
1203 }
1204 return Ok(build_dial_result(Err(format!(
1205 "net.dial_ws_actor: on_open: {e}"
1206 ))));
1207 }
1208 }
1209 }
1210
1211 let loop_result = dial_actor_run_loop(&mut ws, &rx, &on_message, &program, &policy);
1212 if !name.is_empty() {
1213 let _ = lex_bytecode::conc_registry::unregister(&name);
1214 }
1215 let _ = ws.close(None);
1216 Ok(build_dial_result(loop_result))
1217}
1218
1219fn dial_actor_run_loop(
1220 ws: &mut tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>,
1221 rx: &mpsc::Receiver<String>,
1222 on_message: &Value,
1223 program: &Arc<Program>,
1224 policy: &Policy,
1225) -> Result<(), String> {
1226 use std::io::ErrorKind;
1227 use tungstenite::Message;
1228
1229 loop {
1230 let ws_msg = match ws.read() {
1231 Ok(Message::Text(body)) => Some(build_ws_message_text(&body)),
1232 Ok(Message::Binary(payload)) => Some(build_ws_message_binary(&payload)),
1233 Ok(Message::Ping(_)) => Some(build_ws_message_ping()),
1234 Ok(Message::Close(_)) | Err(tungstenite::Error::ConnectionClosed) => {
1235 let handler = crate::handler::DefaultHandler::new(policy.clone())
1236 .with_program(Arc::clone(program));
1237 let mut vm = Vm::with_handler(program, Box::new(handler));
1238 let _ = vm.invoke_closure_value(
1239 on_message.clone(),
1240 vec![build_ws_message_close()],
1241 );
1242 return Ok(());
1243 }
1244 Ok(_) => None,
1245 Err(tungstenite::Error::Io(ref e))
1246 if e.kind() == ErrorKind::WouldBlock || e.kind() == ErrorKind::TimedOut =>
1247 {
1248 None
1249 }
1250 Err(e) => return Err(format!("net.dial_ws_actor: read: {e}")),
1251 };
1252
1253 if let Some(msg) = ws_msg {
1254 let handler = crate::handler::DefaultHandler::new(policy.clone())
1255 .with_program(Arc::clone(program));
1256 let mut vm = Vm::with_handler(program, Box::new(handler));
1257 match vm.invoke_closure_value(on_message.clone(), vec![msg]) {
1258 Ok(action) => {
1259 if let Err(e) = apply_ws_action(&action, ws) {
1260 return Err(format!("net.dial_ws_actor: action: {e}"));
1261 }
1262 }
1263 Err(e) => return Err(format!("net.dial_ws_actor: on_message: {e}")),
1264 }
1265 }
1266
1267 loop {
1269 match rx.try_recv() {
1270 Ok(msg) => {
1271 if let Err(e) = ws.send(Message::Text(msg.into())) {
1272 return Err(format!("net.dial_ws_actor: send: {e}"));
1273 }
1274 }
1275 Err(mpsc::TryRecvError::Empty) => break,
1276 Err(mpsc::TryRecvError::Disconnected) => return Ok(()),
1277 }
1278 }
1279 }
1280}
1281
1282fn dial_run_loop(
1283 ws: &mut tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>,
1284 on_message: &Value,
1285 program: &Arc<Program>,
1286 policy: &Policy,
1287) -> Result<(), String> {
1288 use std::io::ErrorKind;
1289 use tungstenite::Message;
1290
1291 loop {
1292 let ws_msg = match ws.read() {
1293 Ok(Message::Text(body)) => Some(build_ws_message_text(&body)),
1294 Ok(Message::Binary(payload)) => Some(build_ws_message_binary(&payload)),
1295 Ok(Message::Ping(_)) => Some(build_ws_message_ping()),
1296 Ok(Message::Close(_)) | Err(tungstenite::Error::ConnectionClosed) => {
1297 let handler = crate::handler::DefaultHandler::new(policy.clone())
1299 .with_program(Arc::clone(program));
1300 let mut vm = Vm::with_handler(program, Box::new(handler));
1301 let _ = vm.invoke_closure_value(
1302 on_message.clone(),
1303 vec![build_ws_message_close()],
1304 );
1305 return Ok(());
1306 }
1307 Ok(_) => None, Err(tungstenite::Error::Io(ref e))
1309 if e.kind() == ErrorKind::WouldBlock || e.kind() == ErrorKind::TimedOut =>
1310 {
1311 None
1312 }
1313 Err(e) => return Err(format!("net.dial_ws: read: {e}")),
1314 };
1315
1316 if let Some(msg) = ws_msg {
1317 let handler = crate::handler::DefaultHandler::new(policy.clone())
1318 .with_program(Arc::clone(program));
1319 let mut vm = Vm::with_handler(program, Box::new(handler));
1320 match vm.invoke_closure_value(on_message.clone(), vec![msg]) {
1321 Ok(action) => {
1322 if let Err(e) = apply_ws_action(&action, ws) {
1323 return Err(format!("net.dial_ws: action: {e}"));
1324 }
1325 }
1326 Err(e) => return Err(format!("net.dial_ws: on_message: {e}")),
1327 }
1328 }
1329 }
1330}
1331
1332#[cfg(test)]
1333mod tests {
1334 use super::accept_or_drop_probe;
1335 use tungstenite::handshake::server::{NoCallback, ServerHandshake};
1336 use tungstenite::{error::ProtocolError, Error, HandshakeError, WebSocket};
1337
1338 type ProbeResult = Result<
1339 WebSocket<std::net::TcpStream>,
1340 HandshakeError<ServerHandshake<std::net::TcpStream, NoCallback>>,
1341 >;
1342
1343 #[test]
1348 fn handshake_incomplete_is_dropped_as_probe() {
1349 let probe: ProbeResult = Err(HandshakeError::Failure(Error::Protocol(
1350 ProtocolError::HandshakeIncomplete,
1351 )));
1352 assert!(matches!(accept_or_drop_probe(probe), Ok(None)));
1353 }
1354
1355 #[test]
1358 fn genuine_handshake_failure_is_surfaced() {
1359 let failure: ProbeResult = Err(HandshakeError::Failure(Error::ConnectionClosed));
1360 assert!(accept_or_drop_probe(failure).is_err());
1361 }
1362}