nntp_proxy/session/
mod.rs

1//! Session management module
2//!
3//! Handles client sessions with different routing modes.
4//!
5//! This module handles the lifecycle of a client connection, including
6//! command processing, authentication interception, and data transfer.
7//!
8//! # Architecture Overview
9//!
10//! ## Three Operating Modes
11//!
12//! 1. **Standard (1:1) Mode** - `handle_with_pooled_backend()`
13//!    - One client maps to one backend connection for entire session
14//!    - Lowest latency, simplest model
15//!    - Used when routing_mode = Standard
16//!
17//! 2. **Per-Command Mode** - `handle_per_command_routing()`
18//!    - Each command is independently routed to potentially different backends
19//!    - Enables load balancing across multiple backend servers
20//!    - Rejects stateful commands (MODE READER, etc.)
21//!    - Used when routing_mode = PerCommand
22//!
23//! 3. **Hybrid Mode** - `handle_per_command_routing()` + dynamic switching
24//!    - Starts in per-command mode for load balancing
25//!    - Automatically switches to stateful mode when stateful command detected
26//!    - Best of both worlds: load balancing + stateful command support
27//!    - Used when routing_mode = Hybrid
28//!
29//! ## Key Functions
30//!
31//! - `execute_command_on_backend()` - **PERFORMANCE CRITICAL HOT PATH**
32//!   - Pipelined streaming with double-buffering for 100x+ throughput
33//!   - DO NOT refactor to buffer entire responses
34//!
35//! - `switch_to_stateful_mode()` - Hybrid mode transition
36//!   - Acquires dedicated backend connection
37//!   - Transitions from per-command to 1:1 mapping
38//!
39//! - `route_and_execute_command()` - Per-command orchestration
40//!   - Routes command to backend
41//!   - Handles connection pool management
42//!   - Distinguishes backend errors from client disconnects
43
44pub mod backend;
45pub mod connection;
46pub mod error_classification;
47pub mod handlers;
48pub mod streaming;
49
50use std::net::SocketAddr;
51use std::sync::Arc;
52
53use crate::auth::AuthHandler;
54use crate::config::RoutingMode;
55use crate::pool::BufferPool;
56use crate::router::BackendSelector;
57use crate::types::ClientId;
58
59/// Session mode for hybrid routing
60///
61/// In hybrid mode, sessions can dynamically transition between per-command
62/// and stateful modes. This allows load balancing for stateless commands
63/// while supporting stateful commands by switching to dedicated connections.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum SessionMode {
66    /// Per-command routing mode - each command can use a different backend
67    ///
68    /// Benefits:
69    /// - Load balancing across multiple backend servers
70    /// - Better resource utilization
71    /// - Fault tolerance (can route around failed backends)
72    ///
73    /// Limitations:
74    /// - Cannot support stateful commands (MODE READER, GROUP, etc.)
75    /// - Slightly higher latency (connection pool overhead)
76    PerCommand,
77
78    /// Stateful mode - using a dedicated backend connection
79    ///
80    /// Benefits:
81    /// - Lowest latency (no pool overhead)
82    /// - Supports stateful commands
83    /// - Simple 1:1 client-to-backend mapping
84    ///
85    /// Limitations:
86    /// - No load balancing (one backend per client)
87    /// - Less efficient resource usage
88    Stateful,
89}
90
91/// Represents an active client session
92pub struct ClientSession {
93    client_addr: SocketAddr,
94    buffer_pool: BufferPool,
95    /// Unique identifier for this client
96    client_id: ClientId,
97    /// Optional router for per-command routing mode
98    router: Option<Arc<BackendSelector>>,
99    /// Current session mode (for hybrid routing)
100    mode: SessionMode,
101    /// Routing mode configuration (Standard, PerCommand, or Hybrid)
102    routing_mode: RoutingMode,
103    /// Authentication handler
104    auth_handler: Arc<AuthHandler>,
105    /// Whether client has authenticated (starts false, set true after successful auth)
106    authenticated: std::sync::atomic::AtomicBool,
107}
108
109/// Builder for constructing `ClientSession` instances
110///
111/// Provides a fluent API for creating client sessions with different routing modes.
112///
113/// # Examples
114///
115/// ```
116/// use std::net::SocketAddr;
117/// use std::sync::Arc;
118/// use nntp_proxy::session::ClientSession;
119/// use nntp_proxy::pool::BufferPool;
120/// use nntp_proxy::router::BackendSelector;
121/// use nntp_proxy::config::RoutingMode;
122/// use nntp_proxy::types::BufferSize;
123/// use nntp_proxy::auth::AuthHandler;
124///
125/// let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
126/// let buffer_pool = BufferPool::new(BufferSize::DEFAULT, 10);
127/// let auth_handler = Arc::new(AuthHandler::new(None, None));
128///
129/// // Standard 1:1 routing mode
130/// let session = ClientSession::builder(addr, buffer_pool.clone(), auth_handler.clone())
131///     .build();
132///
133/// // Per-command routing mode
134/// let router = Arc::new(BackendSelector::new());
135/// let session = ClientSession::builder(addr, buffer_pool.clone(), auth_handler)
136///     .with_router(router)
137///     .with_routing_mode(RoutingMode::PerCommand)
138///     .build();
139/// ```
140pub struct ClientSessionBuilder {
141    client_addr: SocketAddr,
142    buffer_pool: BufferPool,
143    router: Option<Arc<BackendSelector>>,
144    routing_mode: RoutingMode,
145    auth_handler: Arc<AuthHandler>,
146}
147
148impl ClientSessionBuilder {
149    /// Configure the session to use per-command routing with a backend router
150    ///
151    /// When a router is provided, the session will route each command independently
152    /// to potentially different backend servers.
153    #[must_use]
154    pub fn with_router(mut self, router: Arc<BackendSelector>) -> Self {
155        self.router = Some(router);
156        self
157    }
158
159    /// Set the routing mode for this session
160    ///
161    /// # Arguments
162    /// * `mode` - The routing mode (Standard, PerCommand, or Hybrid)
163    ///
164    /// Note: If you use `with_router()`, you typically want PerCommand or Hybrid mode.
165    #[must_use]
166    pub fn with_routing_mode(mut self, mode: RoutingMode) -> Self {
167        self.routing_mode = mode;
168        self
169    }
170
171    /// Set the authentication handler
172    #[must_use]
173    pub fn with_auth_handler(mut self, auth_handler: Arc<AuthHandler>) -> Self {
174        self.auth_handler = auth_handler;
175        self
176    }
177
178    /// Build the client session
179    ///
180    /// Creates a new `ClientSession` with a unique client ID and the configured
181    /// routing mode.
182    #[must_use]
183    pub fn build(self) -> ClientSession {
184        let (mode, routing_mode) = match (&self.router, self.routing_mode) {
185            // If router is provided, start in per-command mode
186            (Some(_), RoutingMode::PerCommand | RoutingMode::Hybrid) => {
187                (SessionMode::PerCommand, self.routing_mode)
188            }
189            // If router is provided but mode is Standard, default to PerCommand
190            (Some(_), RoutingMode::Standard) => (SessionMode::PerCommand, RoutingMode::PerCommand),
191            // No router means Standard mode
192            (None, _) => (SessionMode::Stateful, RoutingMode::Standard),
193        };
194
195        ClientSession {
196            client_addr: self.client_addr,
197            buffer_pool: self.buffer_pool,
198            client_id: ClientId::new(),
199            router: self.router,
200            mode,
201            routing_mode,
202            auth_handler: self.auth_handler,
203            authenticated: std::sync::atomic::AtomicBool::new(false),
204        }
205    }
206}
207
208impl ClientSession {
209    /// Create a new client session for 1:1 backend mapping
210    #[must_use]
211    pub fn new(
212        client_addr: SocketAddr,
213        buffer_pool: BufferPool,
214        auth_handler: Arc<AuthHandler>,
215    ) -> Self {
216        Self {
217            client_addr,
218            buffer_pool,
219            client_id: ClientId::new(),
220            router: None,
221            mode: SessionMode::Stateful, // 1:1 mode is always stateful
222            routing_mode: RoutingMode::Standard,
223            auth_handler,
224            authenticated: std::sync::atomic::AtomicBool::new(false),
225        }
226    }
227
228    /// Create a new client session for per-command routing mode
229    #[must_use]
230    pub fn new_with_router(
231        client_addr: SocketAddr,
232        buffer_pool: BufferPool,
233        router: Arc<BackendSelector>,
234        routing_mode: RoutingMode,
235        auth_handler: Arc<AuthHandler>,
236    ) -> Self {
237        Self {
238            client_addr,
239            buffer_pool,
240            client_id: ClientId::new(),
241            router: Some(router),
242            mode: SessionMode::PerCommand, // Starts in per-command mode
243            routing_mode,
244            auth_handler,
245            authenticated: std::sync::atomic::AtomicBool::new(false),
246        }
247    }
248
249    /// Create a builder for constructing a client session
250    ///
251    /// # Examples
252    ///
253    /// ```
254    /// use std::net::SocketAddr;
255    /// use std::sync::Arc;
256    /// use nntp_proxy::session::ClientSession;
257    /// use nntp_proxy::pool::BufferPool;
258    /// use nntp_proxy::types::BufferSize;
259    /// use nntp_proxy::auth::AuthHandler;
260    ///
261    /// let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
262    /// let buffer_pool = BufferPool::new(BufferSize::DEFAULT, 10);
263    /// let auth_handler = Arc::new(AuthHandler::new(None, None));
264    ///
265    /// // Standard 1:1 routing mode
266    /// let session = ClientSession::builder(addr, buffer_pool.clone(), auth_handler)
267    ///     .build();
268    ///
269    /// assert!(!session.is_per_command_routing());
270    /// ```
271    #[must_use]
272    pub fn builder(
273        client_addr: SocketAddr,
274        buffer_pool: BufferPool,
275        auth_handler: Arc<AuthHandler>,
276    ) -> ClientSessionBuilder {
277        ClientSessionBuilder {
278            client_addr,
279            buffer_pool,
280            router: None,
281            routing_mode: RoutingMode::Standard,
282            auth_handler,
283        }
284    }
285
286    /// Get the unique client ID
287    #[must_use]
288    #[inline]
289    pub fn client_id(&self) -> ClientId {
290        self.client_id
291    }
292
293    /// Check if this session is using per-command routing
294    #[must_use]
295    #[inline]
296    pub fn is_per_command_routing(&self) -> bool {
297        self.router.is_some()
298    }
299
300    /// Get the current session mode
301    #[must_use]
302    #[inline]
303    pub fn mode(&self) -> SessionMode {
304        self.mode
305    }
306}
307
308#[cfg(test)]
309mod tests;