elif_http/server/
lifecycle.rs

1//! Server lifecycle management - startup, shutdown, and signal handling
2
3use crate::{
4    config::HttpConfig,
5    errors::{HttpError, HttpResult},
6    middleware::v2::MiddlewarePipelineV2,
7    routing::ElifRouter,
8    server::health::health_check_handler,
9};
10use elif_core::container::IocContainer;
11use std::net::SocketAddr;
12use std::sync::Arc;
13use tokio::signal;
14use tracing::{info, warn};
15
16/// Build the internal Axum router (hidden from users)
17pub async fn build_internal_router(
18    container: Arc<IocContainer>,
19    config: HttpConfig,
20    user_router: Option<ElifRouter>,
21    middleware: MiddlewarePipelineV2,
22) -> HttpResult<axum::Router> {
23    // Create health check handler with captured context
24    let health_container = container.clone();
25    let health_config = config.clone();
26    let health_handler = move |_req: crate::request::ElifRequest| {
27        let container = health_container.clone();
28        let config = health_config.clone();
29        async move {
30            crate::response::ElifResponse::ok()
31                .json(&health_check_handler(container, config).await.0)
32        }
33    };
34
35    // Start with framework router
36    let mut router = user_router.unwrap_or_default();
37
38    // Add health check route
39    router = router.get(&config.health_check_path, health_handler);
40
41    // Apply server middleware to router
42    router = router.extend_middleware(middleware);
43
44    // Convert to Axum router
45    Ok(router.into_axum_router())
46}
47
48/// Start the server with graceful shutdown
49pub async fn start_server(addr: SocketAddr, router: axum::Router) -> HttpResult<()> {
50    // Create TCP listener
51    let listener = tokio::net::TcpListener::bind(addr)
52        .await
53        .map_err(|e| HttpError::startup(format!("Failed to bind to {}: {}", addr, e)))?;
54
55    info!("✅ Server listening on {}", addr);
56
57    // Serve with graceful shutdown
58    axum::serve(
59        listener,
60        router.into_make_service_with_connect_info::<SocketAddr>(),
61    )
62    .with_graceful_shutdown(shutdown_signal())
63    .await
64    .map_err(|e| HttpError::internal(format!("Server error: {}", e)))?;
65
66    Ok(())
67}
68
69/// Graceful shutdown signal handler
70async fn shutdown_signal() {
71    let ctrl_c = async {
72        signal::ctrl_c()
73            .await
74            .expect("Failed to install Ctrl+C handler");
75    };
76
77    #[cfg(unix)]
78    let terminate = async {
79        signal::unix::signal(signal::unix::SignalKind::terminate())
80            .expect("Failed to install signal handler")
81            .recv()
82            .await;
83    };
84
85    #[cfg(not(unix))]
86    let terminate = std::future::pending::<()>();
87
88    tokio::select! {
89        _ = ctrl_c => {
90            warn!("📡 Received Ctrl+C, shutting down gracefully...");
91        },
92        _ = terminate => {
93            warn!("📡 Received terminate signal, shutting down gracefully...");
94        },
95    }
96}