use std::net::TcpListener as StdListener;
use std::sync::mpsc;
use tokio::net::TcpListener;
use tokio::sync::broadcast;
use tokio_tungstenite::tungstenite::handshake::server::{
ErrorResponse, Request as HandshakeRequest, Response as HandshakeResponse,
};
use tokio_tungstenite::tungstenite::http::StatusCode;
use super::options::Options;
use super::origin::is_allowed;
use super::page::{Kind, classify, serve_page};
use super::session::serve_client;
use super::{MAX_CLIENTS, MAX_FRAME_BYTES, MAX_MESSAGE_BYTES, Outbound, Request};
pub(super) async fn serve(
listener: StdListener,
outbound: broadcast::Sender<Outbound>,
commands: mpsc::Sender<Request>,
options: Options,
) {
let Ok(listener) = TcpListener::from_std(listener) else {
return;
};
let served_from = options
.page
.as_ref()
.and_then(|_| listener.local_addr().ok());
let Options {
page,
allowed_origins,
} = options;
let mut next_client = 0_u64;
while let Ok((stream, _)) = listener.accept().await {
if outbound.receiver_count() >= MAX_CLIENTS {
drop(stream);
continue;
}
if let Some(body) = page.clone() {
if classify(&stream).await == Kind::Page {
tokio::spawn(async move { serve_page(stream, &body).await });
continue;
}
}
next_client += 1;
let client = next_client;
let outbound_for_client = outbound.clone();
let commands = commands.clone();
let allowed_origins = allowed_origins.clone();
tokio::spawn(async move {
let config = tokio_tungstenite::tungstenite::protocol::WebSocketConfig::default()
.max_message_size(Some(MAX_MESSAGE_BYTES))
.max_frame_size(Some(MAX_FRAME_BYTES));
#[allow(
clippy::result_large_err,
reason = "the callback signature fixes the error type"
)]
let screen = |request: &HandshakeRequest, response: HandshakeResponse| {
let origin = request
.headers()
.get("Origin")
.and_then(|value| value.to_str().ok());
if is_allowed(origin, &allowed_origins, served_from) {
return Ok(response);
}
let mut refusal = ErrorResponse::new(None);
*refusal.status_mut() = StatusCode::FORBIDDEN;
Err(refusal)
};
if let Ok(websocket) =
tokio_tungstenite::accept_hdr_async_with_config(stream, screen, Some(config)).await
{
let subscription = outbound_for_client.subscribe();
serve_client(websocket, client, subscription, commands).await;
}
});
}
}