Skip to main content

Mountain/Binary/Build/
ServiceRegistry.rs

1//! # Service Registry Module
2//!
3//! ## RESPONSIBILITIES
4//!
5//! - Track mapping from land:// domain names to local HTTP service ports
6//! - Provide thread-safe access using Arc<RwLock<>>
7//! - Support service registration and lookup
8//! - Enable health checks for registered services
9//!
10//! ## ARCHITECTURAL ROLE
11//!
12//! The ServiceRegistry provides the bridge between land:// URIs and local
13//! services:
14//!
15//! ```text
16//! land://code.editor.land/path ──► ServiceRegistry ──► http://127.0.0.1:PORT/path
17//! ```
18//!
19//! ## THREAD SAFETY
20//!
21//! - Uses Arc<RwLock<HashMap<String, LocalService>>> for concurrent access
22//! - Multiple readers allowed concurrently
23//! - Writers lock exclusively
24//!
25//! ## USAGE
26//!
27//! ```rust
28//! let registry = ServiceRegistry::new();
29//! registry.register("code.editor.land".to_string(), 8080, Some("/health".to_string()));
30//!
31//! let service = registry.lookup("code.editor.land").unwrap();
32//! assert_eq!(service.port, 8080);
33//! ```
34
35use std::{
36	collections::HashMap,
37	sync::{Arc, RwLock},
38};
39
40#[allow(unused_imports)]
41use http::{Request as HttpRequest, Response as HttpResponse, header};
42use tokio::{
43	io::{AsyncReadExt, AsyncWriteExt},
44	net::TcpStream,
45};
46
47use crate::dev_log;
48
49/// Represents a local HTTP/HTTPS service registered with the land:// scheme
50///
51/// # Fields
52///
53/// - `name`: Domain name (e.g., "code.editor.land")
54/// - `port`: Local port where the service is listening
55/// - `tls_port`: Optional TLS port for HTTPS (defaults to port + 1000 if not
56///   specified)
57/// - `use_tls`: Whether the service uses HTTPS
58/// - `health_check_path`: Optional path for health check endpoint
59#[derive(Debug, Clone)]
60pub struct LocalService {
61	pub name:String,
62
63	pub port:u16,
64
65	pub tls_port:Option<u16>,
66
67	pub use_tls:bool,
68
69	pub health_check_path:Option<String>,
70}
71
72impl LocalService {
73	/// Get the appropriate port based on TLS configuration
74	pub fn get_port(&self) -> u16 {
75		if self.use_tls {
76			self.tls_port.unwrap_or_else(|| self.port + 1000)
77		} else {
78			self.port
79		}
80	}
81}
82
83/// Registry for tracking local HTTP/HTTPS services
84///
85/// Provides thread-safe methods to register and lookup services by domain name.
86/// Supports both HTTP and HTTPS protocols with automatic TLS certificate
87/// provisioning.
88#[derive(Clone)]
89pub struct ServiceRegistry {
90	/// Inner storage using `Arc<RwLock>` for thread-safe concurrent access
91	services:Arc<RwLock<HashMap<String, LocalService>>>,
92
93	/// Optional certificate manager for HTTPS support
94	cert_manager:Option<std::sync::Arc<std::sync::Mutex<super::CertificateManager::CertificateManager>>>,
95}
96
97impl ServiceRegistry {
98	/// Create a new ServiceRegistry instance
99	///
100	/// Returns an empty registry ready to accept service registrations.
101	pub fn new() -> Self {
102		dev_log!("lifecycle", "[ServiceRegistry] Creating new ServiceRegistry");
103
104		Self { services:Arc::new(RwLock::new(HashMap::new())), cert_manager:None }
105	}
106
107	/// Create a new ServiceRegistry instance with TLS support
108	///
109	/// # Parameters
110	///
111	/// * `cert_manager` - Certificate manager for provisioning TLS certificates
112	///
113	/// Returns a registry ready to accept both HTTP and HTTPS service
114	/// registrations.
115	pub fn with_tls(
116		cert_manager:std::sync::Arc<std::sync::Mutex<super::CertificateManager::CertificateManager>>,
117	) -> Self {
118		dev_log!("lifecycle", "[ServiceRegistry] Creating new ServiceRegistry with TLS support");
119
120		Self { services:Arc::new(RwLock::new(HashMap::new())), cert_manager:Some(cert_manager) }
121	}
122
123	/// Register a local HTTP service
124	///
125	/// # Parameters
126	///
127	/// - `name`: Domain name (e.g., "code.editor.land")
128	/// - `port`: Local port where the service is listening
129	/// - `health_check_path`: Optional path for health check endpoint (e.g.,
130	///   "/health")
131	///
132	/// # Example
133	///
134	/// ```rust
135	/// registry.register("code.editor.land".to_string(), 8080, Some("/health".to_string())); 
136	/// ```
137	pub fn register(&self, name:String, port:u16, health_check_path:Option<String>) {
138		self.register_with_options(name, port, None, false, health_check_path);
139	}
140
141	/// Register a local service with TLS options
142	///
143	/// # Parameters
144	///
145	/// - `name`: Domain name (e.g., "code.editor.land")
146	/// - `port`: Local HTTP port
147	/// - `tls_port`: Optional TLS port (defaults to port + 1000)
148	/// - `use_tls`: Whether to enable HTTPS
149	/// - `health_check_path`: Optional path for health check endpoint
150	///
151	/// # Example
152	///
153	/// ```rust
154	/// // Register with TLS enabled
155	/// registry.register_with_options(
156	/// 	"code.editor.land".to_string(),
157	/// 	8080,
158	/// 	None, // Use default TLS port (9080)
159	/// 	true,
160	/// 	Some("/health".to_string()),
161	/// );
162	/// ```
163	pub fn register_with_options(
164		&self,
165
166		name:String,
167
168		port:u16,
169
170		tls_port:Option<u16>,
171
172		use_tls:bool,
173
174		health_check_path:Option<String>,
175	) {
176		dev_log!(
177			"lifecycle",
178			"[ServiceRegistry] Registering service: {} -> HTTP:{}, TLS:{}, use_tls:{}",
179			name,
180			port,
181			tls_port.unwrap_or(port + 1000),
182			use_tls
183		);
184
185		let service = LocalService { name:name.clone(), port, tls_port, use_tls, health_check_path };
186
187		// Pre-provision TLS certificate if needed
188		if use_tls {
189			if let Some(cert_manager) = &self.cert_manager {
190				// NOTE: TLS certificate is generated on-demand when needed
191				dev_log!("lifecycle", "[ServiceRegistry] TLS will be provisioned on-demand for {}", name);
192			} else {
193				dev_log!(
194					"lifecycle",
195					"warn: [ServiceRegistry] Service {} requested TLS but no certificate manager available",
196					name
197				);
198			}
199		}
200
201		if let Ok(mut services) = self.services.write() {
202			// Check if service already exists
203			if services.contains_key(&name) {
204				dev_log!(
205					"lifecycle",
206					"warn: [ServiceRegistry] Service {} already registered, overwriting",
207					name
208				);
209			}
210
211			services.insert(name.clone(), service);
212
213			dev_log!("lifecycle", "[ServiceRegistry] Service {} registered successfully", name);
214		} else {
215			dev_log!(
216				"lifecycle",
217				"error: [ServiceRegistry] Failed to acquire write lock for registration"
218			);
219		}
220	}
221
222	/// Look up a service by domain name
223	///
224	/// # Parameters
225	///
226	/// - `name`: Domain name to look up
227	///
228	/// # Returns
229	///
230	/// - `Some(LocalService)` if found
231	/// - `None` if not registered
232	///
233	/// # Example
234	///
235	/// ```rust
236	/// let service = registry.lookup("code.editor.land");
237	/// if let Some(svc) = service {
238	/// 	println!("Service running on port {}", svc.port);
239	/// }
240	/// ```
241	pub fn lookup(&self, name:&str) -> Option<LocalService> {
242		dev_log!("lifecycle", "[ServiceRegistry] Looking up service: {}", name);
243
244		if let Ok(services) = self.services.read() {
245			let service = services.get(name).cloned();
246
247			if service.is_some() {
248				dev_log!("lifecycle", "[ServiceRegistry] Service {} found", name);
249			} else {
250				dev_log!("lifecycle", "[ServiceRegistry] Service {} not found", name);
251			}
252
253			service
254		} else {
255			dev_log!("lifecycle", "error: [ServiceRegistry] Failed to acquire read lock for lookup");
256
257			None
258		}
259	}
260
261	/// Get all registered services
262	///
263	/// # Returns
264	///
265	/// A vector of all registered LocalService instances
266	pub fn all_services(&self) -> Vec<LocalService> {
267		if let Ok(services) = self.services.read() {
268			services.values().cloned().collect()
269		} else {
270			dev_log!(
271				"lifecycle",
272				"error: [ServiceRegistry] Failed to acquire read lock for all_services"
273			);
274
275			Vec::new()
276		}
277	}
278
279	/// Perform a health check on a registered service
280	///
281	/// # Parameters
282	///
283	/// - `name`: Domain name of the service to check
284	///
285	/// # Returns
286	///
287	/// - `Ok(true)` if service is healthy and responding
288	/// - `Ok(false)` if service is not healthy
289	/// - `Err` if service not found or health check fails
290	pub async fn health_check(&self, name:&str) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
291		let service = self.lookup(name).ok_or_else(|| format!("Service {} not found", name))?;
292
293		let health_path = service.health_check_path.as_deref().unwrap_or("/health");
294
295		let addr = format!("127.0.0.1:{}", service.port);
296
297		dev_log!(
298			"lifecycle",
299			"[ServiceRegistry] Performing health check for {} at {}:{}",
300			name,
301			addr,
302			health_path
303		);
304
305		// Try to connect to the service
306		match TcpStream::connect(&addr).await {
307			Ok(mut stream) => {
308				// Send simple HTTP GET request
309				let request = format!("GET {} HTTP/1.1\r\nHost: 127.0.0.1:{}\r\n\r\n", health_path, service.port);
310
311				match stream.write_all(request.as_bytes()).await {
312					Ok(_) => {
313						// Try to read response
314						let mut buffer = [0u8; 1024];
315
316						match stream.read(&mut buffer).await {
317							Ok(n) => {
318								let response = String::from_utf8_lossy(&buffer[..n]);
319
320								let is_healthy = response.contains("HTTP/1.1 200") || response.contains("HTTP/1.0 200");
321
322								if is_healthy {
323									dev_log!("lifecycle", "[ServiceRegistry] Service {} is healthy", name);
324								} else {
325									dev_log!(
326										"lifecycle",
327										"warn: [ServiceRegistry] Service {} health check failed: not 200",
328										name
329									);
330								}
331
332								Ok(is_healthy)
333							},
334
335							Err(e) => {
336								dev_log!(
337									"lifecycle",
338									"warn: [ServiceRegistry] Service {} health check failed to read: {}",
339									name,
340									e
341								);
342
343								Ok(false)
344							},
345						}
346					},
347
348					Err(e) => {
349						dev_log!(
350							"lifecycle",
351							"warn: [ServiceRegistry] Service {} health check failed to write: {}",
352							name,
353							e
354						);
355
356						Ok(false)
357					},
358				}
359			},
360
361			Err(e) => {
362				dev_log!(
363					"lifecycle",
364					"warn: [ServiceRegistry] Service {} health check failed to connect: {}",
365					name,
366					e
367				);
368
369				Ok(false)
370			},
371		}
372	}
373
374	/// Remove a service from the registry
375	///
376	/// # Parameters
377	///
378	/// - `name`: Domain name of the service to remove
379	///
380	/// # Returns
381	///
382	/// - `Some(LocalService)` if service was removed
383	/// - `None` if service was not found
384	pub fn unregister(&self, name:&str) -> Option<LocalService> {
385		dev_log!("lifecycle", "[ServiceRegistry] Unregistering service: {}", name);
386
387		if let Ok(mut services) = self.services.write() {
388			services.remove(name)
389		} else {
390			dev_log!(
391				"lifecycle",
392				"error: [ServiceRegistry] Failed to acquire write lock for unregistration"
393			);
394
395			None
396		}
397	}
398
399	/// Get TLS configuration for a service (if available)
400	///
401	/// # Parameters
402	///
403	/// - `name`: Domain name of the service
404	///
405	/// # Returns
406	///
407	/// - `Some(Arc<ServerConfig>)` if service uses TLS and certificate manager
408	///   is available
409	/// - `None` if service doesn't use TLS or certificate manager is not
410	///   configured
411	pub async fn get_tls_config(&self, name:&str) -> Option<std::sync::Arc<rustls::ServerConfig>> {
412		let service = self.lookup(name)?;
413
414		if !service.use_tls {
415			return None;
416		}
417
418		let cert_manager = self.cert_manager.as_ref()?;
419
420		let manager = cert_manager
421			.lock()
422			.map_err(|e| {
423				dev_log!("lifecycle", "error: [ServiceRegistry] Failed to acquire lock: {}", e);
424			})
425			.ok()?;
426
427		manager.build_server_config(name).await.ok()
428	}
429
430	/// Check if a service uses TLS
431	///
432	/// # Parameters
433	///
434	/// - `name`: Domain name of the service
435	///
436	/// # Returns
437	///
438	/// `true` if the service is configured to use TLS, `false` otherwise
439	pub fn uses_tls(&self, name:&str) -> bool { self.lookup(name).map(|s| s.use_tls).unwrap_or(false) }
440}
441
442impl Default for ServiceRegistry {
443	fn default() -> Self { Self::new() }
444}
445
446#[cfg(test)]
447mod tests {
448
449	use super::*;
450
451	#[test]
452	fn test_register_and_lookup() {
453		let registry = ServiceRegistry::new();
454
455		registry.register("test.service.land".to_string(), 8080, Some("/health".to_string()));
456
457		let service = registry.lookup("test.service.land").unwrap();
458
459		assert_eq!(service.name, "test.service.land");
460
461		assert_eq!(service.port, 8080);
462
463		assert_eq!(service.health_check_path, Some("/health".to_string()));
464	}
465
466	#[test]
467	fn test_lookup_nonexistent() {
468		let registry = ServiceRegistry::new();
469
470		let service = registry.lookup("nonexistent.service.land");
471
472		assert!(service.is_none());
473	}
474
475	#[test]
476	fn test_all_services() {
477		let registry = ServiceRegistry::new();
478
479		registry.register("service1.land".to_string(), 8080, None);
480
481		registry.register("service2.land".to_string(), 8081, None);
482
483		let services = registry.all_services();
484
485		assert_eq!(services.len(), 2);
486	}
487
488	#[test]
489	fn test_unregister() {
490		let registry = ServiceRegistry::new();
491
492		registry.register("test.service.land".to_string(), 8080, None);
493
494		assert!(registry.lookup("test.service.land").is_some());
495
496		registry.unregister("test.service.land");
497
498		assert!(registry.lookup("test.service.land").is_none());
499	}
500
501	#[test]
502	fn test_overwrite_registration() {
503		let registry = ServiceRegistry::new();
504
505		registry.register("test.service.land".to_string(), 8080, None);
506
507		registry.register("test.service.land".to_string(), 9090, None);
508
509		let service = registry.lookup("test.service.land").unwrap();
510
511		assert_eq!(service.port, 9090);
512	}
513
514	#[test]
515	fn test_tls_service() {
516		let registry = ServiceRegistry::new();
517
518		registry.register_with_options(
519			"secure.service.land".to_string(),
520			8080,
521			Some(8443),
522			true,
523			Some("/health".to_string()),
524		);
525
526		let service = registry.lookup("secure.service.land").unwrap();
527
528		assert_eq!(service.name, "secure.service.land");
529
530		assert_eq!(service.port, 8080);
531
532		assert_eq!(service.tls_port, Some(8443));
533
534		assert_eq!(service.use_tls, true);
535
536		assert_eq!(service.get_port(), 8443);
537	}
538
539	#[test]
540	fn test_default_tls_port() {
541		let registry = ServiceRegistry::new();
542
543		registry.register_with_options(
544			"secure.service.land".to_string(),
545			8080,
546			None, // Use default TLS port (8080 + 1000 = 9080)
547			true,
548			None,
549		);
550
551		let service = registry.lookup("secure.service.land").unwrap();
552
553		assert_eq!(service.tls_port, None); // Explicitly None
554		assert_eq!(service.get_port(), 9080); // But get_port() returns default
555	}
556}