Skip to main content

foxtive_ntex/http/server/
mod.rs

1mod config;
2
3#[cfg(feature = "static")]
4pub use config::StaticFileConfig;
5pub use config::{JsonConfig, ServerConfig};
6
7use crate::FoxtiveNtexState;
8use crate::http::kernel::{ntex_default_service, register_routes, setup_cors, setup_logger};
9use crate::setup::{FoxtiveNtexSetup, make_ntex_state};
10use foxtive::Error;
11use foxtive::prelude::AppResult;
12use foxtive::setup::load_environment_variables;
13use foxtive::setup::trace::Tracing;
14use ntex::io::IoConfig;
15use ntex::{SharedCfg, web};
16use std::future::Future;
17use tracing::{debug, error};
18
19pub fn init_bootstrap(service: &str, config: Tracing) -> AppResult<()> {
20    foxtive::setup::trace::init_tracing(config)?;
21    load_environment_variables(service);
22    Ok(())
23}
24
25pub async fn start_ntex_server<Callback, Fut>(
26    config: ServerConfig,
27    callback: Callback,
28) -> AppResult<()>
29where
30    Callback: FnOnce(FoxtiveNtexState) -> Fut + Copy + Send + 'static,
31    Fut: Future<Output = AppResult<()>> + Send + 'static,
32{
33    if !config.has_started_bootstrap {
34        let t_config = config.tracing.unwrap_or_default();
35        debug!("Starting bootstrap");
36        init_bootstrap(&config.app, t_config).expect("failed to init bootstrap: ");
37    }
38
39    debug!("Creating Foxtive-Ntex state");
40    let json_config = config.json_config.unwrap_or_default();
41    let app_state = make_ntex_state(FoxtiveNtexSetup {
42        allowed_origins: config.allowed_origins,
43        allowed_methods: config.allowed_methods,
44        foxtive_setup: config.foxtive_setup,
45        json_config: json_config.clone(),
46    })
47    .await?;
48
49    debug!("Executing app bootstrap callback");
50    match callback(app_state.clone()).await {
51        Ok(_) => {}
52        Err(err) => {
53            error!("app bootstrap callback returned error: {err:?}");
54            panic!("boostrap failed");
55        }
56    }
57
58    let boot = config.boot_thread;
59    let ntex_json_config = web::types::JsonConfig::default().limit(json_config.limit);
60
61    let shared_config = SharedCfg::new("WEB").add(
62        IoConfig::new()
63            .set_keepalive_timeout(config.keep_alive)
64            .set_connect_timeout(config.client_timeout)
65            .set_disconnect_timeout(config.client_disconnect),
66    );
67
68    let server = web::HttpServer::new(async move || {
69        let routes = boot();
70
71        let app = web::App::new()
72            .state(ntex_json_config.clone())
73            .state(app_state.clone())
74            .configure(|cfg| register_routes(cfg, routes))
75            .middleware(setup_logger())
76            .middleware(
77                setup_cors(
78                    app_state.allowed_origins.clone(),
79                    app_state.allowed_methods.clone(),
80                )
81                .finish(),
82            )
83            .default_service(ntex_default_service());
84
85        if cfg!(feature = "static") {
86            #[cfg(feature = "static")]
87            {
88                return app.service(ntex_files::Files::new(
89                    &config.static_config.path,
90                    &config.static_config.dir,
91                ));
92            }
93        }
94
95        app
96    })
97    .config(shared_config)
98    .backlog(config.backlog)
99    .workers(config.workers)
100    .maxconn(config.max_connections)
101    .maxconnrate(config.max_connections_rate)
102    // .keep_alive(config.keep_alive)
103    .bind((config.host, config.port))?
104    .run();
105
106    // clone server handle
107    let srv = server.clone();
108
109    // use provided shutdown signal or default
110    let shutdown_signal = config
111        .shutdown_signal
112        .unwrap_or_else(default_shutdown_signal);
113
114    // spawn shutdown listener
115    ntex::rt::spawn(async move {
116        shutdown_signal.await;
117
118        debug!("Shutdown signal received");
119
120        // graceful stop
121        srv.stop(true).await;
122    });
123
124    // await server
125    server.await.map_err(Error::from)?;
126
127    // AFTER server fully stops, run cleanup handler
128    if let Some(on_shutdown) = config.on_shutdown {
129        debug!("Running shutdown handler");
130        on_shutdown.await;
131    }
132
133    Ok(())
134}
135
136use std::pin::Pin;
137
138pub fn default_shutdown_signal() -> Pin<Box<dyn Future<Output = ()> + Send>> {
139    Box::pin(async {
140        let ctrl_c = async {
141            tokio::signal::ctrl_c()
142                .await
143                .expect("failed to listen for ctrl_c");
144        };
145
146        #[cfg(unix)]
147        let terminate = async {
148            use tokio::signal::unix::{signal, SignalKind};
149            let mut sigterm =
150                signal(SignalKind::terminate()).expect("failed to listen for SIGTERM");
151            sigterm.recv().await;
152        };
153
154        #[cfg(not(unix))]
155        let terminate = std::future::pending::<()>();
156
157        tokio::select! {
158            _ = ctrl_c => {},
159            _ = terminate => {},
160        }
161    })
162}