1use impulse_utils::errors::ServerError;
11use impulse_utils::prelude::MResult;
12use salvo::prelude::*;
13
14use salvo::conn::rustls::{Keycert, RustlsConfig};
15use salvo::server::ServerHandle;
16use std::future::Future;
17use std::pin::Pin;
18use std::process::Command;
19
20#[cfg(feature = "http3")]
21use salvo::http::HeaderValue;
22#[cfg(feature = "http3")]
23use salvo::http::header::ALT_SVC;
24
25use crate::setup::{GenericServerState, GenericSetup, StartupVariant};
26
27static TLS13: &[&rustls::SupportedProtocolVersion] = &[&rustls::version::TLS13];
28
29#[cfg(feature = "http3")]
30#[handler]
31pub async fn h3_header(depot: &mut Depot, res: &mut Response) {
35 use crate::setup::GenericValues;
36
37 let server_port = match depot.obtain::<GenericValues>() {
38 Ok(app_config) => app_config.server_port.unwrap(),
39 Err(_) => 443,
40 };
41
42 res
43 .headers_mut()
44 .insert(
45 ALT_SVC,
46 HeaderValue::from_str(&format!(r##"h3=":{server_port}"; ma=2592000"##)).unwrap(),
47 )
48 .unwrap();
49}
50
51fn tlsv13(certpath: impl AsRef<str>, keypath: impl AsRef<str>) -> MResult<RustlsConfig> {
52 Ok(
53 RustlsConfig::new(
54 Keycert::new()
55 .cert_from_path(certpath.as_ref())
56 .map_err(|e| ServerError::from_private(e).with_500())?
57 .key_from_path(keypath.as_ref())
58 .map_err(|e| ServerError::from_private(e).with_500())?,
59 )
60 .tls_versions(TLS13),
61 )
62}
63
64#[cfg(feature = "otel")]
65#[handler]
66pub async fn sk_default_metrics(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
70 let meter = crate::otel::api::global::meter("sk_metrics");
71
72 let request_counter = meter
73 .u64_counter("sk_requests")
74 .with_unit("1")
75 .with_description("Total number of requests")
76 .build();
77
78 let request_duration = meter
79 .f64_histogram("sk_request_duration")
80 .with_unit("s")
81 .with_description("HTTP request duration in seconds")
82 .build();
83
84 let active_connections = meter
85 .i64_up_down_counter("sk_active_connections")
86 .with_unit("1")
87 .with_description("Number of active HTTP connections")
88 .build();
89
90 let host = req.uri().host().map(String::from);
91 let path = req.uri().path().to_string();
92 let method = req.method().as_str().to_string();
93
94 let attributes = vec![
95 opentelemetry::KeyValue::new("host", host.unwrap_or(String::from("unknown"))),
96 opentelemetry::KeyValue::new("path", path),
97 opentelemetry::KeyValue::new("method", method),
98 opentelemetry::KeyValue::new("user_agent", req.header("user-agent").unwrap_or("unknown").to_string()),
99 ];
100
101 active_connections.add(1, &[]);
102 active_connections.add(1, &attributes);
103 let start = tokio::time::Instant::now();
104
105 ctrl.call_next(req, depot, res).await;
106
107 let duration = start.elapsed().as_secs_f64();
108
109 let mut result_attributes = attributes.clone();
110 let status = res.status_code.unwrap_or(StatusCode::OK).as_u16().to_string();
111 result_attributes.push(opentelemetry::KeyValue::new("status", status));
112
113 request_counter.add(1, &[]);
114 request_counter.add(1, &result_attributes);
115 request_duration.record(duration, &result_attributes);
116
117 active_connections.add(-1, &attributes);
118}
119
120pub fn get_root_router_autoinject<T: GenericSetup + Send + Sync + Clone + 'static>(
125 app_state: &GenericServerState,
126 app_config: T,
127) -> Router {
128 #[allow(unused_mut)]
129 let mut router = Router::new().hoop(affix_state::inject(app_state.clone()).inject(app_config.clone()));
130
131 let sec = &app_config.generic_values().security_headers;
132 if sec.enabled {
133 router = router.hoop(crate::security_headers::SecurityHeaders::new(sec));
134 }
135
136 #[cfg(all(feature = "http3", feature = "acme"))]
137 if app_state.startup_variant == StartupVariant::QuinnAcme {
138 router = router.hoop(h3_header);
139 }
140
141 #[cfg(feature = "http3")]
142 if app_state.startup_variant == StartupVariant::Quinn || app_state.startup_variant == StartupVariant::QuinnOnly {
143 router = router.hoop(h3_header);
144 }
145
146 #[cfg(feature = "otel")]
147 if app_config.generic_values().tracing_options.otel_http_endpoint.is_some() {
148 router = router.hoop(sk_default_metrics);
149 }
150
151 router
152}
153
154#[allow(unused_variables)]
158pub fn get_root_router(app_state: &GenericServerState) -> Router {
159 #[allow(unused_mut)]
160 let mut router = Router::new();
161
162 #[cfg(all(feature = "http3", feature = "acme"))]
163 if app_state.startup_variant == StartupVariant::QuinnAcme {
164 router = router.hoop(h3_header);
165 }
166
167 #[cfg(feature = "http3")]
168 if app_state.startup_variant == StartupVariant::Quinn || app_state.startup_variant == StartupVariant::QuinnOnly {
169 router = router.hoop(h3_header);
170 }
171
172 router
173}
174
175#[cfg(any(feature = "oapi", feature = "acme"))]
176#[allow(clippy::mut_from_ref, invalid_reference_casting)]
177unsafe fn make_mut<T>(reference: &T) -> &mut T {
178 let const_ptr = reference as *const T;
179 let mut_ptr = const_ptr as *mut T;
180 unsafe { &mut *mut_ptr }
181}
182
183#[cfg(feature = "force-https")]
199pub async fn start_force_https_redirect(
200 listen_port: u16,
201 redirect_port: u16,
202) -> MResult<(Pin<Box<dyn Future<Output = ()> + Send>>, ServerHandle)> {
203 let service = Service::new(Router::new()).hoop(ForceHttps::new().https_port(redirect_port));
204 let acceptor = TcpListener::new(format!("0.0.0.0:{listen_port}")).bind().await;
205 let server = Server::new(acceptor);
206 let handle = server.handle();
207 let server = Box::pin(server.serve(service));
208 Ok((server, handle))
209}
210
211pub async fn start_with_service(
216 app_state: GenericServerState,
217 app_config: &impl GenericSetup,
218 #[allow(unused_mut)] mut service: Service,
219) -> MResult<(Pin<Box<dyn Future<Output = ()> + Send>>, ServerHandle)> {
220 tracing::info!("Server is starting...");
221
222 rustls::crypto::aws_lc_rs::default_provider()
223 .install_default()
224 .map_err(|_| ServerError::from_private_str("Can't install default crypto provider!").with_500())?;
225 let app_config = app_config.generic_values();
226
227 if let Some(bin) = app_config.auto_migrate_bin.as_ref() {
228 Command::new(bin)
229 .spawn()
230 .map_err(|e| ServerError::from_private(e).with_500())?;
231 }
232
233 #[cfg(feature = "oapi")]
234 if app_config.allow_oapi_access.is_some_and(|v| v) {
235 let doc = OpenApi::new(
236 app_config.oapi_name.as_ref().unwrap(),
237 app_config.oapi_ver.as_ref().unwrap(),
238 )
239 .merge_router(&service.router);
240
241 let oapi_endpoint = if let Some(ftype) = app_config.oapi_frontend_type.as_ref() {
242 match ftype.as_str() {
243 "Scalar" => Some(
244 Scalar::new(format!("{}/openapi.json", app_config.oapi_api_addr.as_ref().unwrap()))
245 .title(format!(
246 "{} - API @ Scalar",
247 app_config.oapi_name.as_ref().unwrap_or(&app_config.app_name)
248 ))
249 .description(format!(
250 "{} - API",
251 app_config.oapi_name.as_ref().unwrap_or(&app_config.app_name)
252 ))
253 .into_router(app_config.oapi_api_addr.as_ref().unwrap()),
254 ),
255 "SwaggerUI" => Some(
256 SwaggerUi::new(format!("{}/openapi.json", app_config.oapi_api_addr.as_ref().unwrap()))
257 .title(format!(
258 "{} - API @ SwaggerUI",
259 app_config.oapi_name.as_ref().unwrap_or(&app_config.app_name)
260 ))
261 .description(format!(
262 "{} - API",
263 app_config.oapi_name.as_ref().unwrap_or(&app_config.app_name)
264 ))
265 .into_router(app_config.oapi_api_addr.as_ref().unwrap()),
266 ),
267 _ => None,
268 }
269 } else {
270 None
271 };
272
273 let mut router = Router::new();
274 router = router.push(doc.into_router(format!("{}/openapi.json", app_config.oapi_api_addr.as_ref().unwrap())));
275 if let Some(oapi) = oapi_endpoint {
276 router = router.push(oapi);
277 }
278
279 unsafe {
280 let service_router = make_mut(service.router.as_ref());
281 service_router.routers_mut().insert(0, router);
282 }
283
284 tracing::info!("API is available on {}", app_config.oapi_api_addr.as_ref().unwrap());
285 }
286
287 #[cfg(feature = "cors")]
288 if let Some(domain) = &app_config.allow_cors_domain {
289 let cors = salvo::cors::Cors::new()
290 .allow_origin(domain)
291 .allow_credentials(domain.as_str() != "*")
292 .allow_headers(vec![
293 "Authorization",
294 "Accept",
295 "Access-Control-Allow-Headers",
296 "Content-Type",
297 "Origin",
298 "X-Requested-With",
299 "Cookie",
300 ])
301 .expose_headers(vec!["Set-Cookie"])
302 .allow_methods(vec![
303 salvo::http::Method::GET,
304 salvo::http::Method::POST,
305 salvo::http::Method::PUT,
306 salvo::http::Method::PATCH,
307 salvo::http::Method::DELETE,
308 salvo::http::Method::OPTIONS,
309 ])
310 .into_handler();
311
312 service = service.hoop(cors);
313 }
314
315 let handle;
316
317 let server = match app_state.startup_variant {
318 StartupVariant::HttpLocalhost => {
319 let acceptor = TcpListener::new(format!("127.0.0.1:{}", app_config.server_port.unwrap()))
320 .bind()
321 .await;
322 let server = Server::new(acceptor);
323 handle = server.handle();
324 Box::pin(server.serve(service)) as Pin<Box<dyn Future<Output = ()> + Send>>
325 }
326 StartupVariant::UnsafeHttp => {
327 let acceptor = TcpListener::new(format!(
328 "{}:{}",
329 app_config.server_host.as_ref().unwrap(),
330 app_config.server_port.unwrap()
331 ))
332 .bind()
333 .await;
334 let server = Server::new(acceptor);
335 handle = server.handle();
336 Box::pin(server.serve(service))
337 }
338 #[cfg(feature = "acme")]
339 StartupVariant::HttpsAcme => {
340 let acceptor = TcpListener::new(format!(
341 "{}:{}",
342 app_config.server_host.as_ref().unwrap(),
343 app_config.server_port.unwrap()
344 ))
345 .acme()
346 .cache_path("tmp/letsencrypt")
347 .add_domain(app_config.acme_domain.as_ref().unwrap())
348 .bind()
349 .await;
350 let server = Server::new(acceptor);
351 handle = server.handle();
352 Box::pin(server.serve(service))
353 }
354 StartupVariant::HttpsOnly => {
355 let rustls_config = tlsv13(
356 app_config.ssl_crt_path.as_ref().unwrap(),
357 app_config.ssl_key_path.as_ref().unwrap(),
358 )?;
359 let listener = TcpListener::new(format!(
360 "{}:{}",
361 app_config.server_host.as_ref().unwrap(),
362 app_config.server_port.unwrap()
363 ))
364 .rustls(rustls_config)
365 .bind()
366 .await;
367
368 let server = Server::new(listener);
369 handle = server.handle();
370 Box::pin(server.serve(service))
371 }
372 #[cfg(all(feature = "http3", feature = "acme"))]
373 StartupVariant::QuinnAcme => {
374 let acceptor = TcpListener::new(format!(
375 "{}:{}",
376 app_config.server_host.as_ref().unwrap(),
377 app_config.server_port.unwrap()
378 ))
379 .acme()
380 .cache_path("tmp/letsencrypt")
381 .add_domain(app_config.acme_domain.as_ref().unwrap())
382 .quinn(format!(
383 "{}:{}",
384 app_config.server_host.as_ref().unwrap(),
385 app_config.server_port.unwrap()
386 ))
387 .bind()
388 .await;
389 let server = Server::new(acceptor);
390 handle = server.handle();
391 Box::pin(server.serve(service))
392 }
393 #[cfg(feature = "http3")]
394 StartupVariant::Quinn => {
395 let rustls_config = tlsv13(
396 app_config.ssl_crt_path.as_ref().unwrap(),
397 app_config.ssl_key_path.as_ref().unwrap(),
398 )?;
399 let listener = TcpListener::new(format!(
400 "{}:{}",
401 app_config.server_host.as_ref().unwrap(),
402 app_config.server_port.unwrap()
403 ))
404 .rustls(rustls_config.clone());
405
406 let quinn_config = rustls_config
407 .build_quinn_config()
408 .map_err(|e| ServerError::from_private(e).with_500())?;
409 let acceptor = QuinnListener::new(
410 quinn_config,
411 format!(
412 "{}:{}",
413 app_config.server_host.as_ref().unwrap(),
414 app_config.server_port.unwrap()
415 ),
416 )
417 .join(listener)
418 .bind()
419 .await;
420
421 let server = Server::new(acceptor);
422 handle = server.handle();
423 Box::pin(server.serve(service))
424 }
425 #[cfg(feature = "http3")]
426 StartupVariant::QuinnOnly => {
427 let quinn_config = tlsv13(
428 app_config.ssl_crt_path.as_ref().unwrap(),
429 app_config.ssl_key_path.as_ref().unwrap(),
430 )?
431 .build_quinn_config()
432 .map_err(|e| ServerError::from_private(e).with_500())?;
433 let acceptor = QuinnListener::new(
434 quinn_config,
435 format!(
436 "{}:{}",
437 app_config.server_host.as_ref().unwrap(),
438 app_config.server_port.unwrap()
439 ),
440 )
441 .bind()
442 .await;
443
444 let server = Server::new(acceptor);
445 handle = server.handle();
446 Box::pin(server.serve(service))
447 }
448 };
449
450 Ok((server, handle))
451}
452
453pub async fn start_clean(
455 app_state: GenericServerState,
456 app_config: &impl GenericSetup,
457 router: Router,
458) -> MResult<(Pin<Box<dyn Future<Output = ()> + Send>>, ServerHandle)> {
459 start_with_service(app_state, app_config, Service::new(router)).await
460}
461
462pub async fn start(
464 app_state: GenericServerState,
465 app_config: &impl GenericSetup,
466 router: Router,
467) -> MResult<(Pin<Box<dyn Future<Output = ()> + Send>>, ServerHandle)> {
468 let (fut, handle) = start_clean(app_state, app_config, router).await?;
469 let ctrl_c_handle = handle.clone();
470 tokio::spawn(async move { shutdown_signal(ctrl_c_handle).await });
471 Ok((fut, handle))
472}
473
474pub async fn shutdown_signal(handle: ServerHandle) {
492 tokio::signal::ctrl_c().await.unwrap();
493 tracing::info!("Shutdown with Ctrl+C requested.");
494 handle.stop_graceful(None);
495}