Skip to main content

litellm_rs/config/models/
server.rs

1//! Server configuration
2
3use super::*;
4use serde::{Deserialize, Serialize};
5use tracing::warn;
6
7/// Server configuration
8#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(deny_unknown_fields)]
10pub struct ServerConfig {
11    /// Server host. When a hostname resolves to multiple addresses, startup
12    /// binds the first address that succeeds and does not listen on the rest.
13    #[serde(default = "default_host")]
14    pub host: String,
15    /// Server port
16    #[serde(default = "default_port")]
17    pub port: u16,
18    /// Number of worker threads
19    pub workers: Option<usize>,
20    /// Maximum concurrent connections across the whole server. Actix applies
21    /// connection limits per worker, so startup derives a per-worker limit
22    /// of at least 2 that never exceeds this total (and may conservatively
23    /// round down). Small caps can reduce the effective worker count.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub max_connections: Option<usize>,
26    /// First request head timeout in seconds. Bounds how long a client may
27    /// take to send the initial request headers (408 afterwards); it does not
28    /// apply to later keep-alive requests or bound handler/stream duration.
29    #[serde(default = "default_timeout")]
30    pub timeout: u64,
31    /// Maximum JSON/form request body size in bytes. File and audio uploads
32    /// enforce their own multipart limits instead.
33    #[serde(default = "default_max_body_size")]
34    pub max_body_size: usize,
35    /// Enable development mode
36    #[serde(default)]
37    pub dev_mode: bool,
38    /// TLS configuration
39    pub tls: Option<TlsConfig>,
40    /// CORS configuration
41    #[serde(default)]
42    pub cors: CorsConfig,
43    /// Enabled features
44    #[serde(default)]
45    pub features: Vec<String>,
46    /// Trusted proxy IP addresses for X-Forwarded-For validation.
47    /// Only requests from these IPs will have their X-Forwarded-For header trusted.
48    #[serde(default)]
49    pub trusted_proxies: Vec<String>,
50    /// Idle timeout for SSE streaming connections in seconds.
51    /// If no chunk arrives within this duration, the stream is closed with a timeout error.
52    /// Default: 300 seconds (5 minutes). Set to 0 to disable.
53    #[serde(default = "default_stream_idle_timeout")]
54    pub stream_idle_timeout: u64,
55}
56
57fn default_stream_idle_timeout() -> u64 {
58    300 // 5 minutes
59}
60
61impl Default for ServerConfig {
62    fn default() -> Self {
63        Self {
64            host: default_host(),
65            port: default_port(),
66            workers: None,
67            max_connections: None,
68            timeout: default_timeout(),
69            max_body_size: default_max_body_size(),
70            dev_mode: false,
71            tls: None,
72            cors: CorsConfig::default(),
73            features: Vec::new(),
74            trusted_proxies: Vec::new(),
75            stream_idle_timeout: default_stream_idle_timeout(),
76        }
77    }
78}
79
80impl ServerConfig {
81    /// Merge server configurations
82    pub fn merge(mut self, other: Self) -> Self {
83        if other.host != default_host() {
84            self.host = other.host;
85        }
86        if other.port != default_port() {
87            self.port = other.port;
88        }
89        if other.workers.is_some() {
90            self.workers = other.workers;
91        }
92        if other.max_connections.is_some() {
93            self.max_connections = other.max_connections;
94        }
95        if other.timeout != default_timeout() {
96            self.timeout = other.timeout;
97        }
98        if other.max_body_size != default_max_body_size() {
99            self.max_body_size = other.max_body_size;
100        }
101        if other.dev_mode {
102            self.dev_mode = other.dev_mode;
103        }
104        if other.tls.is_some() {
105            self.tls = other.tls;
106        }
107        self.cors = self.cors.merge(other.cors);
108        if !other.features.is_empty() {
109            self.features = other.features;
110        }
111        if !other.trusted_proxies.is_empty() {
112            self.trusted_proxies = other.trusted_proxies;
113        }
114        if other.stream_idle_timeout != default_stream_idle_timeout() {
115            self.stream_idle_timeout = other.stream_idle_timeout;
116        }
117        self
118    }
119
120    /// Get the server address
121    pub fn address(&self) -> String {
122        format!("{}:{}", self.host, self.port)
123    }
124
125    /// Check if TLS is enabled
126    pub fn is_tls_enabled(&self) -> bool {
127        self.tls.is_some()
128    }
129
130    /// Get the number of workers (defaults to CPU count)
131    pub fn worker_count(&self) -> usize {
132        self.workers
133            .unwrap_or_else(|| std::thread::available_parallelism().map_or(1, |n| n.get()))
134    }
135
136    /// Validate server configuration
137    pub fn validate(&self) -> Result<(), String> {
138        if self.port == 0 {
139            return Err("Port must be between 1 and 65535".to_string());
140        }
141
142        if self.timeout == 0 {
143            return Err("Timeout cannot be 0".to_string());
144        }
145
146        if self.max_body_size == 0 {
147            return Err("Max body size cannot be 0".to_string());
148        }
149
150        if self.max_connections.is_some_and(|limit| limit < 2) {
151            return Err("server.max_connections must be at least 2".to_string());
152        }
153
154        if let Some(tls) = &self.tls {
155            tls.validate()?;
156        }
157
158        Ok(())
159    }
160}
161
162/// TLS configuration
163#[derive(Debug, Clone, Serialize, Deserialize)]
164#[serde(deny_unknown_fields)]
165pub struct TlsConfig {
166    /// Certificate file path
167    pub cert_file: String,
168    /// Private key file path
169    pub key_file: String,
170    /// CA certificate file path (optional)
171    pub ca_file: Option<String>,
172    /// Require client certificates
173    #[serde(default)]
174    pub require_client_cert: bool,
175    /// Request HTTP/2 TLS (currently unsupported)
176    #[serde(default)]
177    pub http2: bool,
178}
179
180impl TlsConfig {
181    /// Validate TLS configuration
182    pub fn validate(&self) -> Result<(), String> {
183        if self.cert_file.is_empty() {
184            return Err("TLS certificate file path is required".to_string());
185        }
186
187        if self.key_file.is_empty() {
188            return Err("TLS private key file path is required".to_string());
189        }
190
191        if self.ca_file.is_some() {
192            return Err(
193                "tls.ca_file is unsupported until client certificate auth is implemented"
194                    .to_string(),
195            );
196        }
197
198        if self.require_client_cert {
199            return Err(
200                "tls.require_client_cert is not implemented yet; set it to false".to_string(),
201            );
202        }
203
204        if self.http2 {
205            return Err("tls.http2 is unsupported; set it to false to use HTTP/1 TLS".to_string());
206        }
207
208        Ok(())
209    }
210}
211
212/// CORS configuration
213#[derive(Debug, Clone, Serialize, Deserialize)]
214#[serde(deny_unknown_fields)]
215pub struct CorsConfig {
216    /// Enable CORS
217    #[serde(default = "default_true")]
218    pub enabled: bool,
219    /// Allowed origins (`*` means allow all; empty means no cross-origin origin is allowed)
220    #[serde(default)]
221    pub allowed_origins: Vec<String>,
222    /// Allowed methods
223    #[serde(default = "default_cors_methods")]
224    pub allowed_methods: Vec<String>,
225    /// Allowed headers
226    #[serde(default = "default_cors_headers")]
227    pub allowed_headers: Vec<String>,
228    /// Max age for preflight requests
229    #[serde(default = "default_cors_max_age")]
230    pub max_age: u32,
231    /// Allow credentials
232    #[serde(default)]
233    pub allow_credentials: bool,
234}
235
236impl Default for CorsConfig {
237    fn default() -> Self {
238        Self {
239            enabled: true,
240            allowed_origins: vec![], // Restrictive by default: no cross-origin origin allowed
241            allowed_methods: default_cors_methods(),
242            allowed_headers: default_cors_headers(),
243            max_age: default_cors_max_age(),
244            allow_credentials: false,
245        }
246    }
247}
248
249impl CorsConfig {
250    /// Merge CORS configurations
251    pub fn merge(mut self, other: Self) -> Self {
252        if !other.enabled {
253            self.enabled = other.enabled;
254        }
255        if !other.allowed_origins.is_empty() {
256            self.allowed_origins = other.allowed_origins;
257        }
258        if other.allowed_methods != default_cors_methods() {
259            self.allowed_methods = other.allowed_methods;
260        }
261        if other.allowed_headers != default_cors_headers() {
262            self.allowed_headers = other.allowed_headers;
263        }
264        if other.max_age != default_cors_max_age() {
265            self.max_age = other.max_age;
266        }
267        if other.allow_credentials {
268            self.allow_credentials = other.allow_credentials;
269        }
270        self
271    }
272
273    /// Check if CORS allows all origins (insecure)
274    pub fn allows_all_origins(&self) -> bool {
275        self.allowed_origins.iter().any(|origin| origin == "*")
276    }
277
278    /// Validate CORS configuration
279    pub fn validate(&self) -> Result<(), String> {
280        if self.enabled {
281            if self.allows_all_origins() && self.allow_credentials {
282                return Err("CORS cannot allow all origins (*) when credentials are enabled for security reasons".to_string());
283            }
284
285            // Warn about insecure configurations
286            if self.allows_all_origins() {
287                warn!("CORS allows all origins. This may be insecure for production.");
288            }
289        }
290        Ok(())
291    }
292}
293
294fn default_cors_methods() -> Vec<String> {
295    vec![
296        "GET".to_string(),
297        "POST".to_string(),
298        "PUT".to_string(),
299        "DELETE".to_string(),
300        "OPTIONS".to_string(),
301    ]
302}
303
304fn default_cors_headers() -> Vec<String> {
305    vec![
306        "authorization".to_string(),
307        "content-type".to_string(),
308        "x-requested-with".to_string(),
309    ]
310}
311
312fn default_cors_max_age() -> u32 {
313    3600
314}
315
316#[cfg(test)]
317#[path = "server_tests.rs"]
318mod tests;