drogue_bazaar/actix/http/
builder.rs1use super::{bind::bind_http, config::HttpConfig};
2use crate::actix::http::{BuildCors, CorsConfig};
3use crate::app::{Startup, StartupExt};
4use crate::{
5 app::RuntimeConfig,
6 core::tls::{TlsAuthConfig, WithTlsAuthConfig},
7};
8use actix_cors::Cors;
9use actix_http::Extensions;
10use actix_web::{
11 middleware,
12 web::{self, ServiceConfig},
13 App, HttpServer,
14};
15use actix_web_extras::middleware::Condition;
16use futures_core::future::BoxFuture;
17use futures_util::{FutureExt, TryFutureExt};
18use std::any::Any;
19
20pub type OnConnectFn = dyn Fn(&dyn Any, &mut Extensions) + Send + Sync + 'static;
21
22pub struct HttpBuilder<F>
24where
25 F: Fn(&mut ServiceConfig) + Send + Clone + 'static,
26{
27 config: HttpConfig,
28 default_cors: Option<CorsConfig>,
29 app_builder: Box<F>,
30 on_connect: Option<Box<OnConnectFn>>,
31 tls_auth_config: TlsAuthConfig,
32 tracing: bool,
33}
34
35impl<F> HttpBuilder<F>
36where
37 F: Fn(&mut ServiceConfig) + Send + Clone + 'static,
38{
39 pub fn new(config: HttpConfig, runtime: Option<&RuntimeConfig>, app_builder: F) -> Self {
41 Self {
42 config,
43 default_cors: None,
44 app_builder: Box::new(app_builder),
45 on_connect: None,
46 tls_auth_config: TlsAuthConfig::default(),
47 tracing: runtime.map(|r| r.tracing.is_enabled()).unwrap_or_default(),
48 }
49 }
50
51 pub fn default_cors<C: Into<Option<CorsConfig>>>(mut self, default_cors: C) -> Self {
53 self.default_cors = default_cors.into();
54 self
55 }
56
57 pub fn on_connect<O>(mut self, on_connect: O) -> Self
59 where
60 O: Fn(&dyn Any, &mut Extensions) + Send + Sync + 'static,
61 {
62 self.on_connect = Some(Box::new(on_connect));
63 self
64 }
65
66 pub fn tls_auth_config<I: Into<TlsAuthConfig>>(mut self, tls_auth_config: I) -> Self {
68 self.tls_auth_config = tls_auth_config.into();
69 self
70 }
71
72 pub fn start(self, startup: &mut dyn Startup) -> anyhow::Result<()> {
74 startup.spawn(self.run()?);
75 Ok(())
76 }
77
78 fn cors_config(&self) -> Option<CorsConfig> {
83 self.config
84 .cors
85 .as_ref()
86 .or(self.default_cors.as_ref())
87 .cloned()
88 }
89
90 pub fn run(
97 #[allow(unused_mut)] mut self,
98 ) -> Result<BoxFuture<'static, Result<(), anyhow::Error>>, anyhow::Error> {
99 let max_payload_size = self.config.max_payload_size;
100 let max_json_payload_size = self.config.max_json_payload_size;
101
102 let prometheus = actix_web_prom::PrometheusMetricsBuilder::new(
103 self.config.metrics_namespace.as_deref().unwrap_or("drogue"),
104 )
105 .registry(prometheus::default_registry().clone())
106 .build()
107 .map_err(|err| anyhow::anyhow!("Failed to build prometheus middleware: {err}"))?;
109
110 let cors = self.cors_config();
111 log::debug!("Effective CORS config {cors:?}");
112
113 let _: Option<Cors> = cors.build_cors()?;
115
116 let mut main = HttpServer::new(move || {
117 let app = App::new();
118
119 let cors: Option<Cors> = cors.build_cors().expect("Configuration must be valid");
124 let app = app.wrap(Condition::from_option(cors));
125
126 let app = app.wrap(prometheus.clone());
128
129 let (logger, tracing_logger) = match self.tracing {
131 false => (Some(middleware::Logger::default()), None),
132 true => (None, Some(tracing_actix_web::TracingLogger::default())),
133 };
134 log::debug!(
135 "Loggers ({}) - logger: {}, tracing: {}",
136 self.tracing,
137 logger.is_some(),
138 tracing_logger.is_some()
139 );
140 let app = app
141 .wrap(Condition::from_option(logger))
142 .wrap(Condition::from_option(tracing_logger));
144
145 let app = app
147 .app_data(web::PayloadConfig::new(max_payload_size))
148 .app_data(web::JsonConfig::default().limit(max_json_payload_size));
149
150 app.configure(|cfg| (self.app_builder)(cfg))
152 });
153
154 if let Some(on_connect) = self.on_connect {
155 main = main.on_connect(on_connect);
156 }
157
158 if self.config.disable_tls_psk {
159 #[cfg(feature = "openssl")]
160 self.tls_auth_config.psk.take();
161 }
162
163 let mut main = bind_http(
164 main,
165 self.config.bind_addr,
166 self.config
167 .disable_tls
168 .with_tls_auth_config(self.tls_auth_config),
169 self.config.key_file,
170 self.config.cert_bundle_file,
171 )?;
172
173 if let Some(workers) = self.config.workers {
174 main = main.workers(workers)
175 }
176
177 Ok(main.run().err_into().boxed())
178 }
179}