liminal-server 0.25.4

Standalone server for the liminal messaging bus
Documentation
//! SRV-044 R1: the WebSocket address binds with its config alone, before
//! the services and the supervisor exist, and
//! [`WebSocketListener::accept_on`](super::WebSocketListener::accept_on)
//! starts the accept worker on it once they do.
//!
//! Between the two, the kernel completes a client's connect and holds it, with
//! its upgrade request, in the listen backlog; the upgrade is read once
//! accepting starts. Nothing is refused for starting and nothing waits on a
//! timer.
use std::net::{SocketAddr, TcpListener};
use std::time::Duration;

use super::AcceptorSettings;
use crate::ServerError;
use crate::config::types::WebSocketConfig;

#[cfg(test)]
#[path = "bound_tests.rs"]
pub(in crate::server) mod tests;

/// The configured WebSocket address, bound and not yet accepting.
#[derive(Debug)]
pub struct BoundWebSocket {
    pub(super) listener: TcpListener,
    pub(super) local_addr: SocketAddr,
    pub(super) settings: AcceptorSettings,
}

impl BoundWebSocket {
    /// Binds the configured WebSocket listen address. No thread starts and no
    /// supervisor is needed.
    ///
    /// # Errors
    /// Returns [`ServerError::ListenerBind`] when the configured address cannot
    /// bind, and [`ServerError::ListenerAccept`] when the bound address cannot
    /// be read or the build target cannot represent the pinned liminal frame
    /// bound.
    pub fn bind(config: &WebSocketConfig) -> Result<Self, ServerError> {
        let settings = AcceptorSettings {
            path: config.path.clone(),
            allowed_origins: config.allowed_origins.clone(),
            allowed_origin_ports: config.allowed_origin_ports.clone(),
            ping_interval: config.ping_interval_ms.map(Duration::from_millis),
            message_bound: super::liminal_ws_message_bound()?,
        };
        let listener = TcpListener::bind(config.listen_address).map_err(|source| {
            ServerError::ListenerBind {
                address: config.listen_address,
                source,
            }
        })?;
        // The listener stays BLOCKING (W4 leg 1): the accept worker kernel-parks
        // in `accept` with zero idle wakes; shutdown wakes it via the self-connect
        // interrupt.
        let local_addr = listener
            .local_addr()
            .map_err(|error| ServerError::ListenerAccept {
                message: format!("failed to inspect websocket listener address: {error}"),
            })?;
        tracing::info!(
            websocket_listen_address = %local_addr,
            upgrade_path = %settings.path,
            allowed_origins = settings.allowed_origins.len(),
            allowed_origin_ports = settings.allowed_origin_ports.len(),
            keepalive = ?settings.ping_interval,
            "liminal websocket listener bound"
        );
        Ok(Self {
            listener,
            local_addr,
            settings,
        })
    }

    /// The bound address, an ephemeral port resolved.
    #[must_use]
    pub const fn local_addr(&self) -> SocketAddr {
        self.local_addr
    }
}