bws_web_server/handlers/
api_handler.rs

1use crate::config::{ServerConfig, SiteConfig};
2use pingora::http::ResponseHeader;
3use pingora::prelude::*;
4use std::sync::Arc;
5
6// Global config path for reload functionality
7static CONFIG_PATH: once_cell::sync::OnceCell<Arc<std::sync::RwLock<Option<String>>>> =
8    once_cell::sync::OnceCell::new();
9
10#[derive(Clone)]
11pub struct ApiHandler {
12    // Simple handler without complex dependencies
13}
14
15impl ApiHandler {
16    pub fn new() -> Self {
17        Self {}
18    }
19
20    /// Set the global config path for reload functionality
21    pub fn set_config_path(path: String) {
22        let config_path = CONFIG_PATH.get_or_init(|| Arc::new(std::sync::RwLock::new(None)));
23        if let Ok(mut config_path_guard) = config_path.write() {
24            *config_path_guard = Some(path);
25        }
26    }
27
28    /// Get the global config path
29    async fn get_config_path() -> Option<String> {
30        let config_path = CONFIG_PATH.get()?;
31        config_path.read().ok()?.clone()
32    }
33
34    pub async fn handle(&self, session: &mut Session, site: Option<&SiteConfig>) -> Result<()> {
35        let path = session.req_header().uri.path().to_string();
36        let method = session.req_header().method.as_str();
37
38        match (method, path.as_str()) {
39            ("GET", "/api/sites") => self.handle_sites_info(session, site).await,
40            ("GET", "/api/ssl/certificates") => self.handle_ssl_certificates(session, site).await,
41            ("POST", path) if path.starts_with("/api/ssl/certificates/") => {
42                self.handle_ssl_certificate_request(session, site, path)
43                    .await
44            }
45            ("GET", "/api/ssl/status") => self.handle_ssl_status(session, site).await,
46            ("GET", "/api/config") => self.handle_config_info(session, site).await,
47            ("POST", "/api/config/reload") => self.handle_config_reload(session, site).await,
48            _ => self.handle_not_found(session, site).await,
49        }
50    }
51
52    async fn handle_sites_info(
53        &self,
54        session: &mut Session,
55        _site: Option<&SiteConfig>,
56    ) -> Result<()> {
57        // This would need access to the full server config
58        // For now, return a placeholder response
59        let response = serde_json::json!({
60            "sites": [],
61            "total_sites": 0,
62            "message": "Sites information endpoint"
63        });
64
65        self.send_json_response(session, 200, &response).await
66    }
67
68    async fn handle_ssl_certificates(
69        &self,
70        session: &mut Session,
71        _site: Option<&SiteConfig>,
72    ) -> Result<()> {
73        // This would need access to the SSL manager
74        // For now, return a placeholder response
75        let response = serde_json::json!({
76            "certificates": [],
77            "total_certificates": 0,
78            "message": "SSL certificates information endpoint"
79        });
80
81        self.send_json_response(session, 200, &response).await
82    }
83
84    async fn handle_ssl_certificate_request(
85        &self,
86        session: &mut Session,
87        _site: Option<&SiteConfig>,
88        path: &str,
89    ) -> Result<()> {
90        // Extract domain from path
91        let domain = path
92            .strip_prefix("/api/ssl/certificates/")
93            .unwrap_or("unknown");
94
95        let response = serde_json::json!({
96            "message": format!("Certificate request for domain: {}", domain),
97            "domain": domain,
98            "status": "not_implemented"
99        });
100
101        self.send_json_response(session, 501, &response).await
102    }
103
104    async fn handle_ssl_status(
105        &self,
106        session: &mut Session,
107        _site: Option<&SiteConfig>,
108    ) -> Result<()> {
109        let response = serde_json::json!({
110            "ssl_enabled": false,
111            "auto_cert": false,
112            "certificates": [],
113            "renewal_status": "unknown",
114            "message": "SSL status endpoint"
115        });
116
117        self.send_json_response(session, 200, &response).await
118    }
119
120    async fn handle_config_info(
121        &self,
122        session: &mut Session,
123        _site: Option<&SiteConfig>,
124    ) -> Result<()> {
125        let response = serde_json::json!({
126            "server": {
127                "name": "bws-web-server",
128                "version": env!("CARGO_PKG_VERSION")
129            },
130            "message": "Configuration information endpoint"
131        });
132
133        self.send_json_response(session, 200, &response).await
134    }
135
136    async fn handle_config_reload(
137        &self,
138        session: &mut Session,
139        _site: Option<&SiteConfig>,
140    ) -> Result<()> {
141        // Get the config path
142        let config_path = match Self::get_config_path().await {
143            Some(path) => path,
144            None => {
145                let response = serde_json::json!({
146                    "error": "Config path not set",
147                    "message": "Configuration path not found (running in temporary mode?)"
148                });
149                return self.send_json_response(session, 400, &response).await;
150            }
151        };
152
153        // Try to reload the configuration
154        match Self::reload_config_from_path(&config_path).await {
155            Ok(_) => {
156                let response = serde_json::json!({
157                    "message": "Configuration reloaded successfully",
158                    "status": "success",
159                    "config_path": config_path,
160                    "timestamp": chrono::Utc::now().to_rfc3339(),
161                    "note": "Changes will apply to new connections"
162                });
163                self.send_json_response(session, 200, &response).await
164            }
165            Err(e) => {
166                let response = serde_json::json!({
167                    "error": "Configuration reload failed",
168                    "message": format!("Failed to reload configuration: {}", e),
169                    "status": "error"
170                });
171                self.send_json_response(session, 400, &response).await
172            }
173        }
174    }
175
176    /// Reload configuration from the given path
177    async fn reload_config_from_path(
178        config_path: &str,
179    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
180        // Load and validate new configuration
181        let new_config = ServerConfig::load_from_file(config_path).map_err(|e| {
182            Box::new(std::io::Error::new(
183                std::io::ErrorKind::InvalidData,
184                e.to_string(),
185            )) as Box<dyn std::error::Error + Send + Sync>
186        })?;
187
188        new_config.validate().map_err(|e| {
189            Box::new(std::io::Error::new(
190                std::io::ErrorKind::InvalidData,
191                e.to_string(),
192            )) as Box<dyn std::error::Error + Send + Sync>
193        })?;
194
195        // Log the successful reload
196        log::info!("Configuration reloaded successfully from {}", config_path);
197
198        // Note: In a more sophisticated implementation, you would update
199        // the running server's configuration. For now, we just validate
200        // that the new config is loadable and valid.
201        Ok(())
202    }
203
204    async fn handle_not_found(
205        &self,
206        session: &mut Session,
207        _site: Option<&SiteConfig>,
208    ) -> Result<()> {
209        let response = serde_json::json!({
210            "error": "API endpoint not found",
211            "message": "The requested API endpoint does not exist",
212            "available_endpoints": [
213                "GET /api/sites",
214                "GET /api/ssl/certificates",
215                "POST /api/ssl/certificates/{domain}",
216                "GET /api/ssl/status",
217                "GET /api/config",
218                "POST /api/config/reload"
219            ]
220        });
221
222        self.send_json_response(session, 404, &response).await
223    }
224
225    async fn send_json_response(
226        &self,
227        session: &mut Session,
228        status: u16,
229        data: &serde_json::Value,
230    ) -> Result<()> {
231        let response_body = data.to_string();
232        let response_bytes = response_body.into_bytes();
233
234        let mut header = ResponseHeader::build(status, Some(4))?;
235        header.insert_header("Content-Type", "application/json; charset=utf-8")?;
236        header.insert_header("Content-Length", response_bytes.len().to_string())?;
237        header.insert_header("Cache-Control", "no-cache, no-store, must-revalidate")?;
238        header.insert_header("Pragma", "no-cache")?;
239
240        session
241            .write_response_header(Box::new(header), false)
242            .await?;
243        session
244            .write_response_body(Some(response_bytes.into()), true)
245            .await?;
246
247        Ok(())
248    }
249}
250
251impl Default for ApiHandler {
252    fn default() -> Self {
253        Self::new()
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260
261    #[test]
262    fn test_api_handler_creation() {
263        let handler = ApiHandler::new();
264        // Basic test that handler can be created
265        assert_eq!(
266            std::mem::size_of_val(&handler),
267            std::mem::size_of::<ApiHandler>()
268        );
269    }
270}