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;