beam/adapters/
ws_server.rs1use crate::Config;
19use crate::actor::{Actor, ActorContext, Addr};
20use crate::adapters::ws_conn::WsConn;
21use crate::message::Message;
22
23use async_trait::async_trait;
24use std::collections::HashSet;
25use std::fs::File;
26use std::io::Read;
27use std::sync::Arc;
28use tokio::sync::RwLock;
29
30use futures_util::StreamExt;
31use log::{debug, info};
32use tokio::net::TcpListener;
33use tokio_native_tls::native_tls::Identity;
34
35use tokio_tungstenite::MaybeTlsStream;
36
37type Clients = Arc<RwLock<HashSet<Addr>>>;
39
40#[derive(Clone)]
42pub struct WsServerConfig {
43 pub port: u16,
45 pub cert_path: Option<String>,
47 pub key_path: Option<String>,
49}
50
51impl Default for WsServerConfig {
52 fn default() -> Self {
53 WsServerConfig {
54 port: 4944,
55 cert_path: None,
56 key_path: None,
57 }
58 }
59}
60
61#[derive(Clone)]
67pub struct WsServer {
68 config: Config,
69 ws_config: WsServerConfig,
70 clients: Clients,
71}
72
73impl WsServer {
74 pub fn new(config: Config) -> Self {
76 Self::new_with_config(config, WsServerConfig::default())
77 }
78
79 pub fn new_with_config(config: Config, ws_config: WsServerConfig) -> Self {
86 Self {
87 config,
88 ws_config,
89 clients: Clients::default(),
90 }
91 }
92
93 async fn handle_stream(
96 stream: MaybeTlsStream<tokio::net::TcpStream>,
97 ctx: &ActorContext,
98 clients: Clients,
99 allow_public_space: bool,
100 ) {
101 let ws_stream = match tokio_tungstenite::accept_async(stream).await {
102 Ok(s) => s,
103 Err(_e) => {
104 return;
107 }
108 };
109
110 let (sender, receiver) = ws_stream.split();
111
112 let conn = WsConn::new(sender, receiver, allow_public_space);
113 let addr = ctx.start_actor(Box::new(conn));
114 clients.write().await.insert(addr);
115 }
116
117 async fn start_web_server(config: WsServerConfig, peer_id: String) {
127 let port = config.port + 1;
128
129 if let Some(cert_path) = config.cert_path {
130 let key_path = config.key_path.unwrap();
131 let addr = format!("https://localhost:{}", port);
132 eprintln!("Web UI: {}", addr);
133
134 let cert = std::fs::read(cert_path).expect("failed to read cert file");
136 let key = std::fs::read(key_path).expect("failed to read key file");
137 let identity = tokio_native_tls::native_tls::Identity::from_pkcs8(&cert, &key)
138 .expect("failed to create TLS identity");
139 let acceptor = tokio_native_tls::TlsAcceptor::from(
140 tokio_native_tls::native_tls::TlsAcceptor::new(identity).unwrap(),
141 );
142
143 let listener = tokio::net::TcpListener::bind(("0.0.0.0", port))
144 .await
145 .expect("failed to bind web UI port");
146
147 loop {
148 let (stream, _) = match listener.accept().await {
149 Ok(s) => s,
150 Err(e) => {
151 log::error!("web UI accept error: {}", e);
152 continue;
153 }
154 };
155 let acceptor = acceptor.clone();
156 let peer_id = peer_id.clone();
157 tokio::spawn(async move {
158 let stream = match acceptor.accept(stream).await {
159 Ok(s) => s,
160 Err(_) => return,
161 };
162 Self::handle_peer_id_request(stream, &peer_id).await;
163 });
164 }
165 }
166
167 let addr = format!("http://localhost:{}", port);
169 eprintln!("Web UI: {}", addr);
170 use warp::Filter;
171 let peer_id_route = warp::path("peer_id".to_string()).map(move || peer_id.to_string());
172 let routes = warp::get().and(peer_id_route);
173 warp::serve(routes).run(([0, 0, 0, 0], port)).await;
174 }
175
176 async fn handle_peer_id_request(
182 mut stream: tokio_native_tls::TlsStream<tokio::net::TcpStream>,
183 peer_id: &str,
184 ) {
185 use tokio::io::{AsyncReadExt, AsyncWriteExt};
186
187 let mut buf = [0u8; 1024];
188 let n = match stream.read(&mut buf).await {
189 Ok(n) => n,
190 Err(_) => return,
191 };
192
193 let request = String::from_utf8_lossy(&buf[..n]);
195 let is_peer_id = request
196 .lines()
197 .next()
198 .is_some_and(|line| line.contains("GET /peer_id") || line.contains("GET /peer_id/"));
199
200 let response = if is_peer_id {
201 format!(
202 "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
203 peer_id.len(),
204 peer_id
205 )
206 } else {
207 "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\n".to_string()
208 };
209
210 let _ = stream.write_all(response.as_bytes()).await;
211 let _ = stream.shutdown().await;
212 }
213
214 pub fn peer_count(&self) -> usize {
230 if let Ok(count) = self.clients.try_read() {
234 count.len()
235 } else {
236 0
237 }
238 }
239}
240
241#[async_trait]
242impl Actor for WsServer {
243 async fn handle(&mut self, msg: Message, _ctx: &ActorContext) {
244 let client_count = self.clients.read().await.len();
245 eprintln!(
246 "[WSSERVER-DIAG] handle called with {} clients, msg type: {}",
247 client_count,
248 match &msg {
249 Message::Put(_) => "Put",
250 Message::Get(_) => "Get",
251 _ => "Other",
252 }
253 );
254 for conn in self.clients.read().await.iter() {
255 if msg.is_from(conn) {
256 eprintln!("[WSSERVER-DIAG] skipping msg.from == conn");
257 continue;
258 }
259 eprintln!("[WSSERVER-DIAG] relaying to WsConn client");
260 if conn.send(msg.clone()).is_err() {
261 eprintln!("[WSSERVER-DIAG] send failed, removing client");
262 self.clients.write().await.remove(conn);
263 }
264 }
265 }
266
267 async fn pre_start(&mut self, ctx: &ActorContext) {
268 let addr = format!("0.0.0.0:{}", self.ws_config.port).to_string();
269 let ctx = ctx.clone();
270
271 let peer_id = ctx.peer_id.read().clone();
272 let config_clone = self.ws_config.clone();
273 ctx.child_task(async move {
274 Self::start_web_server(config_clone, peer_id).await;
275 });
276
277 let try_socket = TcpListener::bind(&addr).await;
279 let listener = try_socket.expect("Failed to bind");
280 eprintln!("Websocket endpoint: ws://{}/ws", addr);
281
282 let allow_public_space = self.config.allow_public_space;
283 let clients = self.clients.clone();
284 if let Some(cert_path) = &self.ws_config.cert_path {
285 let mut cert_file = File::open(cert_path).unwrap();
286 let mut cert = vec![];
287 cert_file.read_to_end(&mut cert).unwrap();
288
289 let key_path = self.ws_config.key_path.as_ref().unwrap();
290 let mut key_file = File::open(key_path).unwrap();
291 let mut key = vec![];
292 key_file.read_to_end(&mut key).unwrap();
293
294 let identity = Identity::from_pkcs8(&cert, &key).unwrap();
295 let acceptor = tokio_native_tls::native_tls::TlsAcceptor::new(identity).unwrap();
296 let acceptor = tokio_native_tls::TlsAcceptor::from(acceptor);
297 let acceptor = Arc::new(acceptor);
298
299 let mut shutdown_rx = ctx.shutdown_rx.clone();
300 ctx.clone().child_task(async move {
301 loop {
302 tokio::select! {
303 biased;
304 _ = shutdown_rx.changed() => {
305 debug!("WsServer TLS accept loop shutting down");
306 break;
307 }
308 result = listener.accept() => {
309 if let Ok((stream, _)) = result {
310 let acceptor = acceptor.clone();
311 let clients = clients.clone();
312 let ctx = ctx.clone();
313 tokio::spawn(async move {
314 let stream = acceptor.accept(stream).await;
315 if let Ok(stream) = stream {
316 Self::handle_stream(
317 MaybeTlsStream::NativeTls(stream),
318 &ctx,
319 clients.clone(),
320 allow_public_space,
321 )
322 .await;
323 }
324 });
325 }
326 }
327 }
328 }
329 });
330 } else {
331 let mut shutdown_rx = ctx.shutdown_rx.clone();
332 ctx.clone().child_task(async move {
333 loop {
334 tokio::select! {
335 biased;
336 _ = shutdown_rx.changed() => {
337 debug!("WsServer plain accept loop shutting down");
338 break;
339 }
340 result = listener.accept() => {
341 if let Ok((stream, _)) = result {
342 Self::handle_stream(
343 MaybeTlsStream::Plain(stream),
344 &ctx,
345 clients.clone(),
346 allow_public_space,
347 )
348 .await;
349 }
350 }
351 }
352 }
353 });
354 }
355 }
356
357 fn subscribe_to_everything(&self) -> bool {
361 true
362 }
363
364 async fn stopping(&mut self, _context: &ActorContext) {
365 info!(
366 "WsServer stopping — closing {} client connections",
367 self.clients.read().await.len()
368 );
369 self.clients.write().await.clear();
374 }
375}