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}