DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/Binary/Build/
ServiceRegistry.rs1use std::{
36 collections::HashMap,
37 sync::{Arc, RwLock},
38};
39
40use http::{Request as HttpRequest, Response as HttpResponse, header};
41use tokio::{
42 io::{AsyncReadExt, AsyncWriteExt},
43 net::TcpStream,
44};
45
46use crate::dev_log;
47
48#[derive(Debug, Clone)]
59pub struct LocalService {
60 pub name:String,
61
62 pub port:u16,
63
64 pub tls_port:Option<u16>,
65
66 pub use_tls:bool,
67
68 pub health_check_path:Option<String>,
69}
70
71impl LocalService {
72 pub fn get_port(&self) -> u16 {
74 if self.use_tls {
75 self.tls_port.unwrap_or_else(|| self.port + 1000)
76 } else {
77 self.port
78 }
79 }
80}
81
82#[derive(Clone)]
88pub struct ServiceRegistry {
89 services:Arc<RwLock<HashMap<String, LocalService>>>,
91
92 cert_manager:Option<std::sync::Arc<std::sync::Mutex<super::CertificateManager::CertificateManager>>>,
94}
95
96impl ServiceRegistry {
97 pub fn new() -> Self {
101 dev_log!("lifecycle", "[ServiceRegistry] Creating new ServiceRegistry");
102
103 Self { services:Arc::new(RwLock::new(HashMap::new())), cert_manager:None }
104 }
105
106 pub fn with_tls(
115 cert_manager:std::sync::Arc<std::sync::Mutex<super::CertificateManager::CertificateManager>>,
116 ) -> Self {
117 dev_log!("lifecycle", "[ServiceRegistry] Creating new ServiceRegistry with TLS support");
118
119 Self { services:Arc::new(RwLock::new(HashMap::new())), cert_manager:Some(cert_manager) }
120 }
121
122 pub fn register(&self, name:String, port:u16, health_check_path:Option<String>) {
137 self.register_with_options(name, port, None, false, health_check_path);
138 }
139
140 pub fn register_with_options(
163 &self,
164
165 name:String,
166
167 port:u16,
168
169 tls_port:Option<u16>,
170
171 use_tls:bool,
172
173 health_check_path:Option<String>,
174 ) {
175 dev_log!(
176 "lifecycle",
177 "[ServiceRegistry] Registering service: {} -> HTTP:{}, TLS:{}, use_tls:{}",
178 name,
179 port,
180 tls_port.unwrap_or(port + 1000),
181 use_tls
182 );
183
184 let service = LocalService { name:name.clone(), port, tls_port, use_tls, health_check_path };
185
186 if use_tls {
188 if let Some(cert_manager) = &self.cert_manager {
189 dev_log!("lifecycle", "[ServiceRegistry] TLS will be provisioned on-demand for {}", name);
191 } else {
192 dev_log!(
193 "lifecycle",
194 "warn: [ServiceRegistry] Service {} requested TLS but no certificate manager available",
195 name
196 );
197 }
198 }
199
200 if let Ok(mut services) = self.services.write() {
201 if services.contains_key(&name) {
203 dev_log!(
204 "lifecycle",
205 "warn: [ServiceRegistry] Service {} already registered, overwriting",
206 name
207 );
208 }
209
210 services.insert(name.clone(), service);
211
212 dev_log!("lifecycle", "[ServiceRegistry] Service {} registered successfully", name);
213 } else {
214 dev_log!(
215 "lifecycle",
216 "error: [ServiceRegistry] Failed to acquire write lock for registration"
217 );
218 }
219 }
220
221 pub fn lookup(&self, name:&str) -> Option<LocalService> {
241 dev_log!("lifecycle", "[ServiceRegistry] Looking up service: {}", name);
242
243 if let Ok(services) = self.services.read() {
244 let service = services.get(name).cloned();
245
246 if service.is_some() {
247 dev_log!("lifecycle", "[ServiceRegistry] Service {} found", name);
248 } else {
249 dev_log!("lifecycle", "[ServiceRegistry] Service {} not found", name);
250 }
251
252 service
253 } else {
254 dev_log!("lifecycle", "error: [ServiceRegistry] Failed to acquire read lock for lookup");
255
256 None
257 }
258 }
259
260 pub fn all_services(&self) -> Vec<LocalService> {
266 if let Ok(services) = self.services.read() {
267 services.values().cloned().collect()
268 } else {
269 dev_log!(
270 "lifecycle",
271 "error: [ServiceRegistry] Failed to acquire read lock for all_services"
272 );
273
274 Vec::new()
275 }
276 }
277
278 pub async fn health_check(&self, name:&str) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
290 let service = self.lookup(name).ok_or_else(|| format!("Service {} not found", name))?;
291
292 let health_path = service.health_check_path.as_deref().unwrap_or("/health");
293
294 let addr = format!("127.0.0.1:{}", service.port);
295
296 dev_log!(
297 "lifecycle",
298 "[ServiceRegistry] Performing health check for {} at {}:{}",
299 name,
300 addr,
301 health_path
302 );
303
304 match TcpStream::connect(&addr).await {
306 Ok(mut stream) => {
307 let request = format!("GET {} HTTP/1.1\r\nHost: 127.0.0.1:{}\r\n\r\n", health_path, service.port);
309
310 match stream.write_all(request.as_bytes()).await {
311 Ok(_) => {
312 let mut buffer = [0u8; 1024];
314
315 match stream.read(&mut buffer).await {
316 Ok(n) => {
317 let response = String::from_utf8_lossy(&buffer[..n]);
318
319 let is_healthy = response.contains("HTTP/1.1 200") || response.contains("HTTP/1.0 200");
320
321 if is_healthy {
322 dev_log!("lifecycle", "[ServiceRegistry] Service {} is healthy", name);
323 } else {
324 dev_log!(
325 "lifecycle",
326 "warn: [ServiceRegistry] Service {} health check failed: not 200",
327 name
328 );
329 }
330
331 Ok(is_healthy)
332 },
333
334 Err(e) => {
335 dev_log!(
336 "lifecycle",
337 "warn: [ServiceRegistry] Service {} health check failed to read: {}",
338 name,
339 e
340 );
341
342 Ok(false)
343 },
344 }
345 },
346
347 Err(e) => {
348 dev_log!(
349 "lifecycle",
350 "warn: [ServiceRegistry] Service {} health check failed to write: {}",
351 name,
352 e
353 );
354
355 Ok(false)
356 },
357 }
358 },
359
360 Err(e) => {
361 dev_log!(
362 "lifecycle",
363 "warn: [ServiceRegistry] Service {} health check failed to connect: {}",
364 name,
365 e
366 );
367
368 Ok(false)
369 },
370 }
371 }
372
373 pub fn unregister(&self, name:&str) -> Option<LocalService> {
384 dev_log!("lifecycle", "[ServiceRegistry] Unregistering service: {}", name);
385
386 if let Ok(mut services) = self.services.write() {
387 services.remove(name)
388 } else {
389 dev_log!(
390 "lifecycle",
391 "error: [ServiceRegistry] Failed to acquire write lock for unregistration"
392 );
393
394 None
395 }
396 }
397
398 pub async fn get_tls_config(&self, name:&str) -> Option<std::sync::Arc<rustls::ServerConfig>> {
411 let service = self.lookup(name)?;
412
413 if !service.use_tls {
414 return None;
415 }
416
417 let cert_manager = self.cert_manager.as_ref()?;
418
419 let manager = cert_manager
420 .lock()
421 .map_err(|e| {
422 dev_log!("lifecycle", "error: [ServiceRegistry] Failed to acquire lock: {}", e);
423 })
424 .ok()?;
425
426 manager.build_server_config(name).await.ok()
427 }
428
429 pub fn uses_tls(&self, name:&str) -> bool { self.lookup(name).map(|s| s.use_tls).unwrap_or(false) }
439}
440
441impl Default for ServiceRegistry {
442 fn default() -> Self { Self::new() }
443}
444
445#[cfg(test)]
446mod tests {
447
448 use super::*;
449
450 #[test]
451 fn test_register_and_lookup() {
452 let registry = ServiceRegistry::new();
453
454 registry.register("test.service.land".to_string(), 8080, Some("/health".to_string()));
455
456 let service = registry.lookup("test.service.land").unwrap();
457
458 assert_eq!(service.name, "test.service.land");
459
460 assert_eq!(service.port, 8080);
461
462 assert_eq!(service.health_check_path, Some("/health".to_string()));
463 }
464
465 #[test]
466 fn test_lookup_nonexistent() {
467 let registry = ServiceRegistry::new();
468
469 let service = registry.lookup("nonexistent.service.land");
470
471 assert!(service.is_none());
472 }
473
474 #[test]
475 fn test_all_services() {
476 let registry = ServiceRegistry::new();
477
478 registry.register("service1.land".to_string(), 8080, None);
479
480 registry.register("service2.land".to_string(), 8081, None);
481
482 let services = registry.all_services();
483
484 assert_eq!(services.len(), 2);
485 }
486
487 #[test]
488 fn test_unregister() {
489 let registry = ServiceRegistry::new();
490
491 registry.register("test.service.land".to_string(), 8080, None);
492
493 assert!(registry.lookup("test.service.land").is_some());
494
495 registry.unregister("test.service.land");
496
497 assert!(registry.lookup("test.service.land").is_none());
498 }
499
500 #[test]
501 fn test_overwrite_registration() {
502 let registry = ServiceRegistry::new();
503
504 registry.register("test.service.land".to_string(), 8080, None);
505
506 registry.register("test.service.land".to_string(), 9090, None);
507
508 let service = registry.lookup("test.service.land").unwrap();
509
510 assert_eq!(service.port, 9090);
511 }
512
513 #[test]
514 fn test_tls_service() {
515 let registry = ServiceRegistry::new();
516
517 registry.register_with_options(
518 "secure.service.land".to_string(),
519 8080,
520 Some(8443),
521 true,
522 Some("/health".to_string()),
523 );
524
525 let service = registry.lookup("secure.service.land").unwrap();
526
527 assert_eq!(service.name, "secure.service.land");
528
529 assert_eq!(service.port, 8080);
530
531 assert_eq!(service.tls_port, Some(8443));
532
533 assert_eq!(service.use_tls, true);
534
535 assert_eq!(service.get_port(), 8443);
536 }
537
538 #[test]
539 fn test_default_tls_port() {
540 let registry = ServiceRegistry::new();
541
542 registry.register_with_options(
543 "secure.service.land".to_string(),
544 8080,
545 None, true,
547 None,
548 );
549
550 let service = registry.lookup("secure.service.land").unwrap();
551
552 assert_eq!(service.tls_port, None); assert_eq!(service.get_port(), 9080); }
556}