hive_router/
lib.rs

1pub mod background_tasks;
2mod consts;
3mod http_utils;
4mod jwt;
5mod logger;
6pub mod pipeline;
7mod schema_state;
8mod shared_state;
9mod supergraph;
10mod utils;
11
12use std::sync::Arc;
13
14use crate::{
15    background_tasks::BackgroundTasksManager,
16    consts::ROUTER_VERSION,
17    http_utils::{
18        landing_page::landing_page_handler,
19        probes::{health_check_handler, readiness_check_handler},
20    },
21    jwt::JwtAuthRuntime,
22    logger::configure_logging,
23    pipeline::graphql_request_handler,
24};
25
26pub use crate::{schema_state::SchemaState, shared_state::RouterSharedState};
27
28use hive_router_config::{load_config, HiveRouterConfig};
29use http::header::RETRY_AFTER;
30use ntex::{
31    util::Bytes,
32    web::{self, HttpRequest},
33};
34use tracing::{info, warn};
35
36async fn graphql_endpoint_handler(
37    mut request: HttpRequest,
38    body_bytes: Bytes,
39    schema_state: web::types::State<Arc<SchemaState>>,
40    app_state: web::types::State<Arc<RouterSharedState>>,
41) -> impl web::Responder {
42    let maybe_supergraph = schema_state.current_supergraph();
43
44    if let Some(supergraph) = maybe_supergraph.as_ref() {
45        // If an early CORS response is needed, return it immediately.
46        if let Some(early_response) = app_state
47            .cors_runtime
48            .as_ref()
49            .and_then(|cors| cors.get_early_response(&request))
50        {
51            return early_response;
52        }
53
54        let mut res = graphql_request_handler(
55            &mut request,
56            body_bytes,
57            supergraph,
58            app_state.get_ref(),
59            schema_state.get_ref(),
60        )
61        .await;
62
63        // Apply CORS headers to the final response if CORS is configured.
64        if let Some(cors) = app_state.cors_runtime.as_ref() {
65            cors.set_headers(&request, res.headers_mut());
66        }
67
68        res
69    } else {
70        warn!("No supergraph available yet, unable to process request");
71
72        web::HttpResponse::ServiceUnavailable()
73            .header(RETRY_AFTER, 10)
74            .finish()
75    }
76}
77
78pub async fn router_entrypoint() -> Result<(), Box<dyn std::error::Error>> {
79    let config_path = std::env::var("ROUTER_CONFIG_FILE_PATH").ok();
80    let router_config = load_config(config_path)?;
81    configure_logging(&router_config.log);
82    info!("hive-router@{} starting...", ROUTER_VERSION);
83    let addr = router_config.http.address();
84    let mut bg_tasks_manager = BackgroundTasksManager::new();
85    let (shared_state, schema_state) =
86        configure_app_from_config(router_config, &mut bg_tasks_manager).await?;
87
88    let maybe_error = web::HttpServer::new(move || {
89        web::App::new()
90            .state(shared_state.clone())
91            .state(schema_state.clone())
92            .configure(configure_ntex_app)
93            .default_service(web::to(landing_page_handler))
94    })
95    .bind(addr)?
96    .run()
97    .await
98    .map_err(|err| err.into());
99
100    info!("server stopped, clearning background tasks");
101    bg_tasks_manager.shutdown();
102
103    maybe_error
104}
105
106pub async fn configure_app_from_config(
107    router_config: HiveRouterConfig,
108    bg_tasks_manager: &mut BackgroundTasksManager,
109) -> Result<(Arc<RouterSharedState>, Arc<SchemaState>), Box<dyn std::error::Error>> {
110    let jwt_runtime = match router_config.jwt.is_jwt_auth_enabled() {
111        true => Some(JwtAuthRuntime::init(bg_tasks_manager, &router_config.jwt).await?),
112        false => None,
113    };
114
115    let router_config_arc = Arc::new(router_config);
116    let schema_state =
117        SchemaState::new_from_config(bg_tasks_manager, router_config_arc.clone()).await?;
118    let schema_state_arc = Arc::new(schema_state);
119    let shared_state = Arc::new(RouterSharedState::new(router_config_arc, jwt_runtime)?);
120
121    Ok((shared_state, schema_state_arc))
122}
123
124pub fn configure_ntex_app(cfg: &mut web::ServiceConfig) {
125    cfg.route("/graphql", web::to(graphql_endpoint_handler))
126        .route("/health", web::to(health_check_handler))
127        .route("/readiness", web::to(readiness_check_handler));
128}