1use crate::Config;
19use crate::actor::{Actor, ActorContext, Addr};
20use crate::adapters::ws_conn::WsConn;
21use crate::message::Message;
22use crate::metrics::Metrics;
23
24use async_trait::async_trait;
25use std::collections::HashSet;
26use std::fs::File;
27use std::io::Read;
28use std::sync::Arc;
29use tokio::sync::RwLock;
30
31use log::{debug, info};
32use tokio::net::TcpListener;
33use tokio_native_tls::native_tls::Identity;
34use tokio_websockets::ServerBuilder;
35
36type Clients = Arc<RwLock<HashSet<Addr>>>;
38
39#[derive(Clone)]
41pub struct WsServerConfig {
42 pub port: u16,
44 pub cert_path: Option<String>,
46 pub key_path: Option<String>,
48}
49
50impl Default for WsServerConfig {
51 fn default() -> Self {
52 WsServerConfig {
53 port: 4944,
54 cert_path: None,
55 key_path: None,
56 }
57 }
58}
59
60#[derive(Clone)]
66pub struct WsServer {
67 config: Config,
68 ws_config: WsServerConfig,
69 clients: Clients,
70}
71
72impl WsServer {
73 pub fn new(config: Config) -> Self {
75 Self::new_with_config(config, WsServerConfig::default())
76 }
77
78 pub fn new_with_config(config: Config, ws_config: WsServerConfig) -> Self {
85 Self {
86 config,
87 ws_config,
88 clients: Clients::default(),
89 }
90 }
91
92 async fn handle_stream<S>(
100 stream: S,
101 ctx: &ActorContext,
102 clients: Clients,
103 allow_public_space: bool,
104 ) where
105 S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
106 {
107 let ws_config = tokio_websockets::Config::default().flush_threshold(usize::MAX);
115 let ws_stream = match ServerBuilder::new().config(ws_config).accept(stream).await {
116 Ok((_req, s)) => s,
117 Err(_e) => {
118 return;
121 }
122 };
123
124 let conn = WsConn::new(ws_stream, allow_public_space);
125 let addr = ctx.start_actor(Box::new(conn));
126 clients.write().await.insert(addr);
127 }
128
129 async fn start_web_server(config: WsServerConfig, peer_id: String, metrics: Arc<Metrics>) {
139 let port = config.port + 1;
140
141 if let Some(cert_path) = config.cert_path {
142 let key_path = config.key_path.unwrap();
143 let _addr = format!("https://localhost:{}", port);
144
145 let cert = std::fs::read(cert_path).expect("failed to read cert file");
147 let key = std::fs::read(key_path).expect("failed to read key file");
148 let identity = tokio_native_tls::native_tls::Identity::from_pkcs8(&cert, &key)
149 .expect("failed to create TLS identity");
150 let acceptor = tokio_native_tls::TlsAcceptor::from(
151 tokio_native_tls::native_tls::TlsAcceptor::new(identity).unwrap(),
152 );
153
154 let listener = tokio::net::TcpListener::bind(("0.0.0.0", port))
155 .await
156 .expect("failed to bind web UI port");
157
158 loop {
159 let (stream, _) = match listener.accept().await {
160 Ok(s) => s,
161 Err(e) => {
162 log::error!("web UI accept error: {}", e);
163 continue;
164 }
165 };
166 let acceptor = acceptor.clone();
167 let peer_id = peer_id.clone();
168 let metrics_clone = metrics.clone();
169 crate::tokio_spawn::spawn(async move {
170 let stream = match acceptor.accept(stream).await {
171 Ok(s) => s,
172 Err(_) => return,
173 };
174 Self::handle_http_request(stream, &peer_id, &metrics_clone).await;
175 });
176 }
177 }
178
179 let _addr = format!("http://localhost:{}", port);
181 let listener = tokio::net::TcpListener::bind(("0.0.0.0", port))
182 .await
183 .expect("failed to bind web UI port");
184 loop {
185 let (stream, _) = match listener.accept().await {
186 Ok(s) => s,
187 Err(e) => {
188 log::error!("web UI accept error: {}", e);
189 continue;
190 }
191 };
192 let peer_id = peer_id.clone();
193 let metrics_clone = metrics.clone();
194 crate::tokio_spawn::spawn(async move {
195 Self::handle_http_request_plain(stream, &peer_id, &metrics_clone).await;
196 });
197 }
198 }
199
200 fn build_http_response(request: &str, peer_id: &str, metrics: &Metrics) -> Option<String> {
211 let request_line = request.lines().next().unwrap_or("");
212
213 if request_line.contains("GET /peer_id") {
214 Some(format!(
215 "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
216 peer_id.len(),
217 peer_id
218 ))
219 } else if request_line.contains("GET /metrics") {
220 let body = serde_json::to_string_pretty(&metrics.snapshot())
221 .unwrap_or_else(|_| "{}".to_string());
222 Some(format!(
223 "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
224 body.len(),
225 body
226 ))
227 } else {
228 None
229 }
230 }
231
232 async fn handle_http_request(
234 mut stream: tokio_native_tls::TlsStream<tokio::net::TcpStream>,
235 peer_id: &str,
236 metrics: &Metrics,
237 ) {
238 use tokio::io::{AsyncReadExt, AsyncWriteExt};
239
240 let mut buf = [0u8; 1024];
241 let n = match stream.read(&mut buf).await {
242 Ok(n) => n,
243 Err(_) => return,
244 };
245
246 let request = String::from_utf8_lossy(&buf[..n]);
247 let response = match Self::build_http_response(&request, peer_id, metrics) {
248 Some(r) => r,
249 None => "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
250 .to_string(),
251 };
252
253 let _ = stream.write_all(response.as_bytes()).await;
254 let _ = stream.shutdown().await;
255 }
256
257 async fn handle_http_request_plain(
259 mut stream: tokio::net::TcpStream,
260 peer_id: &str,
261 metrics: &Metrics,
262 ) {
263 use tokio::io::{AsyncReadExt, AsyncWriteExt};
264
265 let mut buf = [0u8; 1024];
266 let n = match stream.read(&mut buf).await {
267 Ok(n) => n,
268 Err(_) => return,
269 };
270
271 let request = String::from_utf8_lossy(&buf[..n]);
272 let response = match Self::build_http_response(&request, peer_id, metrics) {
273 Some(r) => r,
274 None => "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
275 .to_string(),
276 };
277
278 let _ = stream.write_all(response.as_bytes()).await;
279 let _ = stream.shutdown().await;
280 }
281
282 pub fn peer_count(&self) -> usize {
298 if let Ok(count) = self.clients.try_read() {
302 count.len()
303 } else {
304 0
305 }
306 }
307}
308
309#[async_trait]
310impl Actor for WsServer {
311 async fn handle(&mut self, msg: Arc<Message>, _ctx: &ActorContext) {
317 match &*msg {
322 Message::Put(_)
323 | Message::Get(_)
324 | Message::BatchPut(_)
325 | Message::Hi { .. }
326 | Message::Flush(_)
327 | Message::RtcSignal(_) => {}
328 Message::CheckQuorumTimeouts | Message::RegisterQuorum { .. } => return,
329 }
330
331 for conn in self.clients.read().await.iter() {
332 if msg.is_from(conn) {
333 continue;
334 }
335 if conn.send((*msg).clone()).is_err() {
336 self.clients.write().await.remove(conn);
337 }
338 }
339 }
340
341 async fn pre_start(&mut self, ctx: &ActorContext) {
342 let addr = format!("0.0.0.0:{}", self.ws_config.port).to_string();
343 let ctx = ctx.clone();
344
345 let peer_id = ctx.peer_id.read().clone();
346 let config_clone = self.ws_config.clone();
347 let metrics = ctx.metrics.clone();
348 ctx.child_task(async move {
349 Self::start_web_server(config_clone, peer_id, metrics).await;
350 });
351
352 let try_socket = TcpListener::bind(&addr).await;
354 let listener = try_socket.expect("Failed to bind");
355
356 let allow_public_space = self.config.allow_public_space;
357 let clients = self.clients.clone();
358 if let Some(cert_path) = &self.ws_config.cert_path {
359 let mut cert_file = File::open(cert_path).unwrap();
360 let mut cert = vec![];
361 cert_file.read_to_end(&mut cert).unwrap();
362
363 let key_path = self.ws_config.key_path.as_ref().unwrap();
364 let mut key_file = File::open(key_path).unwrap();
365 let mut key = vec![];
366 key_file.read_to_end(&mut key).unwrap();
367
368 let identity = Identity::from_pkcs8(&cert, &key).unwrap();
369 let acceptor = tokio_native_tls::native_tls::TlsAcceptor::new(identity).unwrap();
370 let acceptor = tokio_native_tls::TlsAcceptor::from(acceptor);
371 let acceptor = Arc::new(acceptor);
372
373 let mut shutdown_rx = ctx.shutdown_rx.clone();
374 ctx.clone().child_task(async move {
375 loop {
376 tokio::select! {
377 biased;
378 _ = shutdown_rx.changed() => {
379 debug!("WsServer TLS accept loop shutting down");
380 break;
381 }
382 result = listener.accept() => {
383 if let Ok((stream, _)) = result {
384 let acceptor = acceptor.clone();
385 let clients = clients.clone();
386 let ctx = ctx.clone();
387 crate::tokio_spawn::spawn(async move {
388 let stream = acceptor.accept(stream).await;
389 if let Ok(stream) = stream {
390 Self::handle_stream(
391 stream,
392 &ctx,
393 clients.clone(),
394 allow_public_space,
395 )
396 .await;
397 }
398 });
399 }
400 }
401 }
402 }
403 });
404 } else {
405 let mut shutdown_rx = ctx.shutdown_rx.clone();
406 ctx.clone().child_task(async move {
407 loop {
408 tokio::select! {
409 biased;
410 _ = shutdown_rx.changed() => {
411 debug!("WsServer plain accept loop shutting down");
412 break;
413 }
414 result = listener.accept() => {
415 if let Ok((stream, _)) = result {
416 Self::handle_stream(
417 stream,
418 &ctx,
419 clients.clone(),
420 allow_public_space,
421 )
422 .await;
423 }
424 }
425 }
426 }
427 });
428 }
429 }
430
431 fn subscribe_to_everything(&self) -> bool {
435 true
436 }
437
438 async fn stopping(&mut self, _context: &ActorContext) {
439 info!(
440 "WsServer stopping — closing {} client connections",
441 self.clients.read().await.len()
442 );
443 self.clients.write().await.clear();
448 }
449}