unleash_edge/
lib.rs

1use crate::edge_builder::build_edge_state;
2use crate::offline_builder::build_offline_app_state;
3use ::tracing::info;
4use axum::Router;
5use axum::middleware::{from_fn, from_fn_with_state};
6use axum::routing::get;
7use chrono::Duration;
8use std::env;
9use std::sync::{Arc, LazyLock};
10use tokio::sync::RwLock;
11use tower::ServiceBuilder;
12use tower_http::compression::CompressionLayer;
13use tower_http::normalize_path::NormalizePathLayer;
14use ulid::Ulid;
15use unleash_edge_auth::token_validator::TokenValidator;
16use unleash_edge_cli::{AuthHeaders, CliArgs, EdgeMode};
17use unleash_edge_delta::cache_manager::DeltaCacheManager;
18use unleash_edge_feature_cache::FeatureCache;
19use unleash_edge_feature_refresh::HydratorType;
20use unleash_edge_http_client::{ClientMetaInformation, HttpClientArgs, new_reqwest_client};
21use unleash_edge_metrics::axum_prometheus_metrics::{
22    PrometheusAxumLayer, render_prometheus_metrics,
23};
24use unleash_edge_persistence::EdgePersistence;
25use unleash_edge_request_logger::log_request_middleware;
26use unleash_edge_types::metrics::instance_data::{EdgeInstanceData, Hosting};
27use unleash_edge_types::{BackgroundTask, EdgeResult, EngineCache, TokenCache};
28
29pub mod edge_builder;
30pub mod health_checker;
31mod middleware;
32pub mod offline_builder;
33pub mod ready_checker;
34pub mod tls;
35pub mod tracing;
36
37static SHOULD_DEFER_VALIDATION: LazyLock<bool> = LazyLock::new(|| {
38    env::var("EDGE_DEFER_TOKEN_VALIDATION")
39        .map(|v| v == "true" || v == "1")
40        .unwrap_or(false)
41});
42
43type CacheContainer = (
44    Arc<TokenCache>,
45    Arc<FeatureCache>,
46    Arc<DeltaCacheManager>,
47    Arc<EngineCache>,
48);
49pub type EdgeInfo = (
50    CacheContainer,
51    Arc<TokenValidator>,
52    HydratorType,
53    Option<Arc<dyn EdgePersistence>>,
54);
55
56pub async fn configure_server(args: CliArgs) -> EdgeResult<(Router, Vec<BackgroundTask>)> {
57    let app_id: Ulid = Ulid::new();
58    let hosting = Hosting::from_env();
59    let edge_instance_data = Arc::new(EdgeInstanceData::new(
60        &args.app_name,
61        &app_id,
62        Some(hosting),
63    ));
64    let client_meta_information = ClientMetaInformation {
65        app_name: args.app_name.clone(),
66        instance_id: app_id.to_string(),
67        connection_id: app_id.to_string(),
68    };
69    let instances_observed_for_app_context: Arc<RwLock<Vec<EdgeInstanceData>>> =
70        Arc::new(RwLock::new(Vec::new()));
71    let metrics_middleware =
72        PrometheusAxumLayer::new(&args.app_name.clone(), &app_id.clone().to_string());
73
74    let (app_state, background_tasks, shutdown_tasks) = match &args.mode {
75        EdgeMode::Edge(edge_args) => {
76            let http_client = new_reqwest_client(HttpClientArgs {
77                skip_ssl_verification: edge_args.skip_ssl_verification,
78                client_identity: edge_args.client_identity.clone(),
79                upstream_certificate_file: edge_args.upstream_certificate_file.clone(),
80                connect_timeout: Duration::seconds(edge_args.upstream_request_timeout),
81                socket_timeout: Duration::seconds(edge_args.upstream_socket_timeout),
82                keep_alive_timeout: Duration::seconds(edge_args.client_keepalive_timeout),
83                client_meta_information: client_meta_information.clone(),
84            })?;
85
86            let auth_headers = AuthHeaders::from(&args);
87
88            build_edge_state(
89                args.clone(),
90                edge_args,
91                client_meta_information,
92                edge_instance_data.clone(),
93                instances_observed_for_app_context.clone(),
94                auth_headers,
95                http_client,
96            )
97            .await?
98        }
99        EdgeMode::Offline(offline_args) => {
100            build_offline_app_state(args.clone(), offline_args.clone()).await?
101        }
102        _ => unreachable!(),
103    };
104
105    for task in background_tasks {
106        tokio::spawn(task);
107    }
108
109    let api_router = Router::new()
110        .nest("/client", unleash_edge_client_api::router())
111        .merge(unleash_edge_frontend_api::router(args.disable_all_endpoint))
112        .layer(
113            ServiceBuilder::new()
114                .layer(from_fn(log_request_middleware))
115                .layer(from_fn_with_state(
116                    app_state.clone(),
117                    middleware::validate_token::validate_token,
118                ))
119                .layer(from_fn_with_state(
120                    app_state.clone(),
121                    middleware::consumption::connection_consumption,
122                ))
123                .layer(from_fn(middleware::etag::etag_middleware)),
124        );
125
126    let backstage_router = if !args.internal_backstage.disable_metrics_endpoint {
127        Router::new()
128            .route("/metrics", get(render_prometheus_metrics))
129            .merge(unleash_edge_backstage::router(
130                args.internal_backstage.clone(),
131            ))
132    } else {
133        unleash_edge_backstage::router(args.internal_backstage.clone())
134    };
135
136    let top_router: Router = Router::new()
137        .nest("/api", api_router)
138        .nest("/edge", unleash_edge_edge_api::router())
139        .nest("/internal-backstage", backstage_router)
140        .layer(
141            ServiceBuilder::new()
142                .layer(NormalizePathLayer::trim_trailing_slash())
143                .layer(CompressionLayer::new())
144                .layer(metrics_middleware)
145                .layer(args.http.cors.middleware())
146                .layer(from_fn_with_state(
147                    app_state.clone(),
148                    middleware::deny_list::deny_middleware,
149                ))
150                .layer(from_fn_with_state(
151                    app_state.clone(),
152                    middleware::allow_list::allow_middleware,
153                ))
154                .layer(from_fn_with_state(
155                    app_state.clone(),
156                    middleware::client_metrics::extract_request_metrics,
157                ))
158                .layer(tower_http::trace::TraceLayer::new_for_http()),
159        )
160        .with_state(app_state);
161    let router_to_host = if args.http.base_path.len() > 1 {
162        info!("Had a path different from root. Setting up a nested router");
163        let path = if !args.http.base_path.starts_with("/") {
164            format!("/{}", args.http.base_path)
165        } else {
166            args.http.base_path.clone()
167        };
168        Router::new().nest(&path, top_router)
169    } else {
170        top_router
171    };
172
173    Ok((router_to_host, shutdown_tasks))
174}