1use crate::config::{ServerConfig, SiteConfig};
2use pingora::http::ResponseHeader;
3use pingora::prelude::*;
4use std::sync::Arc;
5
6static 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 }
14
15impl ApiHandler {
16 pub fn new() -> Self {
17 Self {}
18 }
19
20 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 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 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 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 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 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 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 async fn reload_config_from_path(
178 config_path: &str,
179 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
180 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::info!("Configuration reloaded successfully from {}", config_path);
197
198 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 assert_eq!(
266 std::mem::size_of_val(&handler),
267 std::mem::size_of::<ApiHandler>()
268 );
269 }
270}