Skip to main content

litellm_rs/server/
utils.rs

1//! HTTP server utility methods
2//!
3//! This module provides utility methods for the HttpServer.
4
5use crate::server::HttpServer;
6use crate::utils::error::gateway_error::GatewayError;
7use tracing::{info, warn};
8
9impl HttpServer {
10    /// Graceful shutdown signal handler
11    ///
12    /// Public API for external callers to use for graceful shutdown handling.
13    pub async fn shutdown_signal() {
14        let ctrl_c = async {
15            match tokio::signal::ctrl_c().await {
16                Ok(()) => info!("Received Ctrl+C signal, shutting down gracefully"),
17                Err(e) => warn!("Failed to install Ctrl+C handler: {}", e),
18            }
19        };
20
21        #[cfg(unix)]
22        let terminate = async {
23            match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) {
24                Ok(mut signal) => {
25                    signal.recv().await;
26                    info!("Received terminate signal, shutting down gracefully");
27                }
28                Err(e) => {
29                    warn!("Failed to install SIGTERM handler: {}", e);
30                    // Wait indefinitely if signal handler fails
31                    std::future::pending::<()>().await;
32                }
33            }
34        };
35
36        #[cfg(not(unix))]
37        let terminate = std::future::pending::<()>();
38
39        tokio::select! {
40            _ = ctrl_c => {},
41            _ = terminate => {},
42        }
43    }
44
45    /// Format a user-friendly error message for port binding failures
46    pub(crate) fn format_bind_error(
47        error: std::io::Error,
48        bind_addr: &str,
49        port: u16,
50    ) -> GatewayError {
51        let error_str = error.to_string();
52
53        // Check if it's an "address already in use" error
54        if error_str.contains("Address already in use")
55            || error_str.contains("os error 48")
56            || error_str.contains("os error 98")
57        {
58            let message = format!(
59                r#"
60┌─────────────────────────────────────────────────────────────────┐
61│  ❌ Error: Port {} is already in use
62├─────────────────────────────────────────────────────────────────┤
63│  Possible solutions:
64│
65│  1. Kill the existing process:
66│     lsof -ti:{} | xargs kill -9
67│
68│  2. Use a different port:
69│     --port {} or PORT={}
70│
71│  3. Check what's using it:
72│     lsof -i:{}
73└─────────────────────────────────────────────────────────────────┘
74"#,
75                port,
76                port,
77                port + 1,
78                port + 1,
79                port
80            );
81            GatewayError::server(message)
82        } else if error_str.contains("Permission denied") || error_str.contains("os error 13") {
83            let message = format!(
84                r#"
85┌─────────────────────────────────────────────────────────────────┐
86│  ❌ Error: Permission denied for port {}
87├─────────────────────────────────────────────────────────────────┤
88│  Possible solutions:
89│
90│  1. Use a port >= 1024 (non-privileged):
91│     --port 8000 or PORT=8000
92│
93│  2. Run with elevated privileges (not recommended):
94│     sudo ./gateway
95└─────────────────────────────────────────────────────────────────┘
96"#,
97                port
98            );
99            GatewayError::server(message)
100        } else {
101            GatewayError::server(format!("Failed to bind to {}: {}", bind_addr, error))
102        }
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use std::io::{Error, ErrorKind};
110
111    // ==================== format_bind_error Tests ====================
112
113    #[test]
114    fn test_format_bind_error_address_in_use() {
115        let error = Error::new(ErrorKind::AddrInUse, "Address already in use");
116        let result = HttpServer::format_bind_error(error, "0.0.0.0:8080", 8080);
117
118        let error_msg = result.to_string();
119        assert!(error_msg.contains("8080"));
120        assert!(error_msg.contains("already in use"));
121        assert!(error_msg.contains("8081")); // suggested alternative port
122    }
123
124    #[test]
125    fn test_format_bind_error_os_error_48() {
126        let error = Error::other("os error 48");
127        let result = HttpServer::format_bind_error(error, "0.0.0.0:3000", 3000);
128
129        let error_msg = result.to_string();
130        assert!(error_msg.contains("3000"));
131        assert!(error_msg.contains("3001")); // suggested alternative
132    }
133
134    #[test]
135    fn test_format_bind_error_os_error_98() {
136        let error = Error::other("os error 98");
137        let result = HttpServer::format_bind_error(error, "127.0.0.1:9000", 9000);
138
139        let error_msg = result.to_string();
140        assert!(error_msg.contains("9000"));
141    }
142
143    #[test]
144    fn test_format_bind_error_permission_denied() {
145        let error = Error::new(ErrorKind::PermissionDenied, "Permission denied");
146        let result = HttpServer::format_bind_error(error, "0.0.0.0:80", 80);
147
148        let error_msg = result.to_string();
149        assert!(error_msg.contains("80"));
150        assert!(error_msg.contains("Permission denied"));
151        assert!(error_msg.contains("1024")); // mentions non-privileged ports
152    }
153
154    #[test]
155    fn test_format_bind_error_os_error_13() {
156        let error = Error::other("os error 13");
157        let result = HttpServer::format_bind_error(error, "0.0.0.0:443", 443);
158
159        let error_msg = result.to_string();
160        assert!(error_msg.contains("443"));
161        assert!(error_msg.contains("Permission denied"));
162    }
163
164    #[test]
165    fn test_format_bind_error_generic() {
166        let error = Error::other("Network unreachable");
167        let result = HttpServer::format_bind_error(error, "192.168.1.1:8080", 8080);
168
169        let error_msg = result.to_string();
170        assert!(error_msg.contains("Failed to bind"));
171        assert!(error_msg.contains("192.168.1.1:8080"));
172        assert!(error_msg.contains("Network unreachable"));
173    }
174
175    #[test]
176    fn test_format_bind_error_suggested_port_8000() {
177        let error = Error::new(ErrorKind::AddrInUse, "Address already in use");
178        let result = HttpServer::format_bind_error(error, "0.0.0.0:8000", 8000);
179        assert!(result.to_string().contains("8001"));
180    }
181
182    #[test]
183    fn test_format_bind_error_suggested_port_3000() {
184        let error = Error::new(ErrorKind::AddrInUse, "Address already in use");
185        let result = HttpServer::format_bind_error(error, "0.0.0.0:3000", 3000);
186        assert!(result.to_string().contains("3001"));
187    }
188
189    #[test]
190    fn test_format_bind_error_contains_emoji() {
191        let error = Error::new(ErrorKind::AddrInUse, "Address already in use");
192        let result = HttpServer::format_bind_error(error, "0.0.0.0:8080", 8080);
193
194        let error_msg = result.to_string();
195        assert!(error_msg.contains("❌")); // Error emoji
196    }
197
198    #[test]
199    fn test_format_bind_error_contains_lsof_command() {
200        let error = Error::new(ErrorKind::AddrInUse, "Address already in use");
201        let result = HttpServer::format_bind_error(error, "0.0.0.0:8080", 8080);
202
203        let error_msg = result.to_string();
204        assert!(error_msg.contains("lsof")); // Lists useful command
205    }
206
207    #[test]
208    fn test_format_bind_error_privileged_port() {
209        let error = Error::new(ErrorKind::PermissionDenied, "Permission denied");
210        let result = HttpServer::format_bind_error(error, "0.0.0.0:22", 22);
211
212        let error_msg = result.to_string();
213        assert!(error_msg.contains("22"));
214        assert!(error_msg.contains("8000")); // suggests standard dev port
215    }
216
217    #[test]
218    fn test_format_bind_error_connection_refused() {
219        let error = Error::new(ErrorKind::ConnectionRefused, "Connection refused");
220        let result = HttpServer::format_bind_error(error, "localhost:8080", 8080);
221
222        let error_msg = result.to_string();
223        // Generic error should contain the original message
224        assert!(error_msg.contains("Connection refused"));
225    }
226
227    #[test]
228    fn test_format_bind_error_invalid_input() {
229        let error = Error::new(ErrorKind::InvalidInput, "Invalid address format");
230        let result = HttpServer::format_bind_error(error, "invalid:addr", 0);
231
232        let error_msg = result.to_string();
233        assert!(error_msg.contains("Failed to bind"));
234    }
235
236    // ==================== Error Type Tests ====================
237
238    #[test]
239    fn test_format_bind_error_returns_gateway_error() {
240        let error = Error::new(ErrorKind::AddrInUse, "Address already in use");
241        let result = HttpServer::format_bind_error(error, "0.0.0.0:8080", 8080);
242
243        // Should return a formatted error message string
244        let error_str = result.to_string();
245        assert!(!error_str.is_empty());
246        // The error message should contain port info
247        assert!(error_str.contains("8080"));
248    }
249}